diff --git a/CODEOWNERS b/CODEOWNERS index c6329d68d026b..c9b9feb2b2632 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -63,6 +63,7 @@ /bundles/org.openhab.binding.enigma2/ @gdolfen /bundles/org.openhab.binding.enocean/ @fruggy83 /bundles/org.openhab.binding.enturno/ @klocsson +/bundles/org.openhab.binding.epsonprojector/ @mlobstein /bundles/org.openhab.binding.etherrain/ @dfad1469 /bundles/org.openhab.binding.evohome/ @Nebula83 /bundles/org.openhab.binding.exec/ @kgoderis diff --git a/bom/openhab-addons/pom.xml b/bom/openhab-addons/pom.xml index 5f87670cbfa05..450ff96f58122 100644 --- a/bom/openhab-addons/pom.xml +++ b/bom/openhab-addons/pom.xml @@ -306,6 +306,11 @@ org.openhab.binding.enturno ${project.version} + + org.openhab.addons.bundles + org.openhab.binding.epsonprojector + ${project.version} + org.openhab.addons.bundles org.openhab.binding.etherrain diff --git a/bundles/org.openhab.binding.epsonprojector/NOTICE b/bundles/org.openhab.binding.epsonprojector/NOTICE new file mode 100644 index 0000000000000..38d625e349232 --- /dev/null +++ b/bundles/org.openhab.binding.epsonprojector/NOTICE @@ -0,0 +1,13 @@ +This content is produced and maintained by the openHAB project. + +* Project home: https://www.openhab.org + +== Declared Project Licenses + +This program and the accompanying materials are made available under the terms +of the Eclipse Public License 2.0 which is available at +https://www.eclipse.org/legal/epl-2.0/. + +== Source Code + +https://github.com/openhab/openhab-addons diff --git a/bundles/org.openhab.binding.epsonprojector/README.md b/bundles/org.openhab.binding.epsonprojector/README.md new file mode 100644 index 0000000000000..e2b31391ea819 --- /dev/null +++ b/bundles/org.openhab.binding.epsonprojector/README.md @@ -0,0 +1,174 @@ +# Epson Projector Binding + +This binding is compatible with Epson projectors that support the ESC/VP21 protocol over a serial port or USB to serial adapter. +Alternatively, you can connect to your projector via a TCP connection using a serial over IP device or by using`ser2net`. + +## Supported Things + +This binding supports two thing types based on the connection used: `projector-serial` and `projector-tcp`. + +## Discovery + +The projector thing cannot be auto-discovered, it has to be configured manually. + +## Binding Configuration + +There are no overall binding configuration settings that need to be set. +All settings are through thing configuration parameters. + +## Thing Configuration + +The `projector-serial` thing has the following configuration parameters: + +- _serialPort_: Serial port device name that is connected to the Epson projector to control, e.g. COM1 on Windows, /dev/ttyS0 on Linux or /dev/tty.PL2303-0000103D on Mac +- _pollingInterval_: Polling interval in seconds to update channel states | 5-60 seconds; default 10 seconds + +The `projector-tcp` thing has the following configuration parameters: + +- _host_: IP address for the serial over IP device +- _port_: Port for the serial over IP device +- _pollingInterval_: Polling interval in seconds to update channel states | 5-60 seconds; default 10 seconds + +Some notes: + +* The binding should work on all Epson projectors that support the ESC/VP21 protocol, however not all binding channels will be useable on all projectors. +* The _source_ channel includes a dropdown with the most common source inputs. +* If your projector has a source input that is not in the dropdown, the two digit hex code to access that input will be displayed by the _source_ channel when that input is selected by the remote control. +* By using the sitemap mapping or a rule to send the input's code back to the _source_ channel, any source on the projector can be accessed by the binding. +* The following channels _aspectratio_, _colormode_, _luminance_, _gamma_ and _background_ are pre-populated with a full set of options and not every option will be useable on all projectors. +* If your projector has an option in one of the above mentioned channels that is not recognized by the binding, the channel will display 'UNKNOWN' if that un-recognized option is selected by the remote control. +* If the projector power is switched to off in the middle of a polling operation, some of the channel values may become undefined until the projector is switched on again. + +* On Linux, you may get an error stating the serial port cannot be opened when the epsonprojector binding tries to load. +* You can get around this by adding the `openhab` user to the `dialout` group like this: `usermod -a -G dialout openhab`. +* Also on Linux you may have issues with the USB if using two serial USB devices e.g. epsonprojector and RFXcom. See the [general documentation about serial port configuration](/docs/administration/serial.html) for more on symlinking the USB ports. +* Here is an example of ser2net.conf you can use to share your serial port /dev/ttyUSB0 on IP port 4444 using [ser2net Linux tool](https://sourceforge.net/projects/ser2net/) (take care, the baud rate is specific to the Epson projector): + +``` +4444:raw:0:/dev/ttyUSB0:9600 8DATABITS NONE 1STOPBIT LOCAL +``` + +## Channels + +| Channel | Item Type | Purpose | Values | +| ------------------ | --------- | --------------------------------------------------- | --------- | +| power | Switch | Powers the projector on or off. | | +| powerstate | String | Retrieves the textual power state of the projector. | Read only | +| source | String | Retrieve or set the input source. | See above | +| aspectratio | String | Retrieve or set the aspect ratio. | See above | +| colormode | String | Retrieve or set the color mode. | See above | +| freeze | Switch | Turn the freeze screen mode on or off. | | +| mute | Switch | Turn the AV mute on or off. | | +| volume | Number | Retrieve or set the volume. | 0 - +20 | +| luminance | String | Retrieve or set the lamp mode. | See above | +| brightness | Number | Retrieve or set the brightness. | -24 - +24 | +| contrast | Number | Retrieve or set the contrast. | -24 - +24 | +| density | Number | Retrieve or set the density (color saturation). | -32 - +32 | +| tint | Number | Retrieve or set the tint. | -32 - +32 | +| colortemperature | Number | Retrieve or set the color temperature. | 0 - +9 | +| fleshtemperature | Number | Retrieve or set the flesh temperature. | 0 - +6 | +| gamma | String | Retrieve or set the gamma setting. | See above | +| autokeystone | Switch | Turn the auto keystone mode on or off. | | +| verticalkeystone | Number | Retrieve or set the vertical keystone. | -30 - +30 | +| horizontalkeystone | Number | Retrieve or set the horizontal keystone. | -30 - +30 | +| verticalposition | Number | Retrieve or set the vertical position. | -8 - +10 | +| horizontalposition | Number | Retrieve or set the horizontal position. | -23 - +26 | +| verticalreverse | Switch | Turn the vertical reverse mode on or off. | | +| horizontalreverse | Switch | Turn the horizontal reverse mode on or off. | | +| background | String | Retrieve or set the background color/logo. | See above | +| keycode | Number | Send a key operation command to the projector. | Send only | +| lamptime | Number | Retrieves the lamp hours. | Read only | +| errcode | Number | Retrieves the last error code. | Read only | +| errmessage | String | Retrieves the description of the last error. | Read only | + +## Full Example + +things/epson.things: + +```java +//serial port connection +epsonprojector:projector-serial:hometheater "Projector" [ serialPort="COM5", pollingInterval=10 ] + +// serial over IP connection +epsonprojector:projector-tcp:hometheater "Projector" [ host="192.168.0.10", port=4444, pollingInterval=10 ] + +``` + +items/epson.items + +``` +Switch epsonPower { channel="epsonprojector:projector:hometheater:power" } +String epsonSource "Source [%s]" { channel="epsonprojector:projector:hometheater:source" } +String epsonAspectRatio "Aspect Ratio [%s]" { channel="epsonprojector:projector:hometheater:aspectratio" } +String epsonColorMode "Color Mode [%s]" { channel="epsonprojector:projector:hometheater:colormode" } +Switch epsonFreeze { channel="epsonprojector:projector:hometheater:freeze" } +Switch epsonMute { channel="epsonprojector:projector:hometheater:mute" } +Number epsonVolume { channel="epsonprojector:projector:hometheater:volume" } +String epsonLuminance "Lamp Mode [%s]" { channel="epsonprojector:projector:hometheater:luminance" } + +Number epsonBrightness { channel="epsonprojector:projector:hometheater:brightness" } +Number epsonContrast { channel="epsonprojector:projector:hometheater:contrast" } +Number epsonDensity { channel="epsonprojector:projector:hometheater:density" } +Number epsonTint { channel="epsonprojector:projector:hometheater:tint" } +Number epsonColorTemperature { channel="epsonprojector:projector:hometheater:colortemperature" } +Number epsonFleshTemperature { channel="epsonprojector:projector:hometheater:fleshtemperature" } +String epsonGamma "Gamma [%s]" { channel="epsonprojector:projector:hometheater:gamma" } + +Switch epsonAutokeystone { channel="epsonprojector:projector:hometheater:autokeystone" } +Number epsonVerticalKeystone { channel="epsonprojector:projector:hometheater:verticalkeystone" } +Number epsonHorizontalKeystone { channel="epsonprojector:projector:hometheater:horizontalkeystone" } +Number epsonVerticalPosition { channel="epsonprojector:projector:hometheater:verticalposition" } +Number epsonHorizontalPosition { channel="epsonprojector:projector:hometheater:horizontalposition" } +Switch epsonVerticalReverse { channel="epsonprojector:projector:hometheater:verticalreverse" } +Switch epsonHorizontalReverse { channel="epsonprojector:projector:hometheater:horizontalreverse" } + +String epsonBackground "Background [%s]" { channel="epsonprojector:projector:hometheater:background" } +String epsonPowerState "Power State [%s]" { channel="epsonprojector:projector:hometheater:powerstate" } +Number epsonLampTime "Lamp Time [%d h]" { channel="epsonprojector:projector:hometheater:lamptime" } +Number epsonErrCode "Error Code [%d]" <"siren-on"> { channel="epsonprojector:projector:hometheater:errcode" } +String epsonErrMessage "Error Message [%s]" <"siren-off"> { channel="epsonprojector:projector:hometheater:errmessage" } +``` + +sitemaps/epson.sitemap + +``` +sitemap epson label="Epson Projector Demo" +{ + Frame label="Controls" { + Switch item=epsonPower label="Power" + Selection item=epsonSource label="Source" mappings=["30"="HDMI1", "A0"="HDMI2", "14"="Component", "20"="PC DSUB", "41"="Video", "42"="S-Video"] + Switch item=epsonFreeze label="Freeze" + Switch item=epsonMute label="AV Mute" + Setpoint item=epsonVolume label="Volume" + + } + Frame label="Adjust Image" { + Setpoint item=epsonBrightness label="Brightness" + Setpoint item=epsonContrast label="Contrast" + Setpoint item=epsonDensity label="Color Saturation" + Setpoint item=epsonTint label="Tint" + Switch item=epsonAutokeystone label="Auto Keystone" + Setpoint item=epsonVerticalKeystone label="Vertical Keystone" + Setpoint item=epsonHorizontalKeystone label="Horizontal Keystone" + Setpoint item=epsonVerticalPosition label="Vertical Position" + Setpoint item=epsonHorizontalPosition label="Horizontal Position" + Selection item=epsonBackground label="Background" + } + Frame label="Flip Projection" { + Switch item=epsonVerticalReverse label="Vertical Reverse" + Switch item=epsonHorizontalReverse label="Horizontal Reverse" + } + Frame label="Info" { + Text item=epsonAspectRatio + Text item=epsonColorMode + Text item=epsonColorTemperature label="Color Temperature" + Text item=epsonFleshTemperature label="Flesh Temperature" + Text item=epsonGamma + Text item=epsonLuminance + Text item=epsonPowerState + Text item=epsonLampTime + Text item=epsonErrCode + Text item=epsonErrMessage + } +} +``` diff --git a/bundles/org.openhab.binding.epsonprojector/pom.xml b/bundles/org.openhab.binding.epsonprojector/pom.xml new file mode 100644 index 0000000000000..48625fa887148 --- /dev/null +++ b/bundles/org.openhab.binding.epsonprojector/pom.xml @@ -0,0 +1,17 @@ + + + + 4.0.0 + + + org.openhab.addons.bundles + org.openhab.addons.reactor.bundles + 3.0.0-SNAPSHOT + + + org.openhab.binding.epsonprojector + + openHAB Add-ons :: Bundles :: Epson Projector Binding + + diff --git a/bundles/org.openhab.binding.epsonprojector/src/main/feature/feature.xml b/bundles/org.openhab.binding.epsonprojector/src/main/feature/feature.xml new file mode 100644 index 0000000000000..06f37bff5b409 --- /dev/null +++ b/bundles/org.openhab.binding.epsonprojector/src/main/feature/feature.xml @@ -0,0 +1,10 @@ + + + mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features + + + openhab-runtime-base + openhab-transport-serial + mvn:org.openhab.addons.bundles/org.openhab.binding.epsonprojector/${project.version} + + diff --git a/bundles/org.openhab.binding.epsonprojector/src/main/java/org/openhab/binding/epsonprojector/internal/EpsonProjectorBindingConstants.java b/bundles/org.openhab.binding.epsonprojector/src/main/java/org/openhab/binding/epsonprojector/internal/EpsonProjectorBindingConstants.java new file mode 100644 index 0000000000000..28439cec508d0 --- /dev/null +++ b/bundles/org.openhab.binding.epsonprojector/src/main/java/org/openhab/binding/epsonprojector/internal/EpsonProjectorBindingConstants.java @@ -0,0 +1,37 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.epsonprojector.internal; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.core.thing.ThingTypeUID; + +/** + * The {@link EpsonProjectorBindingConstants} class defines common constants, which are + * used across the whole binding. + * + * @author Yannick Schaus - Initial contribution + */ +@NonNullByDefault +public class EpsonProjectorBindingConstants { + + private static final String BINDING_ID = "epsonprojector"; + + // List of all Thing Type UIDs + public static final ThingTypeUID THING_TYPE_PROJECTOR_SERIAL = new ThingTypeUID(BINDING_ID, "projector-serial"); + public static final ThingTypeUID THING_TYPE_PROJECTOR_TCP = new ThingTypeUID(BINDING_ID, "projector-tcp"); + + // Some Channel types + public static final String CHANNEL_TYPE_POWER = "power"; + public static final String CHANNEL_TYPE_POWERSTATE = "powerstate"; + public static final String CHANNEL_TYPE_LAMPTIME = "lamptime"; +} diff --git a/bundles/org.openhab.binding.epsonprojector/src/main/java/org/openhab/binding/epsonprojector/internal/EpsonProjectorCommandException.java b/bundles/org.openhab.binding.epsonprojector/src/main/java/org/openhab/binding/epsonprojector/internal/EpsonProjectorCommandException.java new file mode 100644 index 0000000000000..c53e14368e844 --- /dev/null +++ b/bundles/org.openhab.binding.epsonprojector/src/main/java/org/openhab/binding/epsonprojector/internal/EpsonProjectorCommandException.java @@ -0,0 +1,30 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.epsonprojector.internal; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * Exception for Epson projector command errors. + * + * @author Michael Lobstein - Initial contribution + */ +@NonNullByDefault +public class EpsonProjectorCommandException extends Exception { + + private static final long serialVersionUID = -8048415193494625295L; + + public EpsonProjectorCommandException(String message) { + super(message); + } +} diff --git a/bundles/org.openhab.binding.epsonprojector/src/main/java/org/openhab/binding/epsonprojector/internal/EpsonProjectorCommandType.java b/bundles/org.openhab.binding.epsonprojector/src/main/java/org/openhab/binding/epsonprojector/internal/EpsonProjectorCommandType.java new file mode 100644 index 0000000000000..774d2cc08526c --- /dev/null +++ b/bundles/org.openhab.binding.epsonprojector/src/main/java/org/openhab/binding/epsonprojector/internal/EpsonProjectorCommandType.java @@ -0,0 +1,121 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.epsonprojector.internal; + +import java.io.InvalidClassException; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.core.items.Item; +import org.openhab.core.library.items.NumberItem; +import org.openhab.core.library.items.StringItem; +import org.openhab.core.library.items.SwitchItem; + +/** + * Represents all valid command types which could be processed by this + * binding. + * + * @author Pauli Anttila - Initial contribution + */ +@NonNullByDefault +public enum EpsonProjectorCommandType { + POWER("Power", SwitchItem.class), + POWER_STATE("PowerState", StringItem.class), + LAMP_TIME("LampTime", NumberItem.class), + KEY_CODE("KeyCode", NumberItem.class), + VKEYSTONE("VerticalKeystone", NumberItem.class), + HKEYSTONE("HorizontalKeystone", NumberItem.class), + AKEYSTONE("AutoKeystone", SwitchItem.class), + FREEZE("Freeze", SwitchItem.class), + ASPECT_RATIO("AspectRatio", StringItem.class), + LUMINANCE("Luminance", StringItem.class), + SOURCE("Source", StringItem.class), + BRIGHTNESS("Brightness", NumberItem.class), + CONTRAST("Contrast", NumberItem.class), + DENSITY("Density", NumberItem.class), + TINT("Tint", NumberItem.class), + COLOR_TEMP("ColorTemperature", NumberItem.class), + FLESH_TEMP("FleshTemperature", NumberItem.class), + COLOR_MODE("ColorMode", StringItem.class), + HPOSITION("HorizontalPosition", NumberItem.class), + VPOSITION("VerticalPosition", NumberItem.class), + GAMMA("Gamma", StringItem.class), + VOLUME("Volume", NumberItem.class), + MUTE("Mute", SwitchItem.class), + HREVERSE("HorizontalReverse", SwitchItem.class), + VREVERSE("VerticalReverse", SwitchItem.class), + BACKGROUND("Background", StringItem.class), + ERR_CODE("ErrCode", NumberItem.class), + ERR_MESSAGE("ErrMessage", StringItem.class),; + + private final String text; + private Class itemClass; + + private EpsonProjectorCommandType(final String text, Class itemClass) { + this.text = text; + this.itemClass = itemClass; + } + + @Override + public String toString() { + return text; + } + + public Class getItemClass() { + return itemClass; + } + + /** + * Procedure to validate command type string. + * + * @param commandTypeText + * command string e.g. RawData, Command, Brightness + * @return true if item is valid. + * @throws IllegalArgumentException + * Not valid command type. + * @throws InvalidClassException + * Not valid class for command type. + */ + public static boolean validateBinding(String commandTypeText, Class itemClass) + throws IllegalArgumentException, InvalidClassException { + for (EpsonProjectorCommandType c : EpsonProjectorCommandType.values()) { + if (c.text.equalsIgnoreCase(commandTypeText)) { + if (c.getItemClass().equals(itemClass)) { + return true; + } else { + throw new InvalidClassException("Not valid class for command type"); + } + } + } + + throw new IllegalArgumentException("Not valid command type"); + } + + /** + * Procedure to convert command type string to command type class. + * + * @param commandTypeText + * command string e.g. RawData, Command, Brightness + * @return corresponding command type. + * @throws InvalidClassException + * Not valid class for command type. + */ + public static EpsonProjectorCommandType getCommandType(String commandTypeText) throws IllegalArgumentException { + for (EpsonProjectorCommandType c : EpsonProjectorCommandType.values()) { + if (c.text.equalsIgnoreCase(commandTypeText)) { + return c; + } + } + + throw new IllegalArgumentException("Not valid command type"); + } +} diff --git a/bundles/org.openhab.binding.epsonprojector/src/main/java/org/openhab/binding/epsonprojector/internal/EpsonProjectorDevice.java b/bundles/org.openhab.binding.epsonprojector/src/main/java/org/openhab/binding/epsonprojector/internal/EpsonProjectorDevice.java new file mode 100644 index 0000000000000..37875759ab366 --- /dev/null +++ b/bundles/org.openhab.binding.epsonprojector/src/main/java/org/openhab/binding/epsonprojector/internal/EpsonProjectorDevice.java @@ -0,0 +1,626 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.epsonprojector.internal; + +import java.time.Duration; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.epsonprojector.internal.configuration.EpsonProjectorConfiguration; +import org.openhab.binding.epsonprojector.internal.connector.EpsonProjectorConnector; +import org.openhab.binding.epsonprojector.internal.connector.EpsonProjectorSerialConnector; +import org.openhab.binding.epsonprojector.internal.connector.EpsonProjectorTcpConnector; +import org.openhab.binding.epsonprojector.internal.enums.AspectRatio; +import org.openhab.binding.epsonprojector.internal.enums.Background; +import org.openhab.binding.epsonprojector.internal.enums.ColorMode; +import org.openhab.binding.epsonprojector.internal.enums.ErrorMessage; +import org.openhab.binding.epsonprojector.internal.enums.Gamma; +import org.openhab.binding.epsonprojector.internal.enums.Luminance; +import org.openhab.binding.epsonprojector.internal.enums.PowerStatus; +import org.openhab.binding.epsonprojector.internal.enums.Switch; +import org.openhab.core.cache.ExpiringCache; +import org.openhab.core.io.transport.serial.SerialPortManager; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Provide high level interface to Epson projector. + * + * @author Pauli Anttila - Initial contribution + * @author Yannick Schaus - Refactoring + * @author Michael Lobstein - Improvements for OH3 + */ +@NonNullByDefault +public class EpsonProjectorDevice { + 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, + 70, 74, 78, 82, 86, 90, 94, 98, 102, 106, 110, 114, 118, 122, 126, 129, 133, 137, 141, 145, 149, 153, 157, + 161, 165, 169, 173, 177, 181, 185, 189, 192, 196, 200, 204, 208, 212, 216, 220, 224, 228, 232, 236, 240, + 244, 248, 252 }; + + 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, + 75, 79, 83, 88, 92, 96, 100, 104, 109, 113, 117, 121, 125, 130, 134, 138, 142, 146, 151, 155, 159, 163, 167, + 172, 176, 180, 184, 188, 193, 197, 201, 205, 209, 214, 218, 222, 226, 230, 235, 239, 243, 247, 251 }; + + 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, + 92, 97, 102, 107, 112, 117, 122, 128, 133, 138, 143, 148, 153, 158, 163, 168, 174, 179, 184, 189, 194, 199, + 204, 209, 215, 220, 225, 230, 235, 240, 245, 250 }; + + 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, + 94, 99, 104, 109, 114, 120, 125, 130, 135, 141, 146, 151, 156, 161, 167, 172, 177, 182, 188, 193, 198, 203, + 208, 214, 219, 224, 229, 235, 240, 245, 250 }; + + private static final int[] MAP20 = new int[] { 0, 12, 24, 36, 48, 60, 73, 85, 97, 109, 121, 134, 146, 158, 170, 182, + 195, 207, 219, 231, 243 }; + + private static final int[] MAP18 = new int[] { 0, 13, 26, 40, 53, 67, 80, 94, 107, 121, 134, 148, 161, 175, 188, + 202, 215, 229, 242 }; + + private static final int[] MAP_COLOR_TEMP = new int[] { 0, 25, 51, 76, 102, 128, 153, 179, 204, 230 }; + private static final int[] MAP_FLESH_COLOR = new int[] { 0, 36, 73, 109, 146, 182, 219 }; + + private static final int DEFAULT_TIMEOUT = 5 * 1000; + private static final int POWER_ON_TIMEOUT = 100 * 1000; + private static final int POWER_OFF_TIMEOUT = 130 * 1000; + private static final int LAMP_REFRESH_WAIT_MINUTES = 5; + + private static final String ON = "ON"; + private static final String ERR = "ERR"; + + private final Logger logger = LoggerFactory.getLogger(EpsonProjectorDevice.class); + + private @Nullable ScheduledExecutorService scheduler = null; + private @Nullable ScheduledFuture timeoutJob; + + private EpsonProjectorConnector connection; + private ExpiringCache cachedLampHours = new ExpiringCache<>(Duration.ofMinutes(LAMP_REFRESH_WAIT_MINUTES), + this::queryLamp); + private boolean connected = false; + private boolean ready = true; + + public EpsonProjectorDevice(SerialPortManager serialPortManager, EpsonProjectorConfiguration config) { + connection = new EpsonProjectorSerialConnector(serialPortManager, config.serialPort); + } + + public EpsonProjectorDevice(EpsonProjectorConfiguration config) { + connection = new EpsonProjectorTcpConnector(config.host, config.port); + } + + public boolean isReady() { + return ready; + } + + public void setScheduler(ScheduledExecutorService scheduler) { + this.scheduler = scheduler; + } + + private synchronized @Nullable String sendQuery(String query, int timeout) + throws EpsonProjectorCommandException, EpsonProjectorException { + logger.debug("Query: '{}'", query); + String response = connection.sendMessage(query, timeout); + + if (response.length() == 0) { + throw new EpsonProjectorException("No response received"); + } + + response = response.replace("\r:", ""); + logger.debug("Response: '{}'", response); + + if (ERR.equals(response)) { + throw new EpsonProjectorCommandException("Error response received for command: " + query); + } + + if ("PWR OFF".equals(query) && ":".equals(response)) { + // When PWR OFF command is sent, next command can be sent 10 seconds after the colon is received + logger.debug("Refusing further commands for 10 seconds to power OFF completion"); + ready = false; + ScheduledExecutorService scheduler = this.scheduler; + if (scheduler != null) { + timeoutJob = scheduler.schedule(() -> { + ready = true; + }, 10, TimeUnit.SECONDS); + } + } + + return response; + } + + private String splitResponse(@Nullable String response) throws EpsonProjectorException { + if (response != null && !"".equals(response)) { + String[] pieces = response.split("="); + + if (pieces.length < 2) { + throw new EpsonProjectorException("Invalid response from projector: " + response); + } + + return pieces[1].trim(); + } else { + throw new EpsonProjectorException("No response received"); + } + } + + protected void sendCommand(String command, int timeout) + throws EpsonProjectorCommandException, EpsonProjectorException { + sendQuery(command, timeout); + } + + protected void sendCommand(String command) throws EpsonProjectorCommandException, EpsonProjectorException { + sendCommand(command, DEFAULT_TIMEOUT); + } + + protected int queryInt(String query, int timeout, int radix) + throws EpsonProjectorCommandException, EpsonProjectorException { + String response = sendQuery(query, timeout); + + String str = splitResponse(response); + + // if the response has two number groups, get the first one (Aspect Ratio does this) + if (str.contains(" ")) { + String[] subStr = str.split(" "); + str = subStr[0]; + } + + return Integer.parseInt(str, radix); + } + + protected int queryInt(String query, int timeout) throws EpsonProjectorCommandException, EpsonProjectorException { + return queryInt(query, timeout, 10); + } + + protected int queryInt(String query) throws EpsonProjectorCommandException, EpsonProjectorException { + return queryInt(query, DEFAULT_TIMEOUT, 10); + } + + protected int queryHexInt(String query, int timeout) + throws EpsonProjectorCommandException, EpsonProjectorException { + return queryInt(query, timeout, 16); + } + + protected int queryHexInt(String query) throws EpsonProjectorCommandException, EpsonProjectorException { + return queryInt(query, DEFAULT_TIMEOUT, 16); + } + + protected String queryString(String query) throws EpsonProjectorCommandException, EpsonProjectorException { + String response = sendQuery(query, DEFAULT_TIMEOUT); + return splitResponse(response); + } + + public void connect() throws EpsonProjectorException { + connection.connect(); + connected = true; + } + + public void disconnect() throws EpsonProjectorException { + connection.disconnect(); + connected = false; + ScheduledFuture timeoutJob = this.timeoutJob; + if (timeoutJob != null) { + timeoutJob.cancel(true); + this.timeoutJob = null; + } + } + + public boolean isConnected() { + return connected; + } + + /* + * Power + */ + public PowerStatus getPowerStatus() throws EpsonProjectorCommandException, EpsonProjectorException { + int val = queryInt("PWR?"); + return PowerStatus.forValue(val); + } + + public void setPower(Switch value) throws EpsonProjectorCommandException, EpsonProjectorException { + sendCommand(String.format("PWR %s", value.name()), value == Switch.ON ? POWER_ON_TIMEOUT : POWER_OFF_TIMEOUT); + } + + /* + * Key code + */ + public void sendKeyCode(int value) throws EpsonProjectorCommandException, EpsonProjectorException { + sendCommand(String.format("KEY %02X", value)); + } + + /* + * Vertical Keystone + */ + public int getVerticalKeystone() throws EpsonProjectorCommandException, EpsonProjectorException { + int vkey = queryInt("VKEYSTONE?"); + for (int i = 0; i < MAP60.length; i++) { + if (vkey == MAP60[i]) { + return i - 30; + } + } + return 0; + } + + public void setVerticalKeystone(int value) throws EpsonProjectorCommandException, EpsonProjectorException { + value = value + 30; + if (value >= 0 && value <= 60) { + sendCommand(String.format("VKEYSTONE %d", MAP60[value])); + } + } + + /* + * Horizontal Keystone + */ + public int getHorizontalKeystone() throws EpsonProjectorCommandException, EpsonProjectorException { + int hkey = queryInt("HKEYSTONE?"); + for (int i = 0; i < MAP60.length; i++) { + if (hkey == MAP60[i]) { + return i - 30; + } + } + return 0; + } + + public void setHorizontalKeystone(int value) throws EpsonProjectorCommandException, EpsonProjectorException { + value = value + 30; + if (value >= 0 && value <= 60) { + sendCommand(String.format("HKEYSTONE %d", MAP60[value])); + } + } + + /* + * Auto Keystone + */ + + public Switch getAutoKeystone() throws EpsonProjectorCommandException, EpsonProjectorException { + String val = queryString("AUTOKEYSTONE?"); + return val.equals(ON) ? Switch.ON : Switch.OFF; + } + + public void setAutoKeystone(Switch value) throws EpsonProjectorCommandException, EpsonProjectorException { + sendCommand(String.format("AUTOKEYSTONE %s", value.name()), DEFAULT_TIMEOUT); + } + + /* + * Freeze + */ + public Switch getFreeze() throws EpsonProjectorCommandException, EpsonProjectorException { + String val = queryString("FREEZE?"); + return val.equals(ON) ? Switch.ON : Switch.OFF; + } + + public void setFreeze(Switch value) throws EpsonProjectorCommandException, EpsonProjectorException { + sendCommand(String.format("FREEZE %s", value.name()), DEFAULT_TIMEOUT); + } + + /* + * Aspect Ratio + */ + public AspectRatio getAspectRatio() throws EpsonProjectorCommandException, EpsonProjectorException { + int val = queryHexInt("ASPECT?"); + return AspectRatio.forValue(val); + } + + public void setAspectRatio(AspectRatio value) throws EpsonProjectorCommandException, EpsonProjectorException { + sendCommand(String.format("ASPECT %02X", value.toInt())); + } + + /* + * Luminance + */ + public Luminance getLuminance() throws EpsonProjectorCommandException, EpsonProjectorException { + int val = queryHexInt("LUMINANCE?"); + return Luminance.forValue(val); + } + + public void setLuminance(Luminance value) throws EpsonProjectorCommandException, EpsonProjectorException { + sendCommand(String.format("LUMINANCE %02X", value.toInt())); + } + + /* + * Source + */ + public String getSource() throws EpsonProjectorCommandException, EpsonProjectorException { + return queryString("SOURCE?"); + } + + public void setSource(String value) throws EpsonProjectorCommandException, EpsonProjectorException { + sendCommand(String.format("SOURCE %s", value)); + } + + /* + * Brightness + */ + public int getBrightness() throws EpsonProjectorCommandException, EpsonProjectorException { + int brt = queryInt("BRIGHT?"); + for (int i = 0; i < MAP48.length; i++) { + if (brt == MAP48[i]) { + return i - 24; + } + } + return 0; + } + + public void setBrightness(int value) throws EpsonProjectorCommandException, EpsonProjectorException { + value = value + 24; + if (value >= 0 && value <= 48) { + sendCommand(String.format("BRIGHT %d", MAP48[value])); + } + } + + /* + * Contrast + */ + public int getContrast() throws EpsonProjectorCommandException, EpsonProjectorException { + int con = queryInt("CONTRAST?"); + for (int i = 0; i < MAP48.length; i++) { + if (con == MAP48[i]) { + return i - 24; + } + } + return 0; + } + + public void setContrast(int value) throws EpsonProjectorCommandException, EpsonProjectorException { + value = value + 24; + if (value >= 0 && value <= 48) { + sendCommand(String.format("CONTRAST %d", MAP48[value])); + } + } + + /* + * Density + */ + public int getDensity() throws EpsonProjectorCommandException, EpsonProjectorException { + int den = queryInt("DENSITY?"); + for (int i = 0; i < MAP64.length; i++) { + if (den == MAP64[i]) { + return i - 32; + } + } + return 0; + } + + public void setDensity(int value) throws EpsonProjectorCommandException, EpsonProjectorException { + value = value + 32; + if (value >= 0 && value <= 64) { + sendCommand(String.format("DENSITY %d", MAP64[value])); + } + } + + /* + * Tint + */ + public int getTint() throws EpsonProjectorCommandException, EpsonProjectorException { + int tint = queryInt("TINT?"); + for (int i = 0; i < MAP64.length; i++) { + if (tint == MAP64[i]) { + return i - 32; + } + } + return 0; + } + + public void setTint(int value) throws EpsonProjectorCommandException, EpsonProjectorException { + value = value + 32; + if (value >= 0 && value <= 64) { + sendCommand(String.format("TINT %d", MAP64[value])); + } + } + + /* + * Color Temperature + */ + public int getColorTemperature() throws EpsonProjectorCommandException, EpsonProjectorException { + int ctemp = queryInt("CTEMP?"); + for (int i = 0; i < MAP_COLOR_TEMP.length; i++) { + if (ctemp == MAP_COLOR_TEMP[i]) { + return i; + } + } + return 0; + } + + public void setColorTemperature(int value) throws EpsonProjectorCommandException, EpsonProjectorException { + if (value >= 0 && value <= 9) { + sendCommand(String.format("CTEMP %d", MAP_COLOR_TEMP[value])); + } + } + + /* + * Flesh Color + */ + public int getFleshColor() throws EpsonProjectorCommandException, EpsonProjectorException { + int fclr = queryInt("FCOLOR?"); + for (int i = 0; i < MAP_FLESH_COLOR.length; i++) { + if (fclr == MAP_FLESH_COLOR[i]) { + return i; + } + } + return 0; + } + + public void setFleshColor(int value) throws EpsonProjectorCommandException, EpsonProjectorException { + if (value >= 0 && value <= 6) { + sendCommand(String.format("FCOLOR %d", MAP_FLESH_COLOR[value])); + } + } + + /* + * Color Mode + */ + public ColorMode getColorMode() throws EpsonProjectorCommandException, EpsonProjectorException { + int val = queryHexInt("CMODE?"); + return ColorMode.forValue(val); + } + + public void setColorMode(ColorMode value) throws EpsonProjectorCommandException, EpsonProjectorException { + sendCommand(String.format("CMODE %02X", value.toInt())); + } + + /* + * Horizontal Position + */ + public int getHorizontalPosition() throws EpsonProjectorCommandException, EpsonProjectorException { + int hpos = queryInt("HPOS?"); + for (int i = 0; i < MAP49.length; i++) { + if (hpos == MAP49[i]) { + return i - 23; + } + } + return 0; + } + + public void setHorizontalPosition(int value) throws EpsonProjectorCommandException, EpsonProjectorException { + value = value + 23; + if (value >= 0 && value <= 49) { + sendCommand(String.format("HPOS %d", MAP49[value])); + } + } + + /* + * Vertical Position + */ + public int getVerticalPosition() throws EpsonProjectorCommandException, EpsonProjectorException { + int vpos = queryInt("VPOS?"); + for (int i = 0; i < MAP18.length; i++) { + if (vpos == MAP18[i]) { + return i - 8; + } + } + return 0; + } + + public void setVerticalPosition(int value) throws EpsonProjectorCommandException, EpsonProjectorException { + value = value + 8; + if (value >= 0 && value <= 18) { + sendCommand(String.format("VPOS %d", MAP18[value])); + } + } + + /* + * Gamma + */ + public Gamma getGamma() throws EpsonProjectorCommandException, EpsonProjectorException { + int val = queryHexInt("GAMMA?"); + return Gamma.forValue(val); + } + + public void setGamma(Gamma value) throws EpsonProjectorCommandException, EpsonProjectorException { + sendCommand(String.format("GAMMA %02X", value.toInt())); + } + + /* + * Volume + */ + public int getVolume() throws EpsonProjectorCommandException, EpsonProjectorException { + int vol = queryInt("VOL?"); + for (int i = 0; i < MAP20.length; i++) { + if (vol == MAP20[i]) { + return i; + } + } + return 0; + } + + public void setVolume(int value) throws EpsonProjectorCommandException, EpsonProjectorException { + if (value >= 0 && value <= 20) { + sendCommand(String.format("VOL %d", MAP20[value])); + } + } + + /* + * AV Mute + */ + public Switch getMute() throws EpsonProjectorCommandException, EpsonProjectorException { + String val = queryString("MUTE?"); + return val.equals(ON) ? Switch.ON : Switch.OFF; + } + + public void setMute(Switch value) throws EpsonProjectorCommandException, EpsonProjectorException { + sendCommand(String.format("MUTE %s", value.name())); + } + + /* + * Horizontal Reverse + */ + public Switch getHorizontalReverse() throws EpsonProjectorCommandException, EpsonProjectorException { + String val = queryString("HREVERSE?"); + return val.equals(ON) ? Switch.ON : Switch.OFF; + } + + public void setHorizontalReverse(Switch value) throws EpsonProjectorCommandException, EpsonProjectorException { + sendCommand(String.format("HREVERSE %s", value.name())); + } + + /* + * Vertical Reverse + */ + public Switch getVerticalReverse() throws EpsonProjectorCommandException, EpsonProjectorException { + String val = queryString("VREVERSE?"); + return val.equals(ON) ? Switch.ON : Switch.OFF; + } + + public void setVerticalReverse(Switch value) throws EpsonProjectorCommandException, EpsonProjectorException { + sendCommand(String.format("VREVERSE %s", value.name())); + } + + /* + * Background Select for AV Mute + */ + public Background getBackground() throws EpsonProjectorCommandException, EpsonProjectorException { + int val = queryHexInt("MSEL?"); + return Background.forValue(val); + } + + public void setBackground(Background value) throws EpsonProjectorCommandException, EpsonProjectorException { + sendCommand(String.format("MSEL %02X", value.toInt())); + } + + /* + * Lamp Time (hours) - get from cache + */ + public int getLampTime() throws EpsonProjectorCommandException, EpsonProjectorException { + Integer lampHours = cachedLampHours.getValue(); + + if (lampHours != null) { + return lampHours.intValue(); + } else { + throw new EpsonProjectorCommandException("cachedLampHours returned null"); + } + } + + /* + * Get Lamp Time + */ + private @Nullable Integer queryLamp() { + try { + return Integer.valueOf(queryInt("LAMP?")); + } catch (EpsonProjectorCommandException | EpsonProjectorException e) { + logger.debug("Error executing command LAMP?", e); + return null; + } + } + + /* + * Error Code + */ + public int getError() throws EpsonProjectorCommandException, EpsonProjectorException { + return queryHexInt("ERR?"); + } + + /* + * Error Code Description + */ + public String getErrorString() throws EpsonProjectorCommandException, EpsonProjectorException { + int err = queryInt("ERR?"); + return ErrorMessage.forCode(err); + } +} diff --git a/bundles/org.openhab.binding.epsonprojector/src/main/java/org/openhab/binding/epsonprojector/internal/EpsonProjectorException.java b/bundles/org.openhab.binding.epsonprojector/src/main/java/org/openhab/binding/epsonprojector/internal/EpsonProjectorException.java new file mode 100644 index 0000000000000..82f51df08d92d --- /dev/null +++ b/bundles/org.openhab.binding.epsonprojector/src/main/java/org/openhab/binding/epsonprojector/internal/EpsonProjectorException.java @@ -0,0 +1,34 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.epsonprojector.internal; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * Exception for Epson projector errors. + * + * @author Pauli Anttila - Initial contribution + */ +@NonNullByDefault +public class EpsonProjectorException extends Exception { + + private static final long serialVersionUID = -8048415193494625295L; + + public EpsonProjectorException(String message) { + super(message); + } + + public EpsonProjectorException(Throwable cause) { + super(cause); + } +} diff --git a/bundles/org.openhab.binding.epsonprojector/src/main/java/org/openhab/binding/epsonprojector/internal/EpsonProjectorHandlerFactory.java b/bundles/org.openhab.binding.epsonprojector/src/main/java/org/openhab/binding/epsonprojector/internal/EpsonProjectorHandlerFactory.java new file mode 100644 index 0000000000000..815e06701d23c --- /dev/null +++ b/bundles/org.openhab.binding.epsonprojector/src/main/java/org/openhab/binding/epsonprojector/internal/EpsonProjectorHandlerFactory.java @@ -0,0 +1,70 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.epsonprojector.internal; + +import static org.openhab.binding.epsonprojector.internal.EpsonProjectorBindingConstants.*; + +import java.util.Collections; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.epsonprojector.internal.handler.EpsonProjectorHandler; +import org.openhab.core.io.transport.serial.SerialPortManager; +import org.openhab.core.thing.Thing; +import org.openhab.core.thing.ThingTypeUID; +import org.openhab.core.thing.binding.BaseThingHandlerFactory; +import org.openhab.core.thing.binding.ThingHandler; +import org.openhab.core.thing.binding.ThingHandlerFactory; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; + +/** + * The {@link EpsonProjectorHandlerFactory} is responsible for creating things and thing + * handlers. + * + * @author Yannick Schaus - Initial contribution + * @author Michael Lobstein - Improvements for OH3 + */ +@NonNullByDefault +@Component(configurationPid = "binding.epsonprojector", service = ThingHandlerFactory.class) +public class EpsonProjectorHandlerFactory extends BaseThingHandlerFactory { + + private static final Set SUPPORTED_THING_TYPES_UIDS = Collections.unmodifiableSet( + Stream.of(THING_TYPE_PROJECTOR_SERIAL, THING_TYPE_PROJECTOR_TCP).collect(Collectors.toSet())); + private final SerialPortManager serialPortManager; + + @Override + public boolean supportsThingType(ThingTypeUID thingTypeUID) { + return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID); + } + + @Activate + public EpsonProjectorHandlerFactory(final @Reference SerialPortManager serialPortManager) { + this.serialPortManager = serialPortManager; + } + + @Override + protected @Nullable ThingHandler createHandler(Thing thing) { + ThingTypeUID thingTypeUID = thing.getThingTypeUID(); + + if (THING_TYPE_PROJECTOR_SERIAL.equals(thingTypeUID) || THING_TYPE_PROJECTOR_TCP.equals(thingTypeUID)) { + return new EpsonProjectorHandler(thing, serialPortManager); + } + + return null; + } +} diff --git a/bundles/org.openhab.binding.epsonprojector/src/main/java/org/openhab/binding/epsonprojector/internal/configuration/EpsonProjectorConfiguration.java b/bundles/org.openhab.binding.epsonprojector/src/main/java/org/openhab/binding/epsonprojector/internal/configuration/EpsonProjectorConfiguration.java new file mode 100644 index 0000000000000..e34021cfd3ae4 --- /dev/null +++ b/bundles/org.openhab.binding.epsonprojector/src/main/java/org/openhab/binding/epsonprojector/internal/configuration/EpsonProjectorConfiguration.java @@ -0,0 +1,44 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.epsonprojector.internal.configuration; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link EpsonProjectorConfiguration} class contains fields mapping thing configuration parameters. + * + * @author Yannick Schaus - Initial contribution + */ +@NonNullByDefault +public class EpsonProjectorConfiguration { + + /** + * Serial port used for communication. + */ + public String serialPort = ""; + + /** + * Host or IP address used for communication over a TCP link (if serialPort is not set). + */ + public String host = ""; + + /** + * Port used for communication over a TCP link (if serialPort is not set). + */ + public int port; + + /** + * Polling interval to refresh states. + */ + public int pollingInterval; +} diff --git a/bundles/org.openhab.binding.epsonprojector/src/main/java/org/openhab/binding/epsonprojector/internal/connector/EpsonProjectorConnector.java b/bundles/org.openhab.binding.epsonprojector/src/main/java/org/openhab/binding/epsonprojector/internal/connector/EpsonProjectorConnector.java new file mode 100644 index 0000000000000..91484ad136239 --- /dev/null +++ b/bundles/org.openhab.binding.epsonprojector/src/main/java/org/openhab/binding/epsonprojector/internal/connector/EpsonProjectorConnector.java @@ -0,0 +1,52 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.epsonprojector.internal.connector; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.epsonprojector.internal.EpsonProjectorException; + +/** + * Base class for Epson projector communication. + * + * @author Pauli Anttila - Initial contribution + */ +@NonNullByDefault +public interface EpsonProjectorConnector { + + /** + * Procedure for connecting to projector. + * + * @throws EpsonProjectorException + */ + void connect() throws EpsonProjectorException; + + /** + * Procedure for disconnecting to projector controller. + * + * @throws EpsonProjectorException + */ + void disconnect() throws EpsonProjectorException; + + /** + * Procedure for send raw data to projector. + * + * @param data + * Message to send. + * + * @param timeout + * timeout to wait response in milliseconds. + * + * @throws EpsonProjectorException + */ + String sendMessage(String data, int timeout) throws EpsonProjectorException; +} diff --git a/bundles/org.openhab.binding.epsonprojector/src/main/java/org/openhab/binding/epsonprojector/internal/connector/EpsonProjectorSerialConnector.java b/bundles/org.openhab.binding.epsonprojector/src/main/java/org/openhab/binding/epsonprojector/internal/connector/EpsonProjectorSerialConnector.java new file mode 100644 index 0000000000000..9523c68768890 --- /dev/null +++ b/bundles/org.openhab.binding.epsonprojector/src/main/java/org/openhab/binding/epsonprojector/internal/connector/EpsonProjectorSerialConnector.java @@ -0,0 +1,209 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.epsonprojector.internal.connector; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.charset.StandardCharsets; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.epsonprojector.internal.EpsonProjectorException; +import org.openhab.core.io.transport.serial.PortInUseException; +import org.openhab.core.io.transport.serial.SerialPort; +import org.openhab.core.io.transport.serial.SerialPortEvent; +import org.openhab.core.io.transport.serial.SerialPortEventListener; +import org.openhab.core.io.transport.serial.SerialPortIdentifier; +import org.openhab.core.io.transport.serial.SerialPortManager; +import org.openhab.core.io.transport.serial.UnsupportedCommOperationException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Connector for serial port communication. + * + * @author Pauli Anttila - Initial contribution + * @author Michael Lobstein - Improvements for OH3 + */ +@NonNullByDefault +public class EpsonProjectorSerialConnector implements EpsonProjectorConnector, SerialPortEventListener { + + private final Logger logger = LoggerFactory.getLogger(EpsonProjectorSerialConnector.class); + private final String serialPortName; + private final SerialPortManager serialPortManager; + + private @Nullable InputStream in = null; + private @Nullable OutputStream out = null; + private @Nullable SerialPort serialPort = null; + + public EpsonProjectorSerialConnector(SerialPortManager serialPortManager, String serialPort) { + this.serialPortManager = serialPortManager; + this.serialPortName = serialPort; + } + + @Override + public void connect() throws EpsonProjectorException { + try { + logger.debug("Open connection to serial port '{}'", serialPortName); + + SerialPortIdentifier serialPortIdentifier = serialPortManager.getIdentifier(serialPortName); + + if (serialPortIdentifier == null) { + throw new IOException("Unknown serial port"); + } + SerialPort serialPort = serialPortIdentifier.open(this.getClass().getName(), 2000); + serialPort.setSerialPortParams(9600, SerialPort.DATABITS_8, SerialPort.STOPBITS_1, SerialPort.PARITY_NONE); + serialPort.enableReceiveThreshold(1); + serialPort.disableReceiveTimeout(); + + InputStream in = serialPort.getInputStream(); + OutputStream out = serialPort.getOutputStream(); + + if (in != null && out != null) { + out.flush(); + if (in.markSupported()) { + in.reset(); + } + + serialPort.notifyOnDataAvailable(true); + + this.serialPort = serialPort; + this.in = in; + this.out = out; + } + } catch (PortInUseException | UnsupportedCommOperationException | IOException e) { + throw new EpsonProjectorException(e); + } + } + + @Override + public void disconnect() throws EpsonProjectorException { + InputStream in = this.in; + OutputStream out = this.out; + SerialPort serialPort = this.serialPort; + + if (out != null) { + logger.debug("Close serial out stream"); + try { + out.close(); + } catch (IOException e) { + logger.debug("Error occurred when closing serial out stream: {}", e.getMessage()); + } + this.out = null; + } + if (in != null) { + logger.debug("Close serial in stream"); + try { + in.close(); + } catch (IOException e) { + logger.debug("Error occurred when closing serial in stream: {}", e.getMessage()); + } + this.in = null; + } + if (serialPort != null) { + logger.debug("Close serial port"); + serialPort.close(); + serialPort.removeEventListener(); + this.serialPort = null; + } + + logger.debug("Closed"); + } + + @Override + public String sendMessage(String data, int timeout) throws EpsonProjectorException { + InputStream in = this.in; + OutputStream out = this.out; + + if (in == null || out == null) { + connect(); + in = this.in; + out = this.out; + } + + try { + if (in != null && out != null) { + // flush input stream + if (in.markSupported()) { + in.reset(); + } else { + while (in.available() > 0) { + int availableBytes = in.available(); + + if (availableBytes > 0) { + byte[] tmpData = new byte[availableBytes]; + in.read(tmpData, 0, availableBytes); + } + } + } + return sendMmsg(data, timeout); + } else { + return ""; + } + } catch (IOException e) { + logger.debug("IO error occurred...reconnect and resend once: {}", e.getMessage()); + disconnect(); + connect(); + + try { + return sendMmsg(data, timeout); + } catch (IOException e1) { + throw new EpsonProjectorException(e); + } + } + } + + @Override + public void serialEvent(SerialPortEvent arg0) { + } + + private String sendMmsg(String data, int timeout) throws IOException, EpsonProjectorException { + String resp = ""; + + InputStream in = this.in; + OutputStream out = this.out; + + if (in != null && out != null) { + out.write(data.getBytes(StandardCharsets.US_ASCII)); + out.write("\r\n".getBytes(StandardCharsets.US_ASCII)); + out.flush(); + + long startTime = System.currentTimeMillis(); + long elapsedTime = 0; + + while (elapsedTime < timeout) { + int availableBytes = in.available(); + if (availableBytes > 0) { + byte[] tmpData = new byte[availableBytes]; + int readBytes = in.read(tmpData, 0, availableBytes); + resp = resp.concat(new String(tmpData, 0, readBytes, StandardCharsets.US_ASCII)); + + if (resp.contains(":")) { + return resp; + } + } else { + try { + Thread.sleep(100); + } catch (InterruptedException e) { + throw new EpsonProjectorException(e); + } + } + + elapsedTime = System.currentTimeMillis() - startTime; + } + } + + return resp; + } +} diff --git a/bundles/org.openhab.binding.epsonprojector/src/main/java/org/openhab/binding/epsonprojector/internal/connector/EpsonProjectorTcpConnector.java b/bundles/org.openhab.binding.epsonprojector/src/main/java/org/openhab/binding/epsonprojector/internal/connector/EpsonProjectorTcpConnector.java new file mode 100644 index 0000000000000..aea7eefc4951b --- /dev/null +++ b/bundles/org.openhab.binding.epsonprojector/src/main/java/org/openhab/binding/epsonprojector/internal/connector/EpsonProjectorTcpConnector.java @@ -0,0 +1,183 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.epsonprojector.internal.connector; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.Socket; +import java.nio.charset.StandardCharsets; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.epsonprojector.internal.EpsonProjectorException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Connector for TCP communication. + * + * @author Pauli Anttila - Initial contribution + * @author Michael Lobstein - Improvements for OH3 + */ +@NonNullByDefault +public class EpsonProjectorTcpConnector implements EpsonProjectorConnector { + + private final Logger logger = LoggerFactory.getLogger(EpsonProjectorTcpConnector.class); + private final String ip; + private final int port; + + private @Nullable Socket socket = null; + private @Nullable InputStream in = null; + private @Nullable OutputStream out = null; + + public EpsonProjectorTcpConnector(String ip, int port) { + this.ip = ip; + this.port = port; + } + + @Override + public void connect() throws EpsonProjectorException { + logger.debug("Open connection to address'{}:{}'", ip, port); + + try { + Socket socket = new Socket(ip, port); + this.socket = socket; + in = socket.getInputStream(); + out = socket.getOutputStream(); + } catch (IOException e) { + throw new EpsonProjectorException(e); + } + } + + @Override + public void disconnect() throws EpsonProjectorException { + OutputStream out = this.out; + + if (out != null) { + logger.debug("Close tcp out stream"); + try { + out.close(); + } catch (IOException e) { + logger.debug("Error occurred when closing tcp out stream: {}", e.getMessage()); + } + } + + InputStream in = this.in; + if (in != null) { + logger.debug("Close tcp in stream"); + try { + in.close(); + } catch (IOException e) { + logger.debug("Error occurred when closing tcp in stream: {}", e.getMessage()); + } + } + + Socket socket = this.socket; + if (socket != null) { + logger.debug("Closing socket"); + try { + socket.close(); + } catch (IOException e) { + logger.debug("Error occurred when closing tcp socket: {}", e.getMessage()); + } + } + + this.socket = null; + this.out = null; + this.in = null; + + logger.debug("Closed"); + } + + @Override + public String sendMessage(String data, int timeout) throws EpsonProjectorException { + InputStream in = this.in; + OutputStream out = this.out; + + if (in == null || out == null) { + connect(); + in = this.in; + out = this.out; + } + + try { + if (in != null) { + // flush input stream + if (in.markSupported()) { + in.reset(); + } else { + while (in.available() > 0) { + int availableBytes = in.available(); + + if (availableBytes > 0) { + byte[] tmpData = new byte[availableBytes]; + in.read(tmpData, 0, availableBytes); + } + } + } + return sendMmsg(data, timeout); + } else { + return ""; + } + } catch (IOException e) { + logger.debug("IO error occurred...reconnect and resend once: {}", e.getMessage()); + disconnect(); + connect(); + + try { + return sendMmsg(data, timeout); + } catch (IOException e1) { + throw new EpsonProjectorException(e); + } + } + } + + private String sendMmsg(String data, int timeout) throws IOException, EpsonProjectorException { + String resp = ""; + + InputStream in = this.in; + OutputStream out = this.out; + + if (in != null && out != null) { + out.write(data.getBytes(StandardCharsets.US_ASCII)); + out.write("\r\n".getBytes(StandardCharsets.US_ASCII)); + out.flush(); + + long startTime = System.currentTimeMillis(); + long elapsedTime = 0; + + while (elapsedTime < timeout) { + int availableBytes = in.available(); + if (availableBytes > 0) { + byte[] tmpData = new byte[availableBytes]; + int readBytes = in.read(tmpData, 0, availableBytes); + resp = resp.concat(new String(tmpData, 0, readBytes, StandardCharsets.US_ASCII)); + + if (resp.contains(":")) { + return resp; + } + } else { + try { + Thread.sleep(100); + } catch (InterruptedException e) { + throw new EpsonProjectorException(e); + } + } + + elapsedTime = System.currentTimeMillis() - startTime; + } + } + return resp; + } +} diff --git a/bundles/org.openhab.binding.epsonprojector/src/main/java/org/openhab/binding/epsonprojector/internal/enums/AspectRatio.java b/bundles/org.openhab.binding.epsonprojector/src/main/java/org/openhab/binding/epsonprojector/internal/enums/AspectRatio.java new file mode 100644 index 0000000000000..30359319c7bfa --- /dev/null +++ b/bundles/org.openhab.binding.epsonprojector/src/main/java/org/openhab/binding/epsonprojector/internal/enums/AspectRatio.java @@ -0,0 +1,61 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.epsonprojector.internal.enums; + +import java.util.Arrays; +import java.util.NoSuchElementException; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * Valid values for AspectRatio. + * + * @author Pauli Anttila - Initial contribution + * @author Yannick Schaus - Refactoring + * @author Michael Lobstein - Improvements for OH3 + */ +@NonNullByDefault +public enum AspectRatio { + NORMAL(0x00), + RATIO4X3(0x10), + ZOOM4X3(0x12), + RATIO16X9(0x20), + UP16X9(0x21), + DOWN16X9(0x22), + AUTO(0x30), + FULL(0x40), + ZOOM(0x50), + REAL(0x60), + WIDE(0x70), + ANAMORPHIC(0x80), + SQUEEZE(0x90), + UNKNOWN(0xFF); + + private final int value; + + AspectRatio(int value) { + this.value = value; + } + + public static AspectRatio forValue(int value) { + try { + return Arrays.stream(values()).filter(e -> e.value == value).findFirst().get(); + } catch (NoSuchElementException e) { + return UNKNOWN; + } + } + + public int toInt() { + return value; + } +} diff --git a/bundles/org.openhab.binding.epsonprojector/src/main/java/org/openhab/binding/epsonprojector/internal/enums/Background.java b/bundles/org.openhab.binding.epsonprojector/src/main/java/org/openhab/binding/epsonprojector/internal/enums/Background.java new file mode 100644 index 0000000000000..c38654a353f55 --- /dev/null +++ b/bundles/org.openhab.binding.epsonprojector/src/main/java/org/openhab/binding/epsonprojector/internal/enums/Background.java @@ -0,0 +1,51 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.epsonprojector.internal.enums; + +import java.util.Arrays; +import java.util.NoSuchElementException; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * Valid values for Background. + * + * @author Pauli Anttila - Initial contribution + * @author Yannick Schaus - Refactoring + * @author Michael Lobstein - Improvements for OH3 + */ +@NonNullByDefault +public enum Background { + BLACK(0x00), + BLUE(0x01), + LOGO(0x02), + UNKNOWN(0xFF); + + private final int value; + + Background(int value) { + this.value = value; + } + + public static Background forValue(int value) { + try { + return Arrays.stream(values()).filter(e -> e.value == value).findFirst().get(); + } catch (NoSuchElementException e) { + return UNKNOWN; + } + } + + public int toInt() { + return value; + } +} diff --git a/bundles/org.openhab.binding.epsonprojector/src/main/java/org/openhab/binding/epsonprojector/internal/enums/ColorMode.java b/bundles/org.openhab.binding.epsonprojector/src/main/java/org/openhab/binding/epsonprojector/internal/enums/ColorMode.java new file mode 100644 index 0000000000000..96bbe66a3e168 --- /dev/null +++ b/bundles/org.openhab.binding.epsonprojector/src/main/java/org/openhab/binding/epsonprojector/internal/enums/ColorMode.java @@ -0,0 +1,74 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.epsonprojector.internal.enums; + +import java.util.Arrays; +import java.util.NoSuchElementException; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * Valid values for ColorMode. + * + * @author Pauli Anttila - Initial contribution + * @author Yannick Schaus - Refactoring + * @author Michael Lobstein - Improvements for OH3 + */ +@NonNullByDefault +public enum ColorMode { + SRGB(0x01), + NORMAL(0x02), + MEETING(0x03), + PRESENTATION(0x04), + CINEMANIGHT(0x05), + DYNAMIC(0x06), + NATURAL(0x07), + SPORTS(0x08), + HD(0x09), + CUSTOM(0x10), + BLACKBOARD(0x11), + WHITEBOARD(0x12), + THX(0x13), + PHOTO(0x14), + CINEMA(0x15), + UNKNOWN16(0x16), + CINEMA3D(0x17), + DYNAMIC3D(0x18), + THX3D(0x19), + BWCINEMA(0x20), + UNKNOWN21(0x21), + DIGITALCINEMA(0x22), + SILVER(0x0A), + XVCOLOR(0x0B), + LIVINGROOM(0x0C), + DICOMSIM(0x0F), + UNKNOWN(0xFF); + + private final int value; + + ColorMode(int value) { + this.value = value; + } + + public static ColorMode forValue(int value) { + try { + return Arrays.stream(values()).filter(e -> e.value == value).findFirst().get(); + } catch (NoSuchElementException e) { + return UNKNOWN; + } + } + + public int toInt() { + return value; + } +} diff --git a/bundles/org.openhab.binding.epsonprojector/src/main/java/org/openhab/binding/epsonprojector/internal/enums/ErrorMessage.java b/bundles/org.openhab.binding.epsonprojector/src/main/java/org/openhab/binding/epsonprojector/internal/enums/ErrorMessage.java new file mode 100644 index 0000000000000..04d78960a6455 --- /dev/null +++ b/bundles/org.openhab.binding.epsonprojector/src/main/java/org/openhab/binding/epsonprojector/internal/enums/ErrorMessage.java @@ -0,0 +1,70 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.epsonprojector.internal.enums; + +import java.util.Arrays; +import java.util.NoSuchElementException; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * Messages for documented error codes. + * + * @author Pauli Anttila - Initial contribution + * @author Yannick Schaus - Refactoring + * @author Michael Lobstein - Improvements for OH3 + */ +@NonNullByDefault +public enum ErrorMessage { + NO_ERROR(0, "No error"), + ERROR1(1, "Fan error"), + ERROR3(3, "Lamp failure at power on"), + ERROR4(4, "High internal temperature error"), + ERROR6(6, "Lamp error"), + ERROR7(7, "Open Lamp cover door error"), + ERROR8(8, "Cinema filter error"), + ERROR9(9, "Electric dual-layered capacitor is disconnected"), + ERROR10(10, "Auto iris error"), + ERROR11(11, "Subsystem error"), + ERROR12(12, "Low air flow error"), + ERROR13(13, "Air filter air flow sensor error"), + ERROR14(14, "Power supply unit error (ballast)"), + ERROR15(15, "Shutter error"), + ERROR16(16, "Cooling system error (peltier element)"), + ERROR17(17, "Cooling system error (pump)"), + ERROR18(18, "Static iris error"), + ERROR19(19, "Power supply unit error (disagreement of ballast)"), + ERROR20(20, "Exhaust shutter error"), + ERROR21(21, "Obstacle detection error"), + ERROR22(22, "IF board discernment error"); + + private final int code; + private final String message; + + ErrorMessage(int code, String message) { + this.code = code; + this.message = message; + } + + public String getMessage() { + return message; + } + + public static String forCode(int code) { + try { + return Arrays.stream(values()).filter(e -> e.code == code).findFirst().get().getMessage(); + } catch (NoSuchElementException e) { + return "Unknown error code: " + code; + } + } +} diff --git a/bundles/org.openhab.binding.epsonprojector/src/main/java/org/openhab/binding/epsonprojector/internal/enums/Gamma.java b/bundles/org.openhab.binding.epsonprojector/src/main/java/org/openhab/binding/epsonprojector/internal/enums/Gamma.java new file mode 100644 index 0000000000000..cef7c0dbfbced --- /dev/null +++ b/bundles/org.openhab.binding.epsonprojector/src/main/java/org/openhab/binding/epsonprojector/internal/enums/Gamma.java @@ -0,0 +1,54 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.epsonprojector.internal.enums; + +import java.util.Arrays; +import java.util.NoSuchElementException; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * Valid values for Gamma. + * + * @author Pauli Anttila - Initial contribution + * @author Yannick Schaus - Refactoring + * @author Michael Lobstein - Improvements for OH3 + */ +@NonNullByDefault +public enum Gamma { + G2_0(0x20), + G2_1(0x21), + G2_2(0x22), + G2_3(0x23), + G2_4(0x24), + CUSTOM(0xF0), + UNKNOWN(0xFF); + + private final int value; + + Gamma(int value) { + this.value = value; + } + + public static Gamma forValue(int value) { + try { + return Arrays.stream(values()).filter(e -> e.value == value).findFirst().get(); + } catch (NoSuchElementException e) { + return UNKNOWN; + } + } + + public int toInt() { + return value; + } +} diff --git a/bundles/org.openhab.binding.epsonprojector/src/main/java/org/openhab/binding/epsonprojector/internal/enums/Luminance.java b/bundles/org.openhab.binding.epsonprojector/src/main/java/org/openhab/binding/epsonprojector/internal/enums/Luminance.java new file mode 100644 index 0000000000000..9419f2858c5c9 --- /dev/null +++ b/bundles/org.openhab.binding.epsonprojector/src/main/java/org/openhab/binding/epsonprojector/internal/enums/Luminance.java @@ -0,0 +1,51 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.epsonprojector.internal.enums; + +import java.util.Arrays; +import java.util.NoSuchElementException; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * Valid values for Luminance. + * + * @author Pauli Anttila - Initial contribution + * @author Yannick Schaus - Refactoring + * @author Michael Lobstein - Improvements for OH3 + */ +@NonNullByDefault +public enum Luminance { + NORMAL(0x00), + ECO(0x01), + MEDIUM(0x02), + UNKNOWN(0xFF); + + private final int value; + + Luminance(int value) { + this.value = value; + } + + public static Luminance forValue(int value) { + try { + return Arrays.stream(values()).filter(e -> e.value == value).findFirst().get(); + } catch (NoSuchElementException e) { + return UNKNOWN; + } + } + + public int toInt() { + return value; + } +} diff --git a/bundles/org.openhab.binding.epsonprojector/src/main/java/org/openhab/binding/epsonprojector/internal/enums/PowerStatus.java b/bundles/org.openhab.binding.epsonprojector/src/main/java/org/openhab/binding/epsonprojector/internal/enums/PowerStatus.java new file mode 100644 index 0000000000000..6e77451bb6a3f --- /dev/null +++ b/bundles/org.openhab.binding.epsonprojector/src/main/java/org/openhab/binding/epsonprojector/internal/enums/PowerStatus.java @@ -0,0 +1,54 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.epsonprojector.internal.enums; + +import java.util.Arrays; +import java.util.NoSuchElementException; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * Valid values for PowerStatus. + * + * @author Pauli Anttila - Initial contribution + * @author Yannick Schaus - Refactoring + * @author Michael Lobstein - Improvements for OH3 + */ +@NonNullByDefault +public enum PowerStatus { + STANDBY(0x00), + ON(0x01), + WARMUP(0x02), + COOLDOWN(0x03), + STANDBYNETWORKON(0x04), + ABNORMALSTANDBY(0x05), + UNKNOWN(0xFF); + + private final int value; + + PowerStatus(int value) { + this.value = value; + } + + public static PowerStatus forValue(int value) { + try { + return Arrays.stream(values()).filter(e -> e.value == value).findFirst().get(); + } catch (NoSuchElementException e) { + return UNKNOWN; + } + } + + public int toInt() { + return value; + } +} diff --git a/bundles/org.openhab.binding.epsonprojector/src/main/java/org/openhab/binding/epsonprojector/internal/enums/Switch.java b/bundles/org.openhab.binding.epsonprojector/src/main/java/org/openhab/binding/epsonprojector/internal/enums/Switch.java new file mode 100644 index 0000000000000..4bc16f620663c --- /dev/null +++ b/bundles/org.openhab.binding.epsonprojector/src/main/java/org/openhab/binding/epsonprojector/internal/enums/Switch.java @@ -0,0 +1,27 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.epsonprojector.internal.enums; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * Valid values for Epson switch commands. + * + * @author Pauli Anttila - Initial contribution + * @author Yannick Schaus - Refactoring + */ +@NonNullByDefault +public enum Switch { + ON, + OFF; +} diff --git a/bundles/org.openhab.binding.epsonprojector/src/main/java/org/openhab/binding/epsonprojector/internal/handler/EpsonProjectorHandler.java b/bundles/org.openhab.binding.epsonprojector/src/main/java/org/openhab/binding/epsonprojector/internal/handler/EpsonProjectorHandler.java new file mode 100644 index 0000000000000..f1c91b7646cc2 --- /dev/null +++ b/bundles/org.openhab.binding.epsonprojector/src/main/java/org/openhab/binding/epsonprojector/internal/handler/EpsonProjectorHandler.java @@ -0,0 +1,417 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.epsonprojector.internal.handler; + +import static org.openhab.binding.epsonprojector.internal.EpsonProjectorBindingConstants.*; + +import java.util.List; +import java.util.Optional; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.epsonprojector.internal.EpsonProjectorCommandException; +import org.openhab.binding.epsonprojector.internal.EpsonProjectorCommandType; +import org.openhab.binding.epsonprojector.internal.EpsonProjectorDevice; +import org.openhab.binding.epsonprojector.internal.EpsonProjectorException; +import org.openhab.binding.epsonprojector.internal.configuration.EpsonProjectorConfiguration; +import org.openhab.binding.epsonprojector.internal.enums.AspectRatio; +import org.openhab.binding.epsonprojector.internal.enums.Background; +import org.openhab.binding.epsonprojector.internal.enums.ColorMode; +import org.openhab.binding.epsonprojector.internal.enums.Gamma; +import org.openhab.binding.epsonprojector.internal.enums.Luminance; +import org.openhab.binding.epsonprojector.internal.enums.PowerStatus; +import org.openhab.binding.epsonprojector.internal.enums.Switch; +import org.openhab.core.io.transport.serial.SerialPortManager; +import org.openhab.core.library.types.DecimalType; +import org.openhab.core.library.types.OnOffType; +import org.openhab.core.library.types.StringType; +import org.openhab.core.thing.Channel; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.Thing; +import org.openhab.core.thing.ThingStatus; +import org.openhab.core.thing.ThingStatusDetail; +import org.openhab.core.thing.ThingTypeUID; +import org.openhab.core.thing.binding.BaseThingHandler; +import org.openhab.core.types.Command; +import org.openhab.core.types.RefreshType; +import org.openhab.core.types.State; +import org.openhab.core.types.UnDefType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link EpsonProjectorHandler} is responsible for handling commands, which are + * sent to one of the channels. + * + * @author Pauli Anttila, Yannick Schaus - Initial contribution + * @author Michael Lobstein - Improvements for OH3 + */ +@NonNullByDefault +public class EpsonProjectorHandler extends BaseThingHandler { + private static final int DEFAULT_POLLING_INTERVAL_SEC = 10; + + private final Logger logger = LoggerFactory.getLogger(EpsonProjectorHandler.class); + private final SerialPortManager serialPortManager; + + private @Nullable ScheduledFuture pollingJob; + private Optional device = Optional.empty(); + + private boolean isPowerOn = false; + private int pollingInterval = DEFAULT_POLLING_INTERVAL_SEC; + + public EpsonProjectorHandler(Thing thing, SerialPortManager serialPortManager) { + super(thing); + this.serialPortManager = serialPortManager; + } + + @Override + public void handleCommand(ChannelUID channelUID, Command command) { + String channelId = channelUID.getId(); + if (command instanceof RefreshType) { + Channel channel = this.thing.getChannel(channelUID); + if (channel != null) { + updateChannelState(channel); + } + } else { + EpsonProjectorCommandType epsonCommand = EpsonProjectorCommandType.getCommandType(channelId); + sendDataToDevice(epsonCommand, command); + } + } + + @Override + public void initialize() { + EpsonProjectorConfiguration config = getConfigAs(EpsonProjectorConfiguration.class); + ThingTypeUID thingTypeUID = thing.getThingTypeUID(); + + if (THING_TYPE_PROJECTOR_SERIAL.equals(thingTypeUID)) { + device = Optional.of(new EpsonProjectorDevice(serialPortManager, config)); + } else if (THING_TYPE_PROJECTOR_TCP.equals(thingTypeUID)) { + device = Optional.of(new EpsonProjectorDevice(config)); + } else { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR); + } + + pollingInterval = config.pollingInterval; + device.ifPresent(dev -> dev.setScheduler(scheduler)); + updateStatus(ThingStatus.UNKNOWN); + schedulePollingJob(); + } + + /** + * Schedule the polling job + */ + private void schedulePollingJob() { + cancelPollingJob(); + + pollingJob = scheduler.scheduleWithFixedDelay(() -> { + List channels = this.thing.getChannels(); + for (Channel channel : channels) { + // only query power & lamp time when projector is off + if (isPowerOn || (channel.getUID().getId().equals(CHANNEL_TYPE_POWER) + || channel.getUID().getId().equals(CHANNEL_TYPE_LAMPTIME))) { + updateChannelState(channel); + } + } + }, 0, (pollingInterval > 0) ? pollingInterval : DEFAULT_POLLING_INTERVAL_SEC, TimeUnit.SECONDS); + } + + /** + * Cancel the polling job + */ + private void cancelPollingJob() { + ScheduledFuture pollingJob = this.pollingJob; + if (pollingJob != null) { + pollingJob.cancel(true); + this.pollingJob = null; + } + } + + @Override + public void dispose() { + cancelPollingJob(); + closeConnection(); + super.dispose(); + } + + private void updateChannelState(Channel channel) { + try { + if (!isLinked(channel.getUID()) && !channel.getUID().getId().equals(CHANNEL_TYPE_POWER)) { + return; + } + + EpsonProjectorCommandType epsonCommand = EpsonProjectorCommandType.getCommandType(channel.getUID().getId()); + + State state = queryDataFromDevice(epsonCommand); + + if (state != null) { + updateStatus(ThingStatus.ONLINE); + if (isLinked(channel.getUID())) { + updateState(channel.getUID(), state); + } + } + } catch (IllegalArgumentException e) { + logger.warn("Unknown channel {}", channel.getUID().getId()); + } + } + + @Nullable + private State queryDataFromDevice(EpsonProjectorCommandType commandType) { + EpsonProjectorDevice remoteController = device.get(); + + try { + if (!remoteController.isConnected()) { + remoteController.connect(); + } + + if (!remoteController.isReady()) { + logger.debug("Refusing command {} while not ready", commandType.toString()); + return null; + } + + switch (commandType) { + case AKEYSTONE: + Switch autoKeystone = remoteController.getAutoKeystone(); + return autoKeystone == Switch.ON ? OnOffType.ON : OnOffType.OFF; + case ASPECT_RATIO: + AspectRatio aspectRatio = remoteController.getAspectRatio(); + return new StringType(aspectRatio.toString()); + case BACKGROUND: + Background background = remoteController.getBackground(); + return new StringType(background.toString()); + case BRIGHTNESS: + int brightness = remoteController.getBrightness(); + return new DecimalType(brightness); + case COLOR_MODE: + ColorMode colorMode = remoteController.getColorMode(); + return new StringType(colorMode.toString()); + case COLOR_TEMP: + int ctemp = remoteController.getColorTemperature(); + return new DecimalType(ctemp); + case CONTRAST: + int contrast = remoteController.getContrast(); + return new DecimalType(contrast); + case DENSITY: + int density = remoteController.getDensity(); + return new DecimalType(density); + case ERR_CODE: + int err = remoteController.getError(); + return new DecimalType(err); + case ERR_MESSAGE: + String errString = remoteController.getErrorString(); + return new StringType(errString); + case FLESH_TEMP: + int fleshColor = remoteController.getFleshColor(); + return new DecimalType(fleshColor); + case FREEZE: + Switch freeze = remoteController.getFreeze(); + return freeze == Switch.ON ? OnOffType.ON : OnOffType.OFF; + case GAMMA: + Gamma gamma = remoteController.getGamma(); + return new StringType(gamma.toString()); + case HKEYSTONE: + int hKeystone = remoteController.getHorizontalKeystone(); + return new DecimalType(hKeystone); + case HPOSITION: + int hPosition = remoteController.getHorizontalPosition(); + return new DecimalType(hPosition); + case HREVERSE: + Switch hReverse = remoteController.getHorizontalReverse(); + return hReverse == Switch.ON ? OnOffType.ON : OnOffType.OFF; + case KEY_CODE: + break; + case LAMP_TIME: + int lampTime = remoteController.getLampTime(); + return new DecimalType(lampTime); + case LUMINANCE: + Luminance luminance = remoteController.getLuminance(); + return new StringType(luminance.toString()); + case MUTE: + Switch mute = remoteController.getMute(); + return mute == Switch.ON ? OnOffType.ON : OnOffType.OFF; + case POWER: + PowerStatus powerStatus = remoteController.getPowerStatus(); + if (isLinked(CHANNEL_TYPE_POWERSTATE)) { + updateState(CHANNEL_TYPE_POWERSTATE, new StringType(powerStatus.toString())); + } + + if (powerStatus == PowerStatus.ON || powerStatus == PowerStatus.WARMUP) { + isPowerOn = true; + return OnOffType.ON; + } else { + isPowerOn = false; + return OnOffType.OFF; + } + case POWER_STATE: + return null; + case SOURCE: + return new StringType(remoteController.getSource()); + case TINT: + int tint = remoteController.getTint(); + return new DecimalType(tint); + case VKEYSTONE: + int vKeystone = remoteController.getVerticalKeystone(); + return new DecimalType(vKeystone); + case VOLUME: + int volume = remoteController.getVolume(); + return new DecimalType(volume); + case VPOSITION: + int vPosition = remoteController.getVerticalPosition(); + return new DecimalType(vPosition); + case VREVERSE: + Switch vReverse = remoteController.getVerticalReverse(); + return vReverse == Switch.ON ? OnOffType.ON : OnOffType.OFF; + default: + logger.warn("Unknown '{}' command!", commandType); + return UnDefType.UNDEF; + } + } catch (EpsonProjectorCommandException e) { + logger.debug("Error executing command '{}', {}", commandType, e.getMessage()); + return UnDefType.UNDEF; + } catch (EpsonProjectorException e) { + logger.debug("Couldn't execute command '{}', {}", commandType, e.getMessage()); + closeConnection(); + return null; + } + + return UnDefType.UNDEF; + } + + private void sendDataToDevice(EpsonProjectorCommandType commandType, Command command) { + EpsonProjectorDevice remoteController = device.get(); + + try { + if (!remoteController.isConnected()) { + remoteController.connect(); + } + + if (!remoteController.isReady()) { + logger.debug("Refusing command '{}' while not ready", commandType.toString()); + return; + } + + switch (commandType) { + case AKEYSTONE: + remoteController.setAutoKeystone((command == OnOffType.ON ? Switch.ON : Switch.OFF)); + break; + case ASPECT_RATIO: + remoteController.setAspectRatio(AspectRatio.valueOf(command.toString())); + break; + case BACKGROUND: + remoteController.setBackground(Background.valueOf(command.toString())); + break; + case BRIGHTNESS: + remoteController.setBrightness(((DecimalType) command).intValue()); + break; + case COLOR_MODE: + remoteController.setColorMode(ColorMode.valueOf(command.toString())); + break; + case COLOR_TEMP: + remoteController.setColorTemperature(((DecimalType) command).intValue()); + break; + case CONTRAST: + remoteController.setContrast(((DecimalType) command).intValue()); + break; + case DENSITY: + remoteController.setDensity(((DecimalType) command).intValue()); + break; + case ERR_CODE: + logger.warn("'{}' is read only parameter", commandType); + break; + case ERR_MESSAGE: + logger.warn("'{}' is read only parameter", commandType); + break; + case FLESH_TEMP: + remoteController.setFleshColor(((DecimalType) command).intValue()); + break; + case FREEZE: + remoteController.setFreeze(command == OnOffType.ON ? Switch.ON : Switch.OFF); + break; + case GAMMA: + remoteController.setGamma(Gamma.valueOf(command.toString())); + break; + case HKEYSTONE: + remoteController.setHorizontalKeystone(((DecimalType) command).intValue()); + break; + case HPOSITION: + remoteController.setHorizontalPosition(((DecimalType) command).intValue()); + break; + case HREVERSE: + remoteController.setHorizontalReverse((command == OnOffType.ON ? Switch.ON : Switch.OFF)); + break; + case KEY_CODE: + remoteController.sendKeyCode(((DecimalType) command).intValue()); + break; + case LAMP_TIME: + logger.warn("'{}' is read only parameter", commandType); + break; + case LUMINANCE: + remoteController.setLuminance(Luminance.valueOf(command.toString())); + break; + case MUTE: + remoteController.setMute((command == OnOffType.ON ? Switch.ON : Switch.OFF)); + break; + case POWER: + if (command == OnOffType.ON) { + remoteController.setPower(Switch.ON); + isPowerOn = true; + } else { + remoteController.setPower(Switch.OFF); + isPowerOn = false; + } + break; + case POWER_STATE: + logger.warn("'{}' is read only parameter", commandType); + break; + case SOURCE: + remoteController.setSource(command.toString()); + break; + case TINT: + remoteController.setTint(((DecimalType) command).intValue()); + break; + case VKEYSTONE: + remoteController.setVerticalKeystone(((DecimalType) command).intValue()); + break; + case VOLUME: + remoteController.setVolume(((DecimalType) command).intValue()); + break; + case VPOSITION: + remoteController.setVerticalPosition(((DecimalType) command).intValue()); + break; + case VREVERSE: + remoteController.setVerticalReverse((command == OnOffType.ON ? Switch.ON : Switch.OFF)); + break; + default: + logger.warn("Unknown '{}' command!", commandType); + break; + } + } catch (EpsonProjectorCommandException e) { + logger.debug("Error executing command '{}', {}", commandType, e.getMessage()); + } catch (EpsonProjectorException e) { + logger.warn("Couldn't execute command '{}', {}", commandType, e.getMessage()); + closeConnection(); + } + } + + private void closeConnection() { + EpsonProjectorDevice remoteController = device.get(); + try { + logger.debug("Closing connection to device '{}'", this.thing.getUID()); + remoteController.disconnect(); + updateStatus(ThingStatus.OFFLINE); + } catch (EpsonProjectorException e) { + logger.debug("Error occurred when closing connection to device '{}'", this.thing.getUID(), e); + } + } +} diff --git a/bundles/org.openhab.binding.epsonprojector/src/main/resources/OH-INF/binding/binding.xml b/bundles/org.openhab.binding.epsonprojector/src/main/resources/OH-INF/binding/binding.xml new file mode 100644 index 0000000000000..604294c098506 --- /dev/null +++ b/bundles/org.openhab.binding.epsonprojector/src/main/resources/OH-INF/binding/binding.xml @@ -0,0 +1,9 @@ + + + + Epson Projector Binding + This binding is compatible with Epson projectors which support the ESC/VP21 protocol + + diff --git a/bundles/org.openhab.binding.epsonprojector/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.epsonprojector/src/main/resources/OH-INF/thing/thing-types.xml new file mode 100644 index 0000000000000..e4302be7b12c1 --- /dev/null +++ b/bundles/org.openhab.binding.epsonprojector/src/main/resources/OH-INF/thing/thing-types.xml @@ -0,0 +1,346 @@ + + + + + + An Epson projector which supports the ESC/VP21 protocol via a serial port connection + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + serial-port + Serial Port to Use for Connecting to the Epson Projector + + + + Configures How Often to Poll the Projector for Updates (5-60; Default 10) + 10 + + + + + + + + An Epson projector which supports the ESC/VP21 protocol via a serial over IP connection + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + network-address + IP address for the serial over IP device + + + + Port for the serial over IP device + + + + Configures How Often to Poll the Projector for Updates (5-60; Default 10) + 10 + + + + + + + Switch + + Powers the Projector On or Off + + + String + + Retrieves the Textual Power State of the Projector + + + + Number + + Retrieves the Lamp Hours + + + + Number + + Send a KEY Operation Command to the Projector + + + Number + + Retrieve or Set the Vertical Keystone + + + + Number + + Retrieve or Set the Horizontal Keystone + + + + Switch + + Turn the Auto Keystone On or Off + + + Switch + + Turn the Freeze Screen Mode On or Off + + + String + + Retrieve or Set the Aspect Ratio + + + + + + + + + + + + + + + + + + + + String + + Retrieve or Set the Lamp Mode + + + + + + + + + + String + + Retrieve or Set the Input Source + + + + + + + + + + + + + Number + + Retrieve or Set the Brightness + + + + Number + + Retrieve or Set the Contrast + + + + Number + + Retrieve or Set the Density + + + + Number + + Retrieve or Set the Tint + + + + Number + + Retrieve or Set the Color Temperature + + + + Number + + Retrieve or Set the Flesh Temperature + + + + String + + Retrieve or Set the Color Mode + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Number + + Retrieve or Set the Horizontal Position + + + + Number + + Retrieve or Set the Vertical Position + + + + String + + Retrieve or Set the Gamma Setting + + + + + + + + + + + + + Number + + Retrieve or Set the Volume + + + + Switch + + Turn the AV Mute On or Off + + + Switch + + Turn the Horizontal Reverse On or Off + + + Switch + + Turn the Vertical Reverse On or Off + + + String + + Select the Background Color/Logo + + + + + + + + + + Number + + Retrieves the Last Error Code + + + + String + + Retrieves the Description of the Last Error + + + + diff --git a/bundles/pom.xml b/bundles/pom.xml index fa3dde1666c27..9b25b075474a6 100644 --- a/bundles/pom.xml +++ b/bundles/pom.xml @@ -95,6 +95,7 @@ org.openhab.binding.enigma2 org.openhab.binding.enocean org.openhab.binding.enturno + org.openhab.binding.epsonprojector org.openhab.binding.etherrain org.openhab.binding.evohome org.openhab.binding.exec