From 6fe2c5f3e597334c91c2b1457601795cff2afb3f Mon Sep 17 00:00:00 2001 From: Michael Lobstein Date: Fri, 26 Feb 2021 20:31:12 -0600 Subject: [PATCH 01/10] BenQ binding - Initial Contribution Signed-off-by: Michael Lobstein --- .../org.openhab.binding.benqprojector/NOTICE | 13 + .../README.md | 108 +++++++ .../org.openhab.binding.benqprojector/pom.xml | 17 ++ .../src/main/feature/feature.xml | 10 + .../BenqProjectorBindingConstants.java | 35 +++ .../BenqProjectorCommandException.java | 30 ++ .../internal/BenqProjectorCommandType.java | 99 ++++++ .../internal/BenqProjectorDevice.java | 184 ++++++++++++ .../internal/BenqProjectorException.java | 34 +++ .../internal/BenqProjectorHandlerFactory.java | 69 +++++ .../BenqProjectorConfiguration.java | 44 +++ .../connector/BenqProjectorConnector.java | 55 ++++ .../BenqProjectorSerialConnector.java | 207 +++++++++++++ .../connector/BenqProjectorTcpConnector.java | 181 +++++++++++ .../benqprojector/internal/enums/Switch.java | 26 ++ .../handler/BenqProjectorHandler.java | 284 ++++++++++++++++++ .../main/resources/OH-INF/binding/binding.xml | 9 + .../resources/OH-INF/thing/thing-types.xml | 185 ++++++++++++ 18 files changed, 1590 insertions(+) create mode 100644 bundles/org.openhab.binding.benqprojector/NOTICE create mode 100644 bundles/org.openhab.binding.benqprojector/README.md create mode 100644 bundles/org.openhab.binding.benqprojector/pom.xml create mode 100644 bundles/org.openhab.binding.benqprojector/src/main/feature/feature.xml create mode 100644 bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/BenqProjectorBindingConstants.java create mode 100644 bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/BenqProjectorCommandException.java create mode 100644 bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/BenqProjectorCommandType.java create mode 100644 bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/BenqProjectorDevice.java create mode 100644 bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/BenqProjectorException.java create mode 100644 bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/BenqProjectorHandlerFactory.java create mode 100644 bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/configuration/BenqProjectorConfiguration.java create mode 100644 bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/connector/BenqProjectorConnector.java create mode 100644 bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/connector/BenqProjectorSerialConnector.java create mode 100644 bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/connector/BenqProjectorTcpConnector.java create mode 100644 bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/enums/Switch.java create mode 100644 bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/handler/BenqProjectorHandler.java create mode 100644 bundles/org.openhab.binding.benqprojector/src/main/resources/OH-INF/binding/binding.xml create mode 100644 bundles/org.openhab.binding.benqprojector/src/main/resources/OH-INF/thing/thing-types.xml diff --git a/bundles/org.openhab.binding.benqprojector/NOTICE b/bundles/org.openhab.binding.benqprojector/NOTICE new file mode 100644 index 0000000000000..38d625e349232 --- /dev/null +++ b/bundles/org.openhab.binding.benqprojector/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.benqprojector/README.md b/bundles/org.openhab.binding.benqprojector/README.md new file mode 100644 index 0000000000000..0738f5745897f --- /dev/null +++ b/bundles/org.openhab.binding.benqprojector/README.md @@ -0,0 +1,108 @@ +# BenQ Projector Binding + +This binding is compatible with BenQ projectors that support the control protocol via the built-in network (ethernet or Wi-Fi?) port, serial port or USB to serial adapter. +If your projector does not have built-in networking, you can connect to your projector's serial port via a TCP connection using a serial over IP device or by using`ser2net`. + +The control protocol can be found here: https://business-display.benq.com/content/dam/bb/en/product/projector/corporate/lx770/quick-start-guide/lx770-rs232-control-guide-0-windows7-windows8-winxp.pdf + +## 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 BenQ 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 projector or serial over IP device +- _port_: Port for the projector or serial over IP device | Default 8000 for BenQ projectors with built in networking +- _pollingInterval_: Polling interval in seconds to update channel states | 5-60 seconds; default 10 seconds + +Some notes: + +* If using a serial port connection, the baud rate in the projector OSD menu must be set to 9600 bps. +* The _source_, _picturemode_ and _aspectratio_ channels include a dropdown with all possible settings as described in the BenQ protocol document. +* Not all pre-defined dropdown options will be usable if your particular projector does support a given option. +* If your projector has an option that is not in the dropdown, the string code to access that option will be displayed by the channel when that option is selected by the remote control. +* By using the sitemap mapping or a rule to send that code back to the channel, any options that are missing in the binding can be accessed. + +* On Linux, you may get an error stating the serial port cannot be opened when the benqprojector 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. benqprojector 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 BenQ 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. | | +| source | String | Retrieve or set the input source. | See above | +| picturemode | String | Retrieve or set the picture mode. | See above | +| aspectratio | String | Retrieve or set the aspect ratio. | See above | +| freeze | Switch | Turn the freeze image mode on or off. | | +| blank | Switch | Turn the screen blank mode on or off. | | +| directcmd | String | Send a command directly to the projector. | Send only | + +## Full Example + +things/benq.things: + +```java +//serial port connection +benqprojector:projector-serial:hometheater "Projector" [ serialPort="COM5", pollingInterval=10 ] + +// serial over IP connection +benqprojector:projector-tcp:hometheater "Projector" [ host="192.168.0.10", port=8000, pollingInterval=10 ] + +``` + +items/benq.items + +``` +Switch benqPower { channel="benqprojector:projector-serial:hometheater:power" } +String benqSource "Source [%s]" { channel="benqprojector:projector-serial:hometheater:source" } +String benqPictureMode "Picture Mode [%s]" { channel="benqprojector:projector-serial:hometheater:picturemode" } +String benqAspectRatio "Aspect Ratio [%s]" { channel="benqprojector:projector-serial:hometheater:aspectratio" } +Switch benqFreeze { channel="benqprojector:projector-serial:hometheater:freeze" } +Switch benqBlank { channel="benqprojector:projector-serial:hometheater:blank" } +String benqDirect { channel="benqprojector:projector-serial:hometheater:directcmd", autoupdate="false" } +``` + +sitemaps/benq.sitemap + +``` +sitemap benq label="BenQ Projector Demo" { + Frame label="Controls" { + Switch item=benqPower label="Power" + Selection item=benqSource label="Source" mappings=["hdmi"="HDMI", "hdmi2"="HDMI2", "ypbr"="Component", "RGB"="Computer", "vid"="Video", "svid"="S-Video"] + Selection item=benqPictureMode label="Picture Mode" + Selection item=benqAspectRatio label="Aspect Ratio" + Switch item=benqFreeze label="Freeze" + Switch item=benqBlank label="Blank Screen" + Selection item=benqDirect label="Direct Command" + } + Frame label="Advanced Controls" { + Switch item=benqDirect label="Image Flip" mappings=["pp=FT"="Front","pp=RE"="Rear","pp=FC"="Front Ceiling","pp=RC"="Rear Ceiling"] + Switch item=benqDirect label="Load Lens Memory" mappings=["lensload=m1"="1","lensload=m2"="2","lensload=m3"="3","lensload=m4"="4"] + Switch item=benqDirect label="Lamp Mode" mappings=["lampm=lnor"="Normal","lampm=eco"="Eco","lampm=seco"="SmartEco"] + Switch item=benqDirect label="Lamp Mode" mappings=["lampm=seco2"="SmartEco2","lampm=seco3"="SmartEco3","lampm=dimming"="Dimming","lampm=custom"="Custom"] + } +} +``` diff --git a/bundles/org.openhab.binding.benqprojector/pom.xml b/bundles/org.openhab.binding.benqprojector/pom.xml new file mode 100644 index 0000000000000..472c773945d5c --- /dev/null +++ b/bundles/org.openhab.binding.benqprojector/pom.xml @@ -0,0 +1,17 @@ + + + + 4.0.0 + + + org.openhab.addons.bundles + org.openhab.addons.reactor.bundles + 3.1.0-SNAPSHOT + + + org.openhab.binding.benqprojector + + openHAB Add-ons :: Bundles :: BenQ Projector Binding + + diff --git a/bundles/org.openhab.binding.benqprojector/src/main/feature/feature.xml b/bundles/org.openhab.binding.benqprojector/src/main/feature/feature.xml new file mode 100644 index 0000000000000..cc4133c31b19b --- /dev/null +++ b/bundles/org.openhab.binding.benqprojector/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.benqprojector/${project.version} + + diff --git a/bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/BenqProjectorBindingConstants.java b/bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/BenqProjectorBindingConstants.java new file mode 100644 index 0000000000000..69b0fe4059e8e --- /dev/null +++ b/bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/BenqProjectorBindingConstants.java @@ -0,0 +1,35 @@ +/** + * Copyright (c) 2010-2021 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.benqprojector.internal; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.core.thing.ThingTypeUID; + +/** + * The {@link BenqProjectorBindingConstants} class defines common constants, which are + * used across the whole binding. + * + * @author Michael Lobstein - Initial contribution + */ +@NonNullByDefault +public class BenqProjectorBindingConstants { + + private static final String BINDING_ID = "benqprojector"; + + // 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"; +} diff --git a/bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/BenqProjectorCommandException.java b/bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/BenqProjectorCommandException.java new file mode 100644 index 0000000000000..8078b1008ae38 --- /dev/null +++ b/bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/BenqProjectorCommandException.java @@ -0,0 +1,30 @@ +/** + * Copyright (c) 2010-2021 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.benqprojector.internal; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * Exception for BenQ projector command errors. + * + * @author Michael Lobstein - Initial contribution + */ +@NonNullByDefault +public class BenqProjectorCommandException extends Exception { + + private static final long serialVersionUID = -8048415193494625295L; + + public BenqProjectorCommandException(String message) { + super(message); + } +} diff --git a/bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/BenqProjectorCommandType.java b/bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/BenqProjectorCommandType.java new file mode 100644 index 0000000000000..a8b5bbf8b0495 --- /dev/null +++ b/bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/BenqProjectorCommandType.java @@ -0,0 +1,99 @@ +/** + * Copyright (c) 2010-2021 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.benqprojector.internal; + +import java.io.InvalidClassException; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.core.items.Item; +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 Michael Lobstein - Initial contribution + */ +@NonNullByDefault +public enum BenqProjectorCommandType { + POWER("Power", SwitchItem.class), + SOURCE("Source", StringItem.class), + PICTURE_MODE("PictureMode", StringItem.class), + ASPECT_RATIO("AspectRatio", StringItem.class), + FREEZE("Freeze", SwitchItem.class), + BLANK("Blank", SwitchItem.class), + DIRECTCMD("DirectCmd", StringItem.class); + + private final String text; + private Class itemClass; + + private BenqProjectorCommandType(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 (BenqProjectorCommandType c : BenqProjectorCommandType.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 BenqProjectorCommandType getCommandType(String commandTypeText) throws IllegalArgumentException { + for (BenqProjectorCommandType c : BenqProjectorCommandType.values()) { + if (c.text.equalsIgnoreCase(commandTypeText)) { + return c; + } + } + + throw new IllegalArgumentException("Not valid command type"); + } +} diff --git a/bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/BenqProjectorDevice.java b/bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/BenqProjectorDevice.java new file mode 100644 index 0000000000000..0c5a98d2b4db6 --- /dev/null +++ b/bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/BenqProjectorDevice.java @@ -0,0 +1,184 @@ +/** + * Copyright (c) 2010-2021 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.benqprojector.internal; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.benqprojector.internal.configuration.BenqProjectorConfiguration; +import org.openhab.binding.benqprojector.internal.connector.BenqProjectorConnector; +import org.openhab.binding.benqprojector.internal.connector.BenqProjectorSerialConnector; +import org.openhab.binding.benqprojector.internal.connector.BenqProjectorTcpConnector; +import org.openhab.binding.benqprojector.internal.enums.Switch; +import org.openhab.core.io.transport.serial.SerialPortManager; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Provide high level interface to BenQ projector. + * + * @author Michael Lobstein - Initial contribution + */ +@NonNullByDefault +public class BenqProjectorDevice { + private static final int DEFAULT_TIMEOUT_MS = 5 * 1000; + + private static final String UNSUPPORTED_ITM = "Unsupported item"; + private static final String ILLEGAL_FMT = "Illegal format"; + + private final Logger logger = LoggerFactory.getLogger(BenqProjectorDevice.class); + + private BenqProjectorConnector connection; + private boolean connected = false; + + public BenqProjectorDevice(SerialPortManager serialPortManager, BenqProjectorConfiguration config) { + connection = new BenqProjectorSerialConnector(serialPortManager, config.serialPort); + } + + public BenqProjectorDevice(BenqProjectorConfiguration config) { + connection = new BenqProjectorTcpConnector(config.host, config.port); + } + + private synchronized String sendQuery(String query, int timeout) + throws BenqProjectorCommandException, BenqProjectorException { + logger.debug("Query: '{}'", query); + String response = connection.sendMessage(query, timeout); + + if (response.length() == 0) { + throw new BenqProjectorException("No response received"); + } + + if (UNSUPPORTED_ITM.equals(response)) { + throw new BenqProjectorCommandException("Unsupported Command response received for command: " + query); + } + + if (ILLEGAL_FMT.equals(response)) { + throw new BenqProjectorCommandException("Illegal Format response received for command: " + query); + } + + logger.debug("Response: '{}'", response); + + String[] responseParts = response.split("="); + if (responseParts.length != 2) { + throw new BenqProjectorCommandException("Invalid respose for command: " + query); + } + + return responseParts[1]; + } + + protected void sendCommand(String command, int timeout) + throws BenqProjectorCommandException, BenqProjectorException { + sendQuery(command, timeout); + } + + protected void sendCommand(String command) throws BenqProjectorCommandException, BenqProjectorException { + sendCommand(command, DEFAULT_TIMEOUT_MS); + } + + /* + * protected int queryInt(String query) throws BenqProjectorCommandException, BenqProjectorException { + * String response = sendQuery(query, DEFAULT_TIMEOUT); + * return Integer.parseInt(response); + * } + */ + + protected String queryString(String query) throws BenqProjectorCommandException, BenqProjectorException { + return sendQuery(query, DEFAULT_TIMEOUT_MS); + } + + public void connect() throws BenqProjectorException { + connection.connect(); + connected = true; + } + + public void disconnect() throws BenqProjectorException { + connection.disconnect(); + connected = false; + } + + public boolean isConnected() { + return connected; + } + + /* + * Power + */ + public Switch getPowerStatus() throws BenqProjectorCommandException, BenqProjectorException { + return (queryString("pow=?").toUpperCase().contains("ON") ? Switch.ON : Switch.OFF); + } + + public void setPower(Switch value) throws BenqProjectorCommandException, BenqProjectorException { + sendCommand(value == Switch.ON ? "pow=on" : "pow=off"); + } + + /* + * Source + */ + public @Nullable String getSource() throws BenqProjectorCommandException, BenqProjectorException { + return queryString("sour=?"); + } + + public void setSource(String value) throws BenqProjectorCommandException, BenqProjectorException { + sendCommand(String.format("sour=%s", value)); + } + + /* + * Picture Mode + */ + public @Nullable String getPictureMode() throws BenqProjectorCommandException, BenqProjectorException { + return queryString("appmod=?"); + } + + public void setPictureMode(String value) throws BenqProjectorCommandException, BenqProjectorException { + sendCommand(String.format("appmod=%s", value)); + } + + /* + * Aspect Ratio + */ + public @Nullable String getAspectRatio() throws BenqProjectorCommandException, BenqProjectorException { + return queryString("asp=?"); + } + + public void setAspectRatio(String value) throws BenqProjectorCommandException, BenqProjectorException { + sendCommand(String.format("asp=%s", value)); + } + + /* + * Blank Screen + */ + public Switch getBlank() throws BenqProjectorCommandException, BenqProjectorException { + return (queryString("blank=?").toUpperCase().contains("ON") ? Switch.ON : Switch.OFF); + } + + public void setBlank(Switch value) throws BenqProjectorCommandException, BenqProjectorException { + sendCommand(String.format("blank=%s", (value == Switch.ON ? "on" : "off")), DEFAULT_TIMEOUT_MS); + } + + /* + * Freeze + */ + public Switch getFreeze() throws BenqProjectorCommandException, BenqProjectorException { + return (queryString("freeze=?").toUpperCase().contains("ON") ? Switch.ON : Switch.OFF); + } + + public void setFreeze(Switch value) throws BenqProjectorCommandException, BenqProjectorException { + sendCommand(String.format("freeze=%s", (value == Switch.ON ? "on" : "off")), DEFAULT_TIMEOUT_MS); + } + + /* + * Direct Command + */ + public void sendDirectCommand(String value) throws BenqProjectorCommandException, BenqProjectorException { + sendCommand(value); + } +} diff --git a/bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/BenqProjectorException.java b/bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/BenqProjectorException.java new file mode 100644 index 0000000000000..3d75404bdd3da --- /dev/null +++ b/bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/BenqProjectorException.java @@ -0,0 +1,34 @@ +/** + * Copyright (c) 2010-2021 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.benqprojector.internal; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * Exception for BenQ projector errors. + * + * @author Michael Lobstein - Initial contribution + */ +@NonNullByDefault +public class BenqProjectorException extends Exception { + + private static final long serialVersionUID = -8048415193494625295L; + + public BenqProjectorException(String message) { + super(message); + } + + public BenqProjectorException(Throwable cause) { + super(cause); + } +} diff --git a/bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/BenqProjectorHandlerFactory.java b/bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/BenqProjectorHandlerFactory.java new file mode 100644 index 0000000000000..160c15fa5e5ec --- /dev/null +++ b/bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/BenqProjectorHandlerFactory.java @@ -0,0 +1,69 @@ +/** + * Copyright (c) 2010-2021 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.benqprojector.internal; + +import static org.openhab.binding.benqprojector.internal.BenqProjectorBindingConstants.*; + +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.benqprojector.internal.handler.BenqProjectorHandler; +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 BenqProjectorHandlerFactory} is responsible for creating things and thing + * handlers. + * + * @author Michael Lobstein - Initial contribution + */ +@NonNullByDefault +@Component(configurationPid = "binding.benqprojector", service = ThingHandlerFactory.class) +public class BenqProjectorHandlerFactory 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 BenqProjectorHandlerFactory(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 BenqProjectorHandler(thing, serialPortManager); + } + + return null; + } +} diff --git a/bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/configuration/BenqProjectorConfiguration.java b/bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/configuration/BenqProjectorConfiguration.java new file mode 100644 index 0000000000000..e028aef916c42 --- /dev/null +++ b/bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/configuration/BenqProjectorConfiguration.java @@ -0,0 +1,44 @@ +/** + * Copyright (c) 2010-2021 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.benqprojector.internal.configuration; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link BenqProjectorConfiguration} class contains fields mapping thing configuration parameters. + * + * @author Michael Lobstein - Initial contribution + */ +@NonNullByDefault +public class BenqProjectorConfiguration { + + /** + * 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.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/connector/BenqProjectorConnector.java b/bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/connector/BenqProjectorConnector.java new file mode 100644 index 0000000000000..2e10c5931ec60 --- /dev/null +++ b/bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/connector/BenqProjectorConnector.java @@ -0,0 +1,55 @@ +/** + * Copyright (c) 2010-2021 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.benqprojector.internal.connector; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.benqprojector.internal.BenqProjectorException; + +/** + * Base class for BenQ projector communication. + * + * @author Michael Lobstein - Initial contribution + */ +@NonNullByDefault +public interface BenqProjectorConnector { + + public static final String START = "\r*"; + public static final String END = "#\r"; + + /** + * Procedure for connecting to projector. + * + * @throws BenqProjectorException + */ + void connect() throws BenqProjectorException; + + /** + * Procedure for disconnecting to projector controller. + * + * @throws BenqProjectorException + */ + void disconnect() throws BenqProjectorException; + + /** + * Procedure for send raw data to projector. + * + * @param data + * Message to send. + * + * @param timeout + * timeout to wait response in milliseconds. + * + * @throws BenqProjectorException + */ + String sendMessage(String data, int timeout) throws BenqProjectorException; +} diff --git a/bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/connector/BenqProjectorSerialConnector.java b/bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/connector/BenqProjectorSerialConnector.java new file mode 100644 index 0000000000000..72ca7caf06736 --- /dev/null +++ b/bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/connector/BenqProjectorSerialConnector.java @@ -0,0 +1,207 @@ +/** + * Copyright (c) 2010-2021 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.benqprojector.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.benqprojector.internal.BenqProjectorException; +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 Michael Lobstein - Initial contribution + */ +@NonNullByDefault +public class BenqProjectorSerialConnector implements BenqProjectorConnector, SerialPortEventListener { + + private final Logger logger = LoggerFactory.getLogger(BenqProjectorSerialConnector.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 BenqProjectorSerialConnector(SerialPortManager serialPortManager, String serialPort) { + this.serialPortManager = serialPortManager; + this.serialPortName = serialPort; + } + + @Override + public void connect() throws BenqProjectorException { + 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 BenqProjectorException(e); + } + } + + @Override + public void disconnect() throws BenqProjectorException { + 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 BenqProjectorException { + 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 BenqProjectorException(e); + } + } + } + + @Override + public void serialEvent(SerialPortEvent arg0) { + } + + private String sendMmsg(String data, int timeout) throws IOException, BenqProjectorException { + String resp = ""; + + InputStream in = this.in; + OutputStream out = this.out; + + if (in != null && out != null) { + data = START + data + END; + out.write(data.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(END)) { + return resp.replace(START, "").replace(END, ""); + } + } else { + try { + Thread.sleep(100); + } catch (InterruptedException e) { + throw new BenqProjectorException(e); + } + } + + elapsedTime = System.currentTimeMillis() - startTime; + } + } + + return resp; + } +} diff --git a/bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/connector/BenqProjectorTcpConnector.java b/bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/connector/BenqProjectorTcpConnector.java new file mode 100644 index 0000000000000..24142fc2cfa46 --- /dev/null +++ b/bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/connector/BenqProjectorTcpConnector.java @@ -0,0 +1,181 @@ +/** + * Copyright (c) 2010-2021 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.benqprojector.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.benqprojector.internal.BenqProjectorException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Connector for TCP communication. + * + * @author Michael Lobstein - Initial contribution + */ +@NonNullByDefault +public class BenqProjectorTcpConnector implements BenqProjectorConnector { + + private final Logger logger = LoggerFactory.getLogger(BenqProjectorTcpConnector.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 BenqProjectorTcpConnector(String ip, int port) { + this.ip = ip; + this.port = port; + } + + @Override + public void connect() throws BenqProjectorException { + 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 BenqProjectorException(e); + } + } + + @Override + public void disconnect() throws BenqProjectorException { + 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 BenqProjectorException { + 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 BenqProjectorException(e); + } + } + } + + private String sendMmsg(String data, int timeout) throws IOException, BenqProjectorException { + String resp = ""; + + InputStream in = this.in; + OutputStream out = this.out; + + if (in != null && out != null) { + data = START + data + END; + out.write(data.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(END)) { + return resp.replace(START, "").replace(END, ""); + } + } else { + try { + Thread.sleep(100); + } catch (InterruptedException e) { + throw new BenqProjectorException(e); + } + } + + elapsedTime = System.currentTimeMillis() - startTime; + } + } + return resp; + } +} diff --git a/bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/enums/Switch.java b/bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/enums/Switch.java new file mode 100644 index 0000000000000..04cab831f613b --- /dev/null +++ b/bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/enums/Switch.java @@ -0,0 +1,26 @@ +/** + * Copyright (c) 2010-2021 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.benqprojector.internal.enums; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * Valid values for BenQ switch commands. + * + * @author Michael Lobstein - Initial contribution + */ +@NonNullByDefault +public enum Switch { + ON, + OFF; +} diff --git a/bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/handler/BenqProjectorHandler.java b/bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/handler/BenqProjectorHandler.java new file mode 100644 index 0000000000000..bcf0dccef8457 --- /dev/null +++ b/bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/handler/BenqProjectorHandler.java @@ -0,0 +1,284 @@ +/** + * Copyright (c) 2010-2021 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.benqprojector.internal.handler; + +import static org.openhab.binding.benqprojector.internal.BenqProjectorBindingConstants.*; + +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.benqprojector.internal.BenqProjectorCommandException; +import org.openhab.binding.benqprojector.internal.BenqProjectorCommandType; +import org.openhab.binding.benqprojector.internal.BenqProjectorDevice; +import org.openhab.binding.benqprojector.internal.BenqProjectorException; +import org.openhab.binding.benqprojector.internal.configuration.BenqProjectorConfiguration; +import org.openhab.binding.benqprojector.internal.enums.Switch; +import org.openhab.core.io.transport.serial.SerialPortManager; +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 BenqProjectorHandler} is responsible for handling commands, which are + * sent to one of the channels. + * + * @author Michael Lobstein - Initial contribution + */ +@NonNullByDefault +public class BenqProjectorHandler extends BaseThingHandler { + private static final int DEFAULT_POLLING_INTERVAL_SEC = 10; + + private final Logger logger = LoggerFactory.getLogger(BenqProjectorHandler.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 BenqProjectorHandler(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 && getThing().getStatus() == ThingStatus.ONLINE) { + updateChannelState(channel); + } + } else { + BenqProjectorCommandType benqCommand = BenqProjectorCommandType.getCommandType(channelId); + sendDataToDevice(benqCommand, command); + } + } + + @Override + public void initialize() { + BenqProjectorConfiguration config = getConfigAs(BenqProjectorConfiguration.class); + ThingTypeUID thingTypeUID = thing.getThingTypeUID(); + + if (THING_TYPE_PROJECTOR_SERIAL.equals(thingTypeUID)) { + device = Optional.of(new BenqProjectorDevice(serialPortManager, config)); + } else if (THING_TYPE_PROJECTOR_TCP.equals(thingTypeUID)) { + device = Optional.of(new BenqProjectorDevice(config)); + } else { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR); + return; + } + + pollingInterval = config.pollingInterval; + 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 when projector is off + if (isPowerOn || channel.getUID().getId().equals(CHANNEL_TYPE_POWER)) { + 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; + } + + BenqProjectorCommandType benqCommand = BenqProjectorCommandType.getCommandType(channel.getUID().getId()); + + State state = queryDataFromDevice(benqCommand); + + if (state != null) { + if (isLinked(channel.getUID())) { + updateState(channel.getUID(), state); + } + if (state != UnDefType.UNDEF && getThing().getStatus() != ThingStatus.ONLINE) { + updateStatus(ThingStatus.ONLINE); + } + } + } catch (IllegalArgumentException e) { + logger.warn("Unknown channel {}", channel.getUID().getId()); + } + } + + @Nullable + private State queryDataFromDevice(BenqProjectorCommandType commandType) { + BenqProjectorDevice remoteController = device.get(); + + try { + if (!remoteController.isConnected()) { + remoteController.connect(); + } + + switch (commandType) { + case POWER: + Switch powerStatus = remoteController.getPowerStatus(); + if (powerStatus == Switch.ON) { + isPowerOn = true; + return OnOffType.ON; + } else { + isPowerOn = false; + return OnOffType.OFF; + } + case SOURCE: + String source = remoteController.getSource(); + if (source != null) { + return new StringType(source); + } else { + return UnDefType.UNDEF; + } + case PICTURE_MODE: + String picturemode = remoteController.getPictureMode(); + if (picturemode != null) { + return new StringType(picturemode); + } else { + return UnDefType.UNDEF; + } + case ASPECT_RATIO: + String aspectratio = remoteController.getPictureMode(); + if (aspectratio != null) { + return new StringType(aspectratio); + } else { + return UnDefType.UNDEF; + } + case FREEZE: + Switch freeze = remoteController.getFreeze(); + return freeze == Switch.ON ? OnOffType.ON : OnOffType.OFF; + case BLANK: + Switch blank = remoteController.getBlank(); + return blank == Switch.ON ? OnOffType.ON : OnOffType.OFF; + case DIRECTCMD: + break; + default: + logger.warn("Unknown '{}' command!", commandType); + return UnDefType.UNDEF; + } + } catch (BenqProjectorCommandException e) { + logger.debug("Error executing command '{}', {}", commandType, e.getMessage()); + return UnDefType.UNDEF; + } catch (BenqProjectorException e) { + logger.debug("Couldn't execute command '{}', {}", commandType, e.getMessage()); + closeConnection(); + return null; + } + + return UnDefType.UNDEF; + } + + private void sendDataToDevice(BenqProjectorCommandType commandType, Command command) { + BenqProjectorDevice remoteController = device.get(); + + try { + if (!remoteController.isConnected()) { + remoteController.connect(); + } + + switch (commandType) { + case POWER: + if (command == OnOffType.ON) { + remoteController.setPower(Switch.ON); + isPowerOn = true; + } else { + remoteController.setPower(Switch.OFF); + isPowerOn = false; + } + break; + case SOURCE: + remoteController.setSource(command.toString()); + break; + case PICTURE_MODE: + remoteController.setPictureMode(command.toString()); + break; + case ASPECT_RATIO: + remoteController.setAspectRatio(command.toString()); + break; + case FREEZE: + remoteController.setFreeze(command == OnOffType.ON ? Switch.ON : Switch.OFF); + break; + case BLANK: + remoteController.setBlank(command == OnOffType.ON ? Switch.ON : Switch.OFF); + break; + case DIRECTCMD: + remoteController.sendDirectCommand(command.toString()); + break; + default: + logger.warn("Unknown '{}' command!", commandType); + break; + } + } catch (BenqProjectorCommandException e) { + logger.debug("Error executing command '{}', {}", commandType, e.getMessage()); + } catch (BenqProjectorException e) { + logger.warn("Couldn't execute command '{}', {}", commandType, e.getMessage()); + closeConnection(); + } + } + + private void closeConnection() { + BenqProjectorDevice remoteController = device.get(); + try { + logger.debug("Closing connection to device '{}'", this.thing.getUID()); + remoteController.disconnect(); + updateStatus(ThingStatus.OFFLINE); + } catch (BenqProjectorException e) { + logger.debug("Error occurred when closing connection to device '{}'", this.thing.getUID(), e); + } + } +} diff --git a/bundles/org.openhab.binding.benqprojector/src/main/resources/OH-INF/binding/binding.xml b/bundles/org.openhab.binding.benqprojector/src/main/resources/OH-INF/binding/binding.xml new file mode 100644 index 0000000000000..d429d87e6d0e2 --- /dev/null +++ b/bundles/org.openhab.binding.benqprojector/src/main/resources/OH-INF/binding/binding.xml @@ -0,0 +1,9 @@ + + + + BenQ Projector Binding + This binding is compatible with BenQ projectors + + diff --git a/bundles/org.openhab.binding.benqprojector/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.benqprojector/src/main/resources/OH-INF/thing/thing-types.xml new file mode 100644 index 0000000000000..6563264386641 --- /dev/null +++ b/bundles/org.openhab.binding.benqprojector/src/main/resources/OH-INF/thing/thing-types.xml @@ -0,0 +1,185 @@ + + + + + + A BenQ projector connected via a serial port + + + + + + + + + + + + + + + serial-port + Serial Port to Use for Connecting to the BenQ Projector + + + + Configures How Often to Poll the Projector for Updates (5-60; Default 10) + 10 + + + + + + + + A BenQ projector connected via the built-in ethernet port or a serial over + IP device + + + + + + + + + + + + + + + network-address + IP address for the projector or serial over IP device + + + + Port for the projector or serial over IP device + 8000 + + + + Configures How Often to Poll the Projector for Updates (5-60; Default 10) + 10 + + + + + + + Switch + + Powers the Projector On or Off + + + String + + Retrieve or Set the Input Source + + + + + + + + + + + + + + + + + + + + + + + + String + + Retrieve or Set the Picture Mode + + + + + + + + + + + + + + + + + + + + + + + + + + + String + + Retrieve or Set the Aspect Ratio + + + + + + + + + + + + + + + + + + Switch + + Turn the Freeze Image Mode On or Off + + + Switch + + Turn the Screen Blank On or Off + + + String + + Send a Command Directly to the Projector + + + + + + + + + + + + + + + + + + + + + From a69cfa6023b95eb7bbf08fb0fd7cdf1de4669ecb Mon Sep 17 00:00:00 2001 From: Michael Lobstein Date: Sat, 27 Feb 2021 17:36:30 -0600 Subject: [PATCH 02/10] fix query command response parsing Signed-off-by: Michael Lobstein --- .../internal/BenqProjectorDevice.java | 9 ++++---- .../connector/BenqProjectorConnector.java | 1 + .../BenqProjectorSerialConnector.java | 3 ++- .../connector/BenqProjectorTcpConnector.java | 3 ++- .../resources/OH-INF/thing/thing-types.xml | 22 +++++++++---------- 5 files changed, 21 insertions(+), 17 deletions(-) diff --git a/bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/BenqProjectorDevice.java b/bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/BenqProjectorDevice.java index 0c5a98d2b4db6..acb819fc816fa 100644 --- a/bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/BenqProjectorDevice.java +++ b/bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/BenqProjectorDevice.java @@ -67,12 +67,13 @@ private synchronized String sendQuery(String query, int timeout) logger.debug("Response: '{}'", response); + // example: SOUR=HDMI2 String[] responseParts = response.split("="); if (responseParts.length != 2) { throw new BenqProjectorCommandException("Invalid respose for command: " + query); } - return responseParts[1]; + return responseParts[1].toLowerCase(); } protected void sendCommand(String command, int timeout) @@ -113,7 +114,7 @@ public boolean isConnected() { * Power */ public Switch getPowerStatus() throws BenqProjectorCommandException, BenqProjectorException { - return (queryString("pow=?").toUpperCase().contains("ON") ? Switch.ON : Switch.OFF); + return (queryString("pow=?").contains("on") ? Switch.ON : Switch.OFF); } public void setPower(Switch value) throws BenqProjectorCommandException, BenqProjectorException { @@ -157,7 +158,7 @@ public void setAspectRatio(String value) throws BenqProjectorCommandException, B * Blank Screen */ public Switch getBlank() throws BenqProjectorCommandException, BenqProjectorException { - return (queryString("blank=?").toUpperCase().contains("ON") ? Switch.ON : Switch.OFF); + return (queryString("blank=?").contains("on") ? Switch.ON : Switch.OFF); } public void setBlank(Switch value) throws BenqProjectorCommandException, BenqProjectorException { @@ -168,7 +169,7 @@ public void setBlank(Switch value) throws BenqProjectorCommandException, BenqPro * Freeze */ public Switch getFreeze() throws BenqProjectorCommandException, BenqProjectorException { - return (queryString("freeze=?").toUpperCase().contains("ON") ? Switch.ON : Switch.OFF); + return (queryString("freeze=?").contains("on") ? Switch.ON : Switch.OFF); } public void setFreeze(Switch value) throws BenqProjectorCommandException, BenqProjectorException { diff --git a/bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/connector/BenqProjectorConnector.java b/bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/connector/BenqProjectorConnector.java index 2e10c5931ec60..89061ec1f2f2d 100644 --- a/bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/connector/BenqProjectorConnector.java +++ b/bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/connector/BenqProjectorConnector.java @@ -25,6 +25,7 @@ public interface BenqProjectorConnector { public static final String START = "\r*"; public static final String END = "#\r"; + public static final String RESP_START = ">*"; /** * Procedure for connecting to projector. diff --git a/bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/connector/BenqProjectorSerialConnector.java b/bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/connector/BenqProjectorSerialConnector.java index 72ca7caf06736..54dca95cc5dd2 100644 --- a/bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/connector/BenqProjectorSerialConnector.java +++ b/bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/connector/BenqProjectorSerialConnector.java @@ -188,7 +188,8 @@ private String sendMmsg(String data, int timeout) throws IOException, BenqProjec int readBytes = in.read(tmpData, 0, availableBytes); resp = resp.concat(new String(tmpData, 0, readBytes, StandardCharsets.US_ASCII)); if (resp.contains(END)) { - return resp.replace(START, "").replace(END, ""); + return resp.replace(RESP_START, "").replace(END, "").replace(data, "").replace("\r", "") + .replace("\n", ""); } } else { try { diff --git a/bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/connector/BenqProjectorTcpConnector.java b/bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/connector/BenqProjectorTcpConnector.java index 24142fc2cfa46..d8bc0173cd348 100644 --- a/bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/connector/BenqProjectorTcpConnector.java +++ b/bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/connector/BenqProjectorTcpConnector.java @@ -163,7 +163,8 @@ private String sendMmsg(String data, int timeout) throws IOException, BenqProjec int readBytes = in.read(tmpData, 0, availableBytes); resp = resp.concat(new String(tmpData, 0, readBytes, StandardCharsets.US_ASCII)); if (resp.contains(END)) { - return resp.replace(START, "").replace(END, ""); + return resp.replace(RESP_START, "").replace(END, "").replace(data, "").replace("\r", "") + .replace("\n", ""); } } else { try { diff --git a/bundles/org.openhab.binding.benqprojector/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.benqprojector/src/main/resources/OH-INF/thing/thing-types.xml index 6563264386641..be1655e1ba986 100644 --- a/bundles/org.openhab.binding.benqprojector/src/main/resources/OH-INF/thing/thing-types.xml +++ b/bundles/org.openhab.binding.benqprojector/src/main/resources/OH-INF/thing/thing-types.xml @@ -83,10 +83,10 @@ - - - - + + + + @@ -138,13 +138,13 @@ - - - - - - - + + + + + + + From c4a6593de32041a83bdb98f26cc290d96a94fc4d Mon Sep 17 00:00:00 2001 From: Michael Lobstein Date: Sat, 27 Feb 2021 20:44:10 -0600 Subject: [PATCH 03/10] fix command response split Signed-off-by: Michael Lobstein --- .../binding/benqprojector/internal/BenqProjectorDevice.java | 6 +++--- .../internal/connector/BenqProjectorSerialConnector.java | 3 +-- .../internal/connector/BenqProjectorTcpConnector.java | 3 +-- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/BenqProjectorDevice.java b/bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/BenqProjectorDevice.java index acb819fc816fa..9b0536344fb8f 100644 --- a/bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/BenqProjectorDevice.java +++ b/bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/BenqProjectorDevice.java @@ -67,13 +67,13 @@ private synchronized String sendQuery(String query, int timeout) logger.debug("Response: '{}'", response); - // example: SOUR=HDMI2 + // example: sour=?*SOUR=HDMI2 String[] responseParts = response.split("="); - if (responseParts.length != 2) { + if (responseParts.length != 3) { throw new BenqProjectorCommandException("Invalid respose for command: " + query); } - return responseParts[1].toLowerCase(); + return responseParts[2].toLowerCase(); } protected void sendCommand(String command, int timeout) diff --git a/bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/connector/BenqProjectorSerialConnector.java b/bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/connector/BenqProjectorSerialConnector.java index 54dca95cc5dd2..7fd782945ef6d 100644 --- a/bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/connector/BenqProjectorSerialConnector.java +++ b/bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/connector/BenqProjectorSerialConnector.java @@ -188,8 +188,7 @@ private String sendMmsg(String data, int timeout) throws IOException, BenqProjec int readBytes = in.read(tmpData, 0, availableBytes); resp = resp.concat(new String(tmpData, 0, readBytes, StandardCharsets.US_ASCII)); if (resp.contains(END)) { - return resp.replace(RESP_START, "").replace(END, "").replace(data, "").replace("\r", "") - .replace("\n", ""); + return resp.replace(RESP_START, "").replace(END, "").replace("\r", "").replace("\n", ""); } } else { try { diff --git a/bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/connector/BenqProjectorTcpConnector.java b/bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/connector/BenqProjectorTcpConnector.java index d8bc0173cd348..cb3d047b48f4c 100644 --- a/bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/connector/BenqProjectorTcpConnector.java +++ b/bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/connector/BenqProjectorTcpConnector.java @@ -163,8 +163,7 @@ private String sendMmsg(String data, int timeout) throws IOException, BenqProjec int readBytes = in.read(tmpData, 0, availableBytes); resp = resp.concat(new String(tmpData, 0, readBytes, StandardCharsets.US_ASCII)); if (resp.contains(END)) { - return resp.replace(RESP_START, "").replace(END, "").replace(data, "").replace("\r", "") - .replace("\n", ""); + return resp.replace(RESP_START, "").replace(END, "").replace("\r", "").replace("\n", ""); } } else { try { From ade34afc8175f265cf40a56e1cbb7430202f4690 Mon Sep 17 00:00:00 2001 From: Michael Lobstein Date: Sat, 27 Feb 2021 21:48:51 -0600 Subject: [PATCH 04/10] fix aspect ratio query Signed-off-by: Michael Lobstein --- .../benqprojector/internal/BenqProjectorDevice.java | 9 +++++++-- .../internal/handler/BenqProjectorHandler.java | 2 +- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/BenqProjectorDevice.java b/bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/BenqProjectorDevice.java index 9b0536344fb8f..5d3841c4123f5 100644 --- a/bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/BenqProjectorDevice.java +++ b/bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/BenqProjectorDevice.java @@ -33,6 +33,7 @@ public class BenqProjectorDevice { private static final int DEFAULT_TIMEOUT_MS = 5 * 1000; private static final String UNSUPPORTED_ITM = "Unsupported item"; + private static final String BLOCK_ITM = "Block item"; private static final String ILLEGAL_FMT = "Illegal format"; private final Logger logger = LoggerFactory.getLogger(BenqProjectorDevice.class); @@ -57,11 +58,15 @@ private synchronized String sendQuery(String query, int timeout) throw new BenqProjectorException("No response received"); } - if (UNSUPPORTED_ITM.equals(response)) { + if (response.contains(UNSUPPORTED_ITM)) { throw new BenqProjectorCommandException("Unsupported Command response received for command: " + query); } - if (ILLEGAL_FMT.equals(response)) { + if (response.contains(BLOCK_ITM)) { + throw new BenqProjectorCommandException("Block Item received for command: " + query); + } + + if (response.contains(ILLEGAL_FMT)) { throw new BenqProjectorCommandException("Illegal Format response received for command: " + query); } diff --git a/bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/handler/BenqProjectorHandler.java b/bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/handler/BenqProjectorHandler.java index bcf0dccef8457..45442913f1f5a 100644 --- a/bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/handler/BenqProjectorHandler.java +++ b/bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/handler/BenqProjectorHandler.java @@ -193,7 +193,7 @@ private State queryDataFromDevice(BenqProjectorCommandType commandType) { return UnDefType.UNDEF; } case ASPECT_RATIO: - String aspectratio = remoteController.getPictureMode(); + String aspectratio = remoteController.getAspectRatio(); if (aspectratio != null) { return new StringType(aspectratio); } else { From a7fee39ffede3eca40eb23322f54f627f0c88864 Mon Sep 17 00:00:00 2001 From: Michael Lobstein Date: Sun, 28 Feb 2021 09:31:41 -0600 Subject: [PATCH 05/10] Add lamp time and clean up dropdown options Signed-off-by: Michael Lobstein --- .../README.md | 3 ++ .../internal/BenqProjectorCommandType.java | 4 +- .../internal/BenqProjectorDevice.java | 43 ++++++++++++++++--- .../handler/BenqProjectorHandler.java | 4 ++ .../resources/OH-INF/thing/thing-types.xml | 28 ++++-------- 5 files changed, 55 insertions(+), 27 deletions(-) diff --git a/bundles/org.openhab.binding.benqprojector/README.md b/bundles/org.openhab.binding.benqprojector/README.md index 0738f5745897f..3c6564f3e691a 100644 --- a/bundles/org.openhab.binding.benqprojector/README.md +++ b/bundles/org.openhab.binding.benqprojector/README.md @@ -59,6 +59,7 @@ Some notes: | freeze | Switch | Turn the freeze image mode on or off. | | | blank | Switch | Turn the screen blank mode on or off. | | | directcmd | String | Send a command directly to the projector. | Send only | +| lamptime | Number | Retrieves the lamp hours. | Read only | ## Full Example @@ -83,6 +84,7 @@ String benqAspectRatio "Aspect Ratio [%s]" { channel="benqprojector:p Switch benqFreeze { channel="benqprojector:projector-serial:hometheater:freeze" } Switch benqBlank { channel="benqprojector:projector-serial:hometheater:blank" } String benqDirect { channel="benqprojector:projector-serial:hometheater:directcmd", autoupdate="false" } +Number benqLampTime "Lamp Time [%d h]" { channel="benqprojector:projector-serial:hometheater:lamptime" } ``` sitemaps/benq.sitemap @@ -97,6 +99,7 @@ sitemap benq label="BenQ Projector Demo" { Switch item=benqFreeze label="Freeze" Switch item=benqBlank label="Blank Screen" Selection item=benqDirect label="Direct Command" + Text item=benqLampTime } Frame label="Advanced Controls" { Switch item=benqDirect label="Image Flip" mappings=["pp=FT"="Front","pp=RE"="Rear","pp=FC"="Front Ceiling","pp=RC"="Rear Ceiling"] diff --git a/bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/BenqProjectorCommandType.java b/bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/BenqProjectorCommandType.java index a8b5bbf8b0495..f1a67f4152899 100644 --- a/bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/BenqProjectorCommandType.java +++ b/bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/BenqProjectorCommandType.java @@ -16,6 +16,7 @@ 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; @@ -33,7 +34,8 @@ public enum BenqProjectorCommandType { ASPECT_RATIO("AspectRatio", StringItem.class), FREEZE("Freeze", SwitchItem.class), BLANK("Blank", SwitchItem.class), - DIRECTCMD("DirectCmd", StringItem.class); + DIRECTCMD("DirectCmd", StringItem.class), + LAMP_TIME("LampTime", NumberItem.class); private final String text; private Class itemClass; diff --git a/bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/BenqProjectorDevice.java b/bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/BenqProjectorDevice.java index 5d3841c4123f5..6583b0cc3c617 100644 --- a/bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/BenqProjectorDevice.java +++ b/bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/BenqProjectorDevice.java @@ -12,6 +12,8 @@ */ package org.openhab.binding.benqprojector.internal; +import java.time.Duration; + import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.benqprojector.internal.configuration.BenqProjectorConfiguration; @@ -19,6 +21,7 @@ import org.openhab.binding.benqprojector.internal.connector.BenqProjectorSerialConnector; import org.openhab.binding.benqprojector.internal.connector.BenqProjectorTcpConnector; import org.openhab.binding.benqprojector.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; @@ -36,6 +39,11 @@ public class BenqProjectorDevice { private static final String BLOCK_ITM = "Block item"; private static final String ILLEGAL_FMT = "Illegal format"; + private static final int LAMP_REFRESH_WAIT_MINUTES = 5; + + private ExpiringCache cachedLampHours = new ExpiringCache<>(Duration.ofMinutes(LAMP_REFRESH_WAIT_MINUTES), + this::queryLamp); + private final Logger logger = LoggerFactory.getLogger(BenqProjectorDevice.class); private BenqProjectorConnector connection; @@ -90,12 +98,10 @@ protected void sendCommand(String command) throws BenqProjectorCommandException, sendCommand(command, DEFAULT_TIMEOUT_MS); } - /* - * protected int queryInt(String query) throws BenqProjectorCommandException, BenqProjectorException { - * String response = sendQuery(query, DEFAULT_TIMEOUT); - * return Integer.parseInt(response); - * } - */ + protected int queryInt(String query) throws BenqProjectorCommandException, BenqProjectorException { + String response = sendQuery(query, DEFAULT_TIMEOUT_MS); + return Integer.parseInt(response); + } protected String queryString(String query) throws BenqProjectorCommandException, BenqProjectorException { return sendQuery(query, DEFAULT_TIMEOUT_MS); @@ -187,4 +193,29 @@ public void setFreeze(Switch value) throws BenqProjectorCommandException, BenqPr public void sendDirectCommand(String value) throws BenqProjectorCommandException, BenqProjectorException { sendCommand(value); } + + /* + * Lamp Time (hours) - get from cache + */ + public int getLampTime() throws BenqProjectorCommandException, BenqProjectorException { + Integer lampHours = cachedLampHours.getValue(); + + if (lampHours != null) { + return lampHours.intValue(); + } else { + throw new BenqProjectorCommandException("cachedLampHours returned null"); + } + } + + /* + * Get Lamp Time + */ + private @Nullable Integer queryLamp() { + try { + return Integer.valueOf(queryInt("ltim=?")); + } catch (BenqProjectorCommandException | BenqProjectorException e) { + logger.debug("Error executing command ltim=?", e); + return null; + } + } } diff --git a/bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/handler/BenqProjectorHandler.java b/bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/handler/BenqProjectorHandler.java index 45442913f1f5a..d8bef679ac8ea 100644 --- a/bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/handler/BenqProjectorHandler.java +++ b/bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/handler/BenqProjectorHandler.java @@ -28,6 +28,7 @@ import org.openhab.binding.benqprojector.internal.configuration.BenqProjectorConfiguration; import org.openhab.binding.benqprojector.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; @@ -207,6 +208,9 @@ private State queryDataFromDevice(BenqProjectorCommandType commandType) { return blank == Switch.ON ? OnOffType.ON : OnOffType.OFF; case DIRECTCMD: break; + case LAMP_TIME: + int lampTime = remoteController.getLampTime(); + return new DecimalType(lampTime); default: logger.warn("Unknown '{}' command!", commandType); return UnDefType.UNDEF; diff --git a/bundles/org.openhab.binding.benqprojector/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.benqprojector/src/main/resources/OH-INF/thing/thing-types.xml index be1655e1ba986..18799a064a7b8 100644 --- a/bundles/org.openhab.binding.benqprojector/src/main/resources/OH-INF/thing/thing-types.xml +++ b/bundles/org.openhab.binding.benqprojector/src/main/resources/OH-INF/thing/thing-types.xml @@ -16,6 +16,7 @@ + @@ -46,6 +47,7 @@ + @@ -82,20 +84,10 @@ - - - - - - - - - - @@ -115,10 +107,6 @@ - - - - @@ -135,16 +123,10 @@ - - - - - - @@ -181,5 +163,11 @@ + + Number + + Retrieves the Lamp Hours + + From 9b372a0d30d1a2286f8314a2332c9df20bb05968 Mon Sep 17 00:00:00 2001 From: Michael Lobstein Date: Tue, 2 Mar 2021 23:37:36 -0600 Subject: [PATCH 06/10] final changes Signed-off-by: Michael Lobstein --- bundles/org.openhab.binding.benqprojector/README.md | 2 +- .../binding/benqprojector/internal/BenqProjectorDevice.java | 3 +++ .../internal/connector/BenqProjectorSerialConnector.java | 1 + .../internal/connector/BenqProjectorTcpConnector.java | 1 + .../benqprojector/internal/handler/BenqProjectorHandler.java | 3 ++- .../src/main/resources/OH-INF/thing/thing-types.xml | 2 +- 6 files changed, 9 insertions(+), 3 deletions(-) diff --git a/bundles/org.openhab.binding.benqprojector/README.md b/bundles/org.openhab.binding.benqprojector/README.md index 3c6564f3e691a..05409fbcafc1b 100644 --- a/bundles/org.openhab.binding.benqprojector/README.md +++ b/bundles/org.openhab.binding.benqprojector/README.md @@ -34,7 +34,7 @@ The `projector-tcp` thing has the following configuration parameters: Some notes: * If using a serial port connection, the baud rate in the projector OSD menu must be set to 9600 bps. -* The _source_, _picturemode_ and _aspectratio_ channels include a dropdown with all possible settings as described in the BenQ protocol document. +* The _source_, _picturemode_ and _aspectratio_ channels include a dropdown with the most commonly used settings. * Not all pre-defined dropdown options will be usable if your particular projector does support a given option. * If your projector has an option that is not in the dropdown, the string code to access that option will be displayed by the channel when that option is selected by the remote control. * By using the sitemap mapping or a rule to send that code back to the channel, any options that are missing in the binding can be accessed. diff --git a/bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/BenqProjectorDevice.java b/bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/BenqProjectorDevice.java index 6583b0cc3c617..93d3e88ebbc7a 100644 --- a/bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/BenqProjectorDevice.java +++ b/bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/BenqProjectorDevice.java @@ -66,10 +66,12 @@ private synchronized String sendQuery(String query, int timeout) throw new BenqProjectorException("No response received"); } + // TODO: Do something with these... if (response.contains(UNSUPPORTED_ITM)) { throw new BenqProjectorCommandException("Unsupported Command response received for command: " + query); } + // TODO: Blocked commands could potentially be re-tried later if (response.contains(BLOCK_ITM)) { throw new BenqProjectorCommandException("Block Item received for command: " + query); } @@ -80,6 +82,7 @@ private synchronized String sendQuery(String query, int timeout) logger.debug("Response: '{}'", response); + // TODO: clean this up. // example: sour=?*SOUR=HDMI2 String[] responseParts = response.split("="); if (responseParts.length != 3) { diff --git a/bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/connector/BenqProjectorSerialConnector.java b/bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/connector/BenqProjectorSerialConnector.java index 7fd782945ef6d..e2505cc3173d0 100644 --- a/bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/connector/BenqProjectorSerialConnector.java +++ b/bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/connector/BenqProjectorSerialConnector.java @@ -188,6 +188,7 @@ private String sendMmsg(String data, int timeout) throws IOException, BenqProjec int readBytes = in.read(tmpData, 0, availableBytes); resp = resp.concat(new String(tmpData, 0, readBytes, StandardCharsets.US_ASCII)); if (resp.contains(END)) { + // TODO: Clean this up and remove the query string if it is sent back in the response return resp.replace(RESP_START, "").replace(END, "").replace("\r", "").replace("\n", ""); } } else { diff --git a/bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/connector/BenqProjectorTcpConnector.java b/bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/connector/BenqProjectorTcpConnector.java index cb3d047b48f4c..1c367fb70ac53 100644 --- a/bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/connector/BenqProjectorTcpConnector.java +++ b/bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/connector/BenqProjectorTcpConnector.java @@ -163,6 +163,7 @@ private String sendMmsg(String data, int timeout) throws IOException, BenqProjec int readBytes = in.read(tmpData, 0, availableBytes); resp = resp.concat(new String(tmpData, 0, readBytes, StandardCharsets.US_ASCII)); if (resp.contains(END)) { + // TODO: Clean this up and remove the query string if it is sent back in the response return resp.replace(RESP_START, "").replace(END, "").replace("\r", "").replace("\n", ""); } } else { diff --git a/bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/handler/BenqProjectorHandler.java b/bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/handler/BenqProjectorHandler.java index d8bef679ac8ea..41b4f327fa395 100644 --- a/bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/handler/BenqProjectorHandler.java +++ b/bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/handler/BenqProjectorHandler.java @@ -151,7 +151,8 @@ private void updateChannelState(Channel channel) { if (isLinked(channel.getUID())) { updateState(channel.getUID(), state); } - if (state != UnDefType.UNDEF && getThing().getStatus() != ThingStatus.ONLINE) { + // the first valid response will cause the thing to go ONLINE + if (state != UnDefType.UNDEF) { updateStatus(ThingStatus.ONLINE); } } diff --git a/bundles/org.openhab.binding.benqprojector/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.benqprojector/src/main/resources/OH-INF/thing/thing-types.xml index 18799a064a7b8..b39cadc094aa3 100644 --- a/bundles/org.openhab.binding.benqprojector/src/main/resources/OH-INF/thing/thing-types.xml +++ b/bundles/org.openhab.binding.benqprojector/src/main/resources/OH-INF/thing/thing-types.xml @@ -85,9 +85,9 @@ + - From 79e500eb8ffd3fe2859cec2b112760683abe790c Mon Sep 17 00:00:00 2001 From: Michael Lobstein Date: Wed, 3 Mar 2021 17:58:12 -0600 Subject: [PATCH 07/10] finish benq binding Signed-off-by: Michael Lobstein --- CODEOWNERS | 1 + bom/openhab-addons/pom.xml | 5 +++++ bundles/org.openhab.binding.benqprojector/README.md | 2 +- .../benqprojector/internal/BenqProjectorDevice.java | 11 ++++------- .../internal/connector/BenqProjectorConnector.java | 2 +- .../connector/BenqProjectorSerialConnector.java | 10 ++++------ .../internal/connector/BenqProjectorTcpConnector.java | 10 ++++------ .../internal/handler/BenqProjectorHandler.java | 2 ++ bundles/pom.xml | 1 + 9 files changed, 23 insertions(+), 21 deletions(-) diff --git a/CODEOWNERS b/CODEOWNERS index 2a8d2cbeaf9e2..b712b676bbc50 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -22,6 +22,7 @@ /bundles/org.openhab.binding.autelis/ @digitaldan /bundles/org.openhab.binding.automower/ @maxpg /bundles/org.openhab.binding.avmfritz/ @cweitkamp +/bundles/org.openhab.binding.benqprojector/ @mlobstein /bundles/org.openhab.binding.bigassfan/ @mhilbush /bundles/org.openhab.binding.bluetooth/ @cdjackson @cpmeister /bundles/org.openhab.binding.bluetooth.airthings/ @paulianttila diff --git a/bom/openhab-addons/pom.xml b/bom/openhab-addons/pom.xml index 1b0f7a7afa26e..a70d62c035df5 100644 --- a/bom/openhab-addons/pom.xml +++ b/bom/openhab-addons/pom.xml @@ -101,6 +101,11 @@ org.openhab.binding.avmfritz ${project.version} + + org.openhab.addons.bundles + org.openhab.binding.benqprojector + ${project.version} + org.openhab.addons.bundles org.openhab.binding.bigassfan diff --git a/bundles/org.openhab.binding.benqprojector/README.md b/bundles/org.openhab.binding.benqprojector/README.md index 05409fbcafc1b..d4d201c3581c6 100644 --- a/bundles/org.openhab.binding.benqprojector/README.md +++ b/bundles/org.openhab.binding.benqprojector/README.md @@ -1,6 +1,6 @@ # BenQ Projector Binding -This binding is compatible with BenQ projectors that support the control protocol via the built-in network (ethernet or Wi-Fi?) port, serial port or USB to serial adapter. +This binding is compatible with BenQ projectors that support the control protocol via the built-in ethernet port, serial port or USB to serial adapter. If your projector does not have built-in networking, you can connect to your projector's serial port via a TCP connection using a serial over IP device or by using`ser2net`. The control protocol can be found here: https://business-display.benq.com/content/dam/bb/en/product/projector/corporate/lx770/quick-start-guide/lx770-rs232-control-guide-0-windows7-windows8-winxp.pdf diff --git a/bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/BenqProjectorDevice.java b/bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/BenqProjectorDevice.java index 93d3e88ebbc7a..b652b325b45bd 100644 --- a/bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/BenqProjectorDevice.java +++ b/bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/BenqProjectorDevice.java @@ -66,12 +66,10 @@ private synchronized String sendQuery(String query, int timeout) throw new BenqProjectorException("No response received"); } - // TODO: Do something with these... if (response.contains(UNSUPPORTED_ITM)) { - throw new BenqProjectorCommandException("Unsupported Command response received for command: " + query); + return "UNSUPPORTED"; } - // TODO: Blocked commands could potentially be re-tried later if (response.contains(BLOCK_ITM)) { throw new BenqProjectorCommandException("Block Item received for command: " + query); } @@ -82,14 +80,13 @@ private synchronized String sendQuery(String query, int timeout) logger.debug("Response: '{}'", response); - // TODO: clean this up. - // example: sour=?*SOUR=HDMI2 + // example: SOUR=HDMI2 String[] responseParts = response.split("="); - if (responseParts.length != 3) { + if (responseParts.length != 2) { throw new BenqProjectorCommandException("Invalid respose for command: " + query); } - return responseParts[2].toLowerCase(); + return responseParts[1].toLowerCase(); } protected void sendCommand(String command, int timeout) diff --git a/bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/connector/BenqProjectorConnector.java b/bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/connector/BenqProjectorConnector.java index 89061ec1f2f2d..6ab86b65a9839 100644 --- a/bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/connector/BenqProjectorConnector.java +++ b/bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/connector/BenqProjectorConnector.java @@ -25,7 +25,7 @@ public interface BenqProjectorConnector { public static final String START = "\r*"; public static final String END = "#\r"; - public static final String RESP_START = ">*"; + public static final String BLANK = ""; /** * Procedure for connecting to projector. diff --git a/bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/connector/BenqProjectorSerialConnector.java b/bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/connector/BenqProjectorSerialConnector.java index e2505cc3173d0..8acf57c38505c 100644 --- a/bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/connector/BenqProjectorSerialConnector.java +++ b/bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/connector/BenqProjectorSerialConnector.java @@ -148,7 +148,7 @@ public String sendMessage(String data, int timeout) throws BenqProjectorExceptio } return sendMmsg(data, timeout); } else { - return ""; + return BLANK; } } catch (IOException e) { logger.debug("IO error occurred...reconnect and resend once: {}", e.getMessage()); @@ -168,14 +168,13 @@ public void serialEvent(SerialPortEvent arg0) { } private String sendMmsg(String data, int timeout) throws IOException, BenqProjectorException { - String resp = ""; + String resp = BLANK; InputStream in = this.in; OutputStream out = this.out; if (in != null && out != null) { - data = START + data + END; - out.write(data.getBytes(StandardCharsets.US_ASCII)); + out.write((START + data + END).getBytes(StandardCharsets.US_ASCII)); out.flush(); long startTime = System.currentTimeMillis(); @@ -188,8 +187,7 @@ private String sendMmsg(String data, int timeout) throws IOException, BenqProjec int readBytes = in.read(tmpData, 0, availableBytes); resp = resp.concat(new String(tmpData, 0, readBytes, StandardCharsets.US_ASCII)); if (resp.contains(END)) { - // TODO: Clean this up and remove the query string if it is sent back in the response - return resp.replace(RESP_START, "").replace(END, "").replace("\r", "").replace("\n", ""); + return resp.replaceAll("[\\r\\n*#>]", BLANK).replace(data, BLANK); } } else { try { diff --git a/bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/connector/BenqProjectorTcpConnector.java b/bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/connector/BenqProjectorTcpConnector.java index 1c367fb70ac53..85604598609e5 100644 --- a/bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/connector/BenqProjectorTcpConnector.java +++ b/bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/connector/BenqProjectorTcpConnector.java @@ -127,7 +127,7 @@ public String sendMessage(String data, int timeout) throws BenqProjectorExceptio } return sendMmsg(data, timeout); } else { - return ""; + return BLANK; } } catch (IOException e) { logger.debug("IO error occurred...reconnect and resend once: {}", e.getMessage()); @@ -143,14 +143,13 @@ public String sendMessage(String data, int timeout) throws BenqProjectorExceptio } private String sendMmsg(String data, int timeout) throws IOException, BenqProjectorException { - String resp = ""; + String resp = BLANK; InputStream in = this.in; OutputStream out = this.out; if (in != null && out != null) { - data = START + data + END; - out.write(data.getBytes(StandardCharsets.US_ASCII)); + out.write((START + data + END).getBytes(StandardCharsets.US_ASCII)); out.flush(); long startTime = System.currentTimeMillis(); @@ -163,8 +162,7 @@ private String sendMmsg(String data, int timeout) throws IOException, BenqProjec int readBytes = in.read(tmpData, 0, availableBytes); resp = resp.concat(new String(tmpData, 0, readBytes, StandardCharsets.US_ASCII)); if (resp.contains(END)) { - // TODO: Clean this up and remove the query string if it is sent back in the response - return resp.replace(RESP_START, "").replace(END, "").replace("\r", "").replace("\n", ""); + return resp.replaceAll("[\\r\\n*#>]", BLANK).replace(data, BLANK); } } else { try { diff --git a/bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/handler/BenqProjectorHandler.java b/bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/handler/BenqProjectorHandler.java index 41b4f327fa395..63fb9c3c0b897 100644 --- a/bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/handler/BenqProjectorHandler.java +++ b/bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/handler/BenqProjectorHandler.java @@ -49,6 +49,8 @@ * The {@link BenqProjectorHandler} is responsible for handling commands, which are * sent to one of the channels. * + * Based on 'epsonprojector' originally by Pauli Anttila & Yannick Schaus + * * @author Michael Lobstein - Initial contribution */ @NonNullByDefault diff --git a/bundles/pom.xml b/bundles/pom.xml index 3c6b37c4875a2..10bcaa12871a0 100644 --- a/bundles/pom.xml +++ b/bundles/pom.xml @@ -53,6 +53,7 @@ org.openhab.binding.autelis org.openhab.binding.automower org.openhab.binding.avmfritz + org.openhab.binding.benqprojector org.openhab.binding.bigassfan org.openhab.binding.bluetooth org.openhab.binding.bluetooth.airthings From a020d34952dcd3caffffccf1b0e43c30ecc8fbeb Mon Sep 17 00:00:00 2001 From: Michael Lobstein Date: Sat, 5 Jun 2021 15:48:37 -0500 Subject: [PATCH 08/10] review changes Signed-off-by: Michael Lobstein --- .../README.md | 16 +++-- .../internal/BenqProjectorDevice.java | 22 +++---- .../internal/BenqProjectorHandlerFactory.java | 7 +-- .../connector/BenqProjectorConnector.java | 59 +++++++++++++++++-- .../BenqProjectorSerialConnector.java | 44 +------------- .../connector/BenqProjectorTcpConnector.java | 43 +------------- .../resources/OH-INF/thing/thing-types.xml | 11 +--- 7 files changed, 83 insertions(+), 119 deletions(-) diff --git a/bundles/org.openhab.binding.benqprojector/README.md b/bundles/org.openhab.binding.benqprojector/README.md index d4d201c3581c6..aac021583a551 100644 --- a/bundles/org.openhab.binding.benqprojector/README.md +++ b/bundles/org.openhab.binding.benqprojector/README.md @@ -22,14 +22,18 @@ All settings are through thing configuration parameters. The `projector-serial` thing has the following configuration parameters: -- _serialPort_: Serial port device name that is connected to the BenQ 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 +| Parameter | Name | Description | Required | +|-------------------|------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------|----------| +| `serialPort` | Serial Port | Serial port device name that is connected to the BenQ projector to control, e.g. COM1 on Windows, /dev/ttyS0 on Linux or /dev/tty.PL2303-0000103D on Mac. | yes | +| `pollingInterval` | Polling Interval | Polling interval in seconds to update channel states, range 5-60 seconds; default 10 seconds. | no | The `projector-tcp` thing has the following configuration parameters: -- _host_: IP address for the projector or serial over IP device -- _port_: Port for the projector or serial over IP device | Default 8000 for BenQ projectors with built in networking -- _pollingInterval_: Polling interval in seconds to update channel states | 5-60 seconds; default 10 seconds +| Parameter | Name | Description | Required | +|-------------------|------------------|-------------------------------------------------------------------------------------------------------------|----------| +| `host` | Host Name | Host Name or IP address for the projector or serial over IP device. | yes | +| `port` | Port | Port for the projector or serial over IP device, Default 8000 for BenQ projectors with built in networking. | yes | +| `pollingInterval` | Polling Interval | Polling interval in seconds to update channel states, range 5-60 seconds; default 10 seconds. | no | Some notes: @@ -65,7 +69,7 @@ Some notes: things/benq.things: -```java +``` //serial port connection benqprojector:projector-serial:hometheater "Projector" [ serialPort="COM5", pollingInterval=10 ] diff --git a/bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/BenqProjectorDevice.java b/bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/BenqProjectorDevice.java index b652b325b45bd..169ea8c738a34 100644 --- a/bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/BenqProjectorDevice.java +++ b/bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/BenqProjectorDevice.java @@ -33,8 +33,6 @@ */ @NonNullByDefault public class BenqProjectorDevice { - private static final int DEFAULT_TIMEOUT_MS = 5 * 1000; - private static final String UNSUPPORTED_ITM = "Unsupported item"; private static final String BLOCK_ITM = "Block item"; private static final String ILLEGAL_FMT = "Illegal format"; @@ -57,10 +55,9 @@ public BenqProjectorDevice(BenqProjectorConfiguration config) { connection = new BenqProjectorTcpConnector(config.host, config.port); } - private synchronized String sendQuery(String query, int timeout) - throws BenqProjectorCommandException, BenqProjectorException { + private synchronized String sendQuery(String query) throws BenqProjectorCommandException, BenqProjectorException { logger.debug("Query: '{}'", query); - String response = connection.sendMessage(query, timeout); + String response = connection.sendMessage(query); if (response.length() == 0) { throw new BenqProjectorException("No response received"); @@ -89,22 +86,17 @@ private synchronized String sendQuery(String query, int timeout) return responseParts[1].toLowerCase(); } - protected void sendCommand(String command, int timeout) - throws BenqProjectorCommandException, BenqProjectorException { - sendQuery(command, timeout); - } - protected void sendCommand(String command) throws BenqProjectorCommandException, BenqProjectorException { - sendCommand(command, DEFAULT_TIMEOUT_MS); + sendQuery(command); } protected int queryInt(String query) throws BenqProjectorCommandException, BenqProjectorException { - String response = sendQuery(query, DEFAULT_TIMEOUT_MS); + String response = sendQuery(query); return Integer.parseInt(response); } protected String queryString(String query) throws BenqProjectorCommandException, BenqProjectorException { - return sendQuery(query, DEFAULT_TIMEOUT_MS); + return sendQuery(query); } public void connect() throws BenqProjectorException { @@ -173,7 +165,7 @@ public Switch getBlank() throws BenqProjectorCommandException, BenqProjectorExce } public void setBlank(Switch value) throws BenqProjectorCommandException, BenqProjectorException { - sendCommand(String.format("blank=%s", (value == Switch.ON ? "on" : "off")), DEFAULT_TIMEOUT_MS); + sendCommand(String.format("blank=%s", (value == Switch.ON ? "on" : "off"))); } /* @@ -184,7 +176,7 @@ public Switch getFreeze() throws BenqProjectorCommandException, BenqProjectorExc } public void setFreeze(Switch value) throws BenqProjectorCommandException, BenqProjectorException { - sendCommand(String.format("freeze=%s", (value == Switch.ON ? "on" : "off")), DEFAULT_TIMEOUT_MS); + sendCommand(String.format("freeze=%s", (value == Switch.ON ? "on" : "off"))); } /* diff --git a/bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/BenqProjectorHandlerFactory.java b/bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/BenqProjectorHandlerFactory.java index 160c15fa5e5ec..55013a808d2db 100644 --- a/bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/BenqProjectorHandlerFactory.java +++ b/bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/BenqProjectorHandlerFactory.java @@ -14,10 +14,7 @@ import static org.openhab.binding.benqprojector.internal.BenqProjectorBindingConstants.*; -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; @@ -42,8 +39,8 @@ @Component(configurationPid = "binding.benqprojector", service = ThingHandlerFactory.class) public class BenqProjectorHandlerFactory 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 static final Set SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_PROJECTOR_SERIAL, + THING_TYPE_PROJECTOR_TCP); private final SerialPortManager serialPortManager; @Override diff --git a/bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/connector/BenqProjectorConnector.java b/bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/connector/BenqProjectorConnector.java index 6ab86b65a9839..d76117f21830d 100644 --- a/bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/connector/BenqProjectorConnector.java +++ b/bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/connector/BenqProjectorConnector.java @@ -12,7 +12,13 @@ */ package org.openhab.binding.benqprojector.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.benqprojector.internal.BenqProjectorException; /** @@ -22,6 +28,7 @@ */ @NonNullByDefault public interface BenqProjectorConnector { + public static final int TIMEOUT_MS = 5 * 1000; public static final String START = "\r*"; public static final String END = "#\r"; @@ -42,15 +49,59 @@ public interface BenqProjectorConnector { void disconnect() throws BenqProjectorException; /** - * Procedure for send raw data to projector. + * Procedure for sending raw data to projector. * * @param data * Message to send. * - * @param timeout - * timeout to wait response in milliseconds. + * @throws BenqProjectorException + */ + String sendMessage(String data) throws BenqProjectorException; + + /** + * Common method called by the Serial or Tcp connector to send the message to the projector, wait for a response and + * return it after processing. + * + * @param data + * Message to send. + * @param in + * The connector's input stream. + * @param out + * The connector's output stream. * * @throws BenqProjectorException */ - String sendMessage(String data, int timeout) throws BenqProjectorException; + default String sendMsgReadResp(String data, @Nullable InputStream in, @Nullable OutputStream out) + throws IOException, BenqProjectorException { + String resp = BLANK; + + if (in != null && out != null) { + out.write((START + data + END).getBytes(StandardCharsets.US_ASCII)); + out.flush(); + + long startTime = System.currentTimeMillis(); + long elapsedTime = 0; + + while (elapsedTime < TIMEOUT_MS) { + 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(END)) { + return resp.replaceAll("[\\r\\n*#>]", BLANK).replace(data, BLANK); + } + } else { + try { + Thread.sleep(100); + } catch (InterruptedException e) { + throw new BenqProjectorException(e); + } + } + + elapsedTime = System.currentTimeMillis() - startTime; + } + } + return resp; + } } diff --git a/bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/connector/BenqProjectorSerialConnector.java b/bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/connector/BenqProjectorSerialConnector.java index 8acf57c38505c..b72c6d95e738a 100644 --- a/bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/connector/BenqProjectorSerialConnector.java +++ b/bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/connector/BenqProjectorSerialConnector.java @@ -15,7 +15,6 @@ 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; @@ -121,7 +120,7 @@ public void disconnect() throws BenqProjectorException { } @Override - public String sendMessage(String data, int timeout) throws BenqProjectorException { + public String sendMessage(String data) throws BenqProjectorException { InputStream in = this.in; OutputStream out = this.out; @@ -146,7 +145,7 @@ public String sendMessage(String data, int timeout) throws BenqProjectorExceptio } } } - return sendMmsg(data, timeout); + return sendMsgReadResp(data, in, out); } else { return BLANK; } @@ -156,7 +155,7 @@ public String sendMessage(String data, int timeout) throws BenqProjectorExceptio connect(); try { - return sendMmsg(data, timeout); + return sendMsgReadResp(data, in, out); } catch (IOException e1) { throw new BenqProjectorException(e); } @@ -166,41 +165,4 @@ public String sendMessage(String data, int timeout) throws BenqProjectorExceptio @Override public void serialEvent(SerialPortEvent arg0) { } - - private String sendMmsg(String data, int timeout) throws IOException, BenqProjectorException { - String resp = BLANK; - - InputStream in = this.in; - OutputStream out = this.out; - - if (in != null && out != null) { - out.write((START + data + END).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(END)) { - return resp.replaceAll("[\\r\\n*#>]", BLANK).replace(data, BLANK); - } - } else { - try { - Thread.sleep(100); - } catch (InterruptedException e) { - throw new BenqProjectorException(e); - } - } - - elapsedTime = System.currentTimeMillis() - startTime; - } - } - - return resp; - } } diff --git a/bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/connector/BenqProjectorTcpConnector.java b/bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/connector/BenqProjectorTcpConnector.java index 85604598609e5..af7861972e275 100644 --- a/bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/connector/BenqProjectorTcpConnector.java +++ b/bundles/org.openhab.binding.benqprojector/src/main/java/org/openhab/binding/benqprojector/internal/connector/BenqProjectorTcpConnector.java @@ -16,7 +16,6 @@ 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; @@ -100,7 +99,7 @@ public void disconnect() throws BenqProjectorException { } @Override - public String sendMessage(String data, int timeout) throws BenqProjectorException { + public String sendMessage(String data) throws BenqProjectorException { InputStream in = this.in; OutputStream out = this.out; @@ -125,7 +124,7 @@ public String sendMessage(String data, int timeout) throws BenqProjectorExceptio } } } - return sendMmsg(data, timeout); + return sendMsgReadResp(data, in, out); } else { return BLANK; } @@ -135,46 +134,10 @@ public String sendMessage(String data, int timeout) throws BenqProjectorExceptio connect(); try { - return sendMmsg(data, timeout); + return sendMsgReadResp(data, in, out); } catch (IOException e1) { throw new BenqProjectorException(e); } } } - - private String sendMmsg(String data, int timeout) throws IOException, BenqProjectorException { - String resp = BLANK; - - InputStream in = this.in; - OutputStream out = this.out; - - if (in != null && out != null) { - out.write((START + data + END).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(END)) { - return resp.replaceAll("[\\r\\n*#>]", BLANK).replace(data, BLANK); - } - } else { - try { - Thread.sleep(100); - } catch (InterruptedException e) { - throw new BenqProjectorException(e); - } - } - - elapsedTime = System.currentTimeMillis() - startTime; - } - } - return resp; - } } diff --git a/bundles/org.openhab.binding.benqprojector/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.benqprojector/src/main/resources/OH-INF/thing/thing-types.xml index b39cadc094aa3..4b23b7600ff9c 100644 --- a/bundles/org.openhab.binding.benqprojector/src/main/resources/OH-INF/thing/thing-types.xml +++ b/bundles/org.openhab.binding.benqprojector/src/main/resources/OH-INF/thing/thing-types.xml @@ -9,7 +9,7 @@ A BenQ projector connected via a serial port - + @@ -40,7 +40,7 @@ IP device - + @@ -62,7 +62,7 @@ 8000 - + Configures How Often to Poll the Projector for Updates (5-60; Default 10) 10 @@ -70,11 +70,6 @@ - - Switch - - Powers the Projector On or Off - String From ceb013fa2fd3c3f85424819878aaf3c604f4f215 Mon Sep 17 00:00:00 2001 From: Michael Lobstein Date: Tue, 8 Jun 2021 10:48:20 -0500 Subject: [PATCH 09/10] more readme updates Signed-off-by: Michael Lobstein --- .../README.md | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/bundles/org.openhab.binding.benqprojector/README.md b/bundles/org.openhab.binding.benqprojector/README.md index aac021583a551..c306fb5dff73f 100644 --- a/bundles/org.openhab.binding.benqprojector/README.md +++ b/bundles/org.openhab.binding.benqprojector/README.md @@ -22,18 +22,18 @@ All settings are through thing configuration parameters. The `projector-serial` thing has the following configuration parameters: -| Parameter | Name | Description | Required | -|-------------------|------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------|----------| -| `serialPort` | Serial Port | Serial port device name that is connected to the BenQ projector to control, e.g. COM1 on Windows, /dev/ttyS0 on Linux or /dev/tty.PL2303-0000103D on Mac. | yes | -| `pollingInterval` | Polling Interval | Polling interval in seconds to update channel states, range 5-60 seconds; default 10 seconds. | no | +| Parameter | Name | Description | Required | +|-----------------|------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------|----------| +| serialPort | Serial Port | Serial port device name that is connected to the BenQ projector to control, e.g. COM1 on Windows, /dev/ttyS0 on Linux or /dev/tty.PL2303-0000103D on Mac. | yes | +| pollingInterval | Polling Interval | Polling interval in seconds to update channel states, range 5-60 seconds; default 10 seconds. | no | The `projector-tcp` thing has the following configuration parameters: -| Parameter | Name | Description | Required | -|-------------------|------------------|-------------------------------------------------------------------------------------------------------------|----------| -| `host` | Host Name | Host Name or IP address for the projector or serial over IP device. | yes | -| `port` | Port | Port for the projector or serial over IP device, Default 8000 for BenQ projectors with built in networking. | yes | -| `pollingInterval` | Polling Interval | Polling interval in seconds to update channel states, range 5-60 seconds; default 10 seconds. | no | +| Parameter | Name | Description | Required | +|-----------------|------------------|-------------------------------------------------------------------------------------------------------------|----------| +| host | Host Name | Host Name or IP address for the projector or serial over IP device. | yes | +| port | Port | Port for the projector or serial over IP device, Default 8000 for BenQ projectors with built in networking. | yes | +| pollingInterval | Polling Interval | Polling interval in seconds to update channel states, range 5-60 seconds; default 10 seconds. | no | Some notes: @@ -74,7 +74,7 @@ things/benq.things: benqprojector:projector-serial:hometheater "Projector" [ serialPort="COM5", pollingInterval=10 ] // serial over IP connection -benqprojector:projector-tcp:hometheater "Projector" [ host="192.168.0.10", port=8000, pollingInterval=10 ] +benqprojector:projector-tcp:hometheater "Projector" [ host="192.168.0.10", port=8000, pollingInterval=10 ] ``` From 62b7fd7e005cc0623540b53be766c2a242b9f962 Mon Sep 17 00:00:00 2001 From: Michael Lobstein Date: Tue, 8 Jun 2021 13:14:54 -0500 Subject: [PATCH 10/10] capitalize label Signed-off-by: Michael Lobstein --- .../src/main/resources/OH-INF/thing/thing-types.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bundles/org.openhab.binding.benqprojector/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.benqprojector/src/main/resources/OH-INF/thing/thing-types.xml index 4b23b7600ff9c..4bf9876508bc8 100644 --- a/bundles/org.openhab.binding.benqprojector/src/main/resources/OH-INF/thing/thing-types.xml +++ b/bundles/org.openhab.binding.benqprojector/src/main/resources/OH-INF/thing/thing-types.xml @@ -26,7 +26,7 @@ Serial Port to Use for Connecting to the BenQ Projector - + Configures How Often to Poll the Projector for Updates (5-60; Default 10) 10