From 05382279a9cdc0e592602ef0af68ba874f174e09 Mon Sep 17 00:00:00 2001 From: mlobstein Date: Tue, 31 Dec 2024 13:28:29 -0600 Subject: [PATCH] [epsonprojector] Add retrieval of source list (#18006) * Add retrieval of source list Signed-off-by: Michael Lobstein --- .../README.md | 11 +++-- .../EpsonProjectorBindingConstants.java | 1 + .../internal/EpsonProjectorDevice.java | 29 +++++++++++++ .../EpsonProjectorHandlerFactory.java | 7 +++- .../EpsonStateDescriptionOptionProvider.java | 42 +++++++++++++++++++ .../EpsonProjectorConfiguration.java | 5 +++ .../handler/EpsonProjectorHandler.java | 22 +++++++++- .../OH-INF/i18n/epsonprojector.properties | 7 ++++ .../resources/OH-INF/thing/thing-types.xml | 13 ++++++ 9 files changed, 131 insertions(+), 6 deletions(-) create mode 100644 bundles/org.openhab.binding.epsonprojector/src/main/java/org/openhab/binding/epsonprojector/internal/EpsonStateDescriptionOptionProvider.java diff --git a/bundles/org.openhab.binding.epsonprojector/README.md b/bundles/org.openhab.binding.epsonprojector/README.md index 2f0ce2af889b1..017f79a0e024c 100644 --- a/bundles/org.openhab.binding.epsonprojector/README.md +++ b/bundles/org.openhab.binding.epsonprojector/README.md @@ -25,7 +25,8 @@ The `projector-serial` thing has the following configuration parameters: |-----------------|------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------|----------| | serialPort | Serial Port | Serial port device name that is connected to the Epson projector to control, e.g. COM1 on Windows, /dev/ttyS0 on Linux or /dev/tty.PL2303-0000103D on Mac. | yes | | pollingInterval | Polling Interval | Polling interval in seconds to update channel states, range 5-60 seconds; default 10 seconds. | no | -| maxVolume | Max Volume Range | Set to the maximum volume level available in the projector's OSD to select the correct range for the volume control. e.g. 20 or 40; default 20 | no | +| loadSourceList | Load Source List | Attempt to load source list options from the projector when True; default True. | no | +| maxVolume | Volume Range | Set to the maximum volume level available in the projector's OSD to select the correct range for the volume control. e.g. 20 or 40; default 20 | no | The `projector-tcp` thing has the following configuration parameters: @@ -34,12 +35,15 @@ The `projector-tcp` thing has the following configuration parameters: | 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 3629 for projectors with built-in Ethernet connector or Wi-Fi. | yes | | pollingInterval | Polling Interval | Polling interval in seconds to update channel states, range 5-60 seconds; default 10 seconds. | no | -| maxVolume | Max Volume Range | Set to the maximum volume level available in the projector's OSD to select the correct range for the volume control. e.g. 20 or 40; default 20 | no | +| loadSourceList | Load Source List | Attempt to load source list options from the projector when True; default True. | no | +| maxVolume | Volume Range | Set to the maximum volume level available in the projector's OSD to select the correct range for the volume control. e.g. 20 or 40; default 20 | no | Some notes: - The binding should work on all Epson projectors that support the ESC/VP21 protocol, however not all binding channels will be useable on all projectors. - The _source_ channel includes a dropdown with the most common source inputs. +- When the `loadSourceList` configuration option is set to true, the binding attempts to retrieve the available sources list from the projector and loads them into the _source_ channel. +- The command to retrieve the sources list is not supported by older projectors and in this case the pre-defined sources list is used instead. - If your projector has a source input that is not in the dropdown, the two character hex code to access that input will be displayed by the _source_ channel when that input is selected by the remote control. - By using the sitemap mapping or a rule to send the input's code back to the _source_ channel, any source on the projector can be accessed by the binding. - The following channels _aspectratio_, _colormode_, _luminance_, _gamma_ and _background_ are pre-populated with a full set of options but not every option will be useable on all projectors. @@ -159,7 +163,8 @@ sitemap epson label="Epson Projector" { Frame label="Controls" { Switch item=epsonPower label="Power" - Selection item=epsonSource label="Source" mappings=["30"="HDMI1", "A0"="HDMI2", "14"="Component", "20"="PC DSUB", "41"="Video", "42"="S-Video"] + // Uncomment mappings below to override the Source state options + Selection item=epsonSource label="Source" // mappings=["30"="HDMI1", "A0"="HDMI2", "14"="Component", "20"="PC DSUB", "41"="Video", "42"="S-Video", "52"="USB", "53"="LAN", "56"="WiFi Direct"] Switch item=epsonFreeze label="Freeze" Switch item=epsonMute label="AV Mute" // Volume can be a Setpoint also diff --git a/bundles/org.openhab.binding.epsonprojector/src/main/java/org/openhab/binding/epsonprojector/internal/EpsonProjectorBindingConstants.java b/bundles/org.openhab.binding.epsonprojector/src/main/java/org/openhab/binding/epsonprojector/internal/EpsonProjectorBindingConstants.java index cb90d5ebe7205..79de347150ad1 100644 --- a/bundles/org.openhab.binding.epsonprojector/src/main/java/org/openhab/binding/epsonprojector/internal/EpsonProjectorBindingConstants.java +++ b/bundles/org.openhab.binding.epsonprojector/src/main/java/org/openhab/binding/epsonprojector/internal/EpsonProjectorBindingConstants.java @@ -41,6 +41,7 @@ public class EpsonProjectorBindingConstants { public static final String CHANNEL_TYPE_POWER = "power"; public static final String CHANNEL_TYPE_POWERSTATE = "powerstate"; public static final String CHANNEL_TYPE_LAMPTIME = "lamptime"; + public static final String CHANNEL_TYPE_SOURCE = "source"; // Config properties public static final String THING_PROPERTY_HOST = "host"; diff --git a/bundles/org.openhab.binding.epsonprojector/src/main/java/org/openhab/binding/epsonprojector/internal/EpsonProjectorDevice.java b/bundles/org.openhab.binding.epsonprojector/src/main/java/org/openhab/binding/epsonprojector/internal/EpsonProjectorDevice.java index b02bd336a216f..978d197490b2d 100644 --- a/bundles/org.openhab.binding.epsonprojector/src/main/java/org/openhab/binding/epsonprojector/internal/EpsonProjectorDevice.java +++ b/bundles/org.openhab.binding.epsonprojector/src/main/java/org/openhab/binding/epsonprojector/internal/EpsonProjectorDevice.java @@ -13,9 +13,13 @@ package org.openhab.binding.epsonprojector.internal; import java.time.Duration; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; +import java.util.stream.IntStream; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; @@ -33,6 +37,7 @@ import org.openhab.binding.epsonprojector.internal.enums.Switch; import org.openhab.core.cache.ExpiringCache; import org.openhab.core.io.transport.serial.SerialPortManager; +import org.openhab.core.types.StateOption; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -652,4 +657,28 @@ public String getErrorString() throws EpsonProjectorCommandException, EpsonProje int err = queryHexInt("ERR?"); return ErrorMessage.forCode(err); } + + /* + * Source List + */ + public List getSourceList() throws EpsonProjectorException { + final List sourceListOptions = new ArrayList<>(); + + try { + // example: 30 HDMI1 A0 HDMI2 + final String[] sources = queryString("SOURCELIST?").split(" "); + + if (sources.length % 2 != 0) { + logger.debug("getSourceList(): {} has odd number of elements!", Arrays.toString(sources)); + } else if (sources[0].length() != 2) { + logger.debug("getSourceList(): {} has invalid first entry", Arrays.toString(sources)); + } else { + IntStream.range(0, sources.length / 2) + .forEach(i -> sourceListOptions.add(new StateOption(sources[i * 2], sources[i * 2 + 1]))); + } + } catch (EpsonProjectorCommandException e) { + logger.debug("getSourceList(): {}", e.getMessage()); + } + return sourceListOptions; + } } diff --git a/bundles/org.openhab.binding.epsonprojector/src/main/java/org/openhab/binding/epsonprojector/internal/EpsonProjectorHandlerFactory.java b/bundles/org.openhab.binding.epsonprojector/src/main/java/org/openhab/binding/epsonprojector/internal/EpsonProjectorHandlerFactory.java index f21da67945222..b50b5c6152da8 100644 --- a/bundles/org.openhab.binding.epsonprojector/src/main/java/org/openhab/binding/epsonprojector/internal/EpsonProjectorHandlerFactory.java +++ b/bundles/org.openhab.binding.epsonprojector/src/main/java/org/openhab/binding/epsonprojector/internal/EpsonProjectorHandlerFactory.java @@ -38,6 +38,7 @@ @Component(configurationPid = "binding.epsonprojector", service = ThingHandlerFactory.class) public class EpsonProjectorHandlerFactory extends BaseThingHandlerFactory { private final SerialPortManager serialPortManager; + private final EpsonStateDescriptionOptionProvider stateDescriptionProvider; @Override public boolean supportsThingType(ThingTypeUID thingTypeUID) { @@ -45,8 +46,10 @@ public boolean supportsThingType(ThingTypeUID thingTypeUID) { } @Activate - public EpsonProjectorHandlerFactory(final @Reference SerialPortManager serialPortManager) { + public EpsonProjectorHandlerFactory(final @Reference SerialPortManager serialPortManager, + final @Reference EpsonStateDescriptionOptionProvider provider) { this.serialPortManager = serialPortManager; + this.stateDescriptionProvider = provider; } @Override @@ -54,7 +57,7 @@ public EpsonProjectorHandlerFactory(final @Reference SerialPortManager serialPor ThingTypeUID thingTypeUID = thing.getThingTypeUID(); if (THING_TYPE_PROJECTOR_SERIAL.equals(thingTypeUID) || THING_TYPE_PROJECTOR_TCP.equals(thingTypeUID)) { - return new EpsonProjectorHandler(thing, serialPortManager); + return new EpsonProjectorHandler(thing, serialPortManager, stateDescriptionProvider); } return null; diff --git a/bundles/org.openhab.binding.epsonprojector/src/main/java/org/openhab/binding/epsonprojector/internal/EpsonStateDescriptionOptionProvider.java b/bundles/org.openhab.binding.epsonprojector/src/main/java/org/openhab/binding/epsonprojector/internal/EpsonStateDescriptionOptionProvider.java new file mode 100644 index 0000000000000..83159c532f76d --- /dev/null +++ b/bundles/org.openhab.binding.epsonprojector/src/main/java/org/openhab/binding/epsonprojector/internal/EpsonStateDescriptionOptionProvider.java @@ -0,0 +1,42 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.epsonprojector.internal; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.core.events.EventPublisher; +import org.openhab.core.thing.binding.BaseDynamicStateDescriptionProvider; +import org.openhab.core.thing.i18n.ChannelTypeI18nLocalizationService; +import org.openhab.core.thing.link.ItemChannelLinkRegistry; +import org.openhab.core.thing.type.DynamicStateDescriptionProvider; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; + +/** + * Dynamic provider of state options while leaving other state description fields as original. + * + * @author Michael Lobstein - Initial contribution + */ +@Component(service = { DynamicStateDescriptionProvider.class, EpsonStateDescriptionOptionProvider.class }) +@NonNullByDefault +public class EpsonStateDescriptionOptionProvider extends BaseDynamicStateDescriptionProvider { + + @Activate + public EpsonStateDescriptionOptionProvider(final @Reference EventPublisher eventPublisher, + final @Reference ItemChannelLinkRegistry itemChannelLinkRegistry, + final @Reference ChannelTypeI18nLocalizationService channelTypeI18nLocalizationService) { + this.eventPublisher = eventPublisher; + this.itemChannelLinkRegistry = itemChannelLinkRegistry; + this.channelTypeI18nLocalizationService = channelTypeI18nLocalizationService; + } +} diff --git a/bundles/org.openhab.binding.epsonprojector/src/main/java/org/openhab/binding/epsonprojector/internal/configuration/EpsonProjectorConfiguration.java b/bundles/org.openhab.binding.epsonprojector/src/main/java/org/openhab/binding/epsonprojector/internal/configuration/EpsonProjectorConfiguration.java index 89724213d8e46..a79b84c6617d3 100644 --- a/bundles/org.openhab.binding.epsonprojector/src/main/java/org/openhab/binding/epsonprojector/internal/configuration/EpsonProjectorConfiguration.java +++ b/bundles/org.openhab.binding.epsonprojector/src/main/java/org/openhab/binding/epsonprojector/internal/configuration/EpsonProjectorConfiguration.java @@ -46,4 +46,9 @@ public class EpsonProjectorConfiguration { * Maximum volume setting of this projector, ie 20, 40, etc. */ public int maxVolume = 20; + + /** + * Boolean to indicate if source list should be retrieved from projector + */ + public boolean loadSourceList; } diff --git a/bundles/org.openhab.binding.epsonprojector/src/main/java/org/openhab/binding/epsonprojector/internal/handler/EpsonProjectorHandler.java b/bundles/org.openhab.binding.epsonprojector/src/main/java/org/openhab/binding/epsonprojector/internal/handler/EpsonProjectorHandler.java index 629eeee604911..582e0ad8118f6 100644 --- a/bundles/org.openhab.binding.epsonprojector/src/main/java/org/openhab/binding/epsonprojector/internal/handler/EpsonProjectorHandler.java +++ b/bundles/org.openhab.binding.epsonprojector/src/main/java/org/openhab/binding/epsonprojector/internal/handler/EpsonProjectorHandler.java @@ -26,6 +26,7 @@ import org.openhab.binding.epsonprojector.internal.EpsonProjectorCommandType; import org.openhab.binding.epsonprojector.internal.EpsonProjectorDevice; import org.openhab.binding.epsonprojector.internal.EpsonProjectorException; +import org.openhab.binding.epsonprojector.internal.EpsonStateDescriptionOptionProvider; import org.openhab.binding.epsonprojector.internal.configuration.EpsonProjectorConfiguration; import org.openhab.binding.epsonprojector.internal.enums.AspectRatio; import org.openhab.binding.epsonprojector.internal.enums.Background; @@ -49,6 +50,7 @@ import org.openhab.core.types.Command; import org.openhab.core.types.RefreshType; import org.openhab.core.types.State; +import org.openhab.core.types.StateOption; import org.openhab.core.types.UnDefType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -66,18 +68,23 @@ public class EpsonProjectorHandler extends BaseThingHandler { private final Logger logger = LoggerFactory.getLogger(EpsonProjectorHandler.class); private final SerialPortManager serialPortManager; + private final EpsonStateDescriptionOptionProvider stateDescriptionProvider; private @Nullable ScheduledFuture pollingJob; private Optional device = Optional.empty(); + private boolean loadSourceList = false; + private boolean isSourceListLoaded = false; private boolean isPowerOn = false; private int maxVolume = 20; private int curVolumeStep = -1; private int pollingInterval = DEFAULT_POLLING_INTERVAL_SEC; - public EpsonProjectorHandler(Thing thing, SerialPortManager serialPortManager) { + public EpsonProjectorHandler(Thing thing, SerialPortManager serialPortManager, + EpsonStateDescriptionOptionProvider stateDescriptionProvider) { super(thing); this.serialPortManager = serialPortManager; + this.stateDescriptionProvider = stateDescriptionProvider; } @Override @@ -107,6 +114,7 @@ public void initialize() { updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR); } + loadSourceList = config.loadSourceList; maxVolume = config.maxVolume; pollingInterval = config.pollingInterval; device.ifPresent(dev -> dev.setScheduler(scheduler)); @@ -188,6 +196,18 @@ private State queryDataFromDevice(EpsonProjectorCommandType commandType) { return null; } + // When polling for PWR status, also try to get SOURCELIST when enabled until successful + if (EpsonProjectorCommandType.POWER == commandType && loadSourceList && !isSourceListLoaded) { + final List sourceListOptions = remoteController.getSourceList(); + + // If a SOURCELIST was retrieved, load it in the source channel state options + if (!sourceListOptions.isEmpty()) { + stateDescriptionProvider.setStateOptions(new ChannelUID(getThing().getUID(), CHANNEL_TYPE_SOURCE), + sourceListOptions); + isSourceListLoaded = true; + } + } + switch (commandType) { case AKEYSTONE: Switch autoKeystone = remoteController.getAutoKeystone(); diff --git a/bundles/org.openhab.binding.epsonprojector/src/main/resources/OH-INF/i18n/epsonprojector.properties b/bundles/org.openhab.binding.epsonprojector/src/main/resources/OH-INF/i18n/epsonprojector.properties index f427ca1349db0..46c146a6ee1f0 100644 --- a/bundles/org.openhab.binding.epsonprojector/src/main/resources/OH-INF/i18n/epsonprojector.properties +++ b/bundles/org.openhab.binding.epsonprojector/src/main/resources/OH-INF/i18n/epsonprojector.properties @@ -13,6 +13,8 @@ thing-type.epsonprojector.projector-tcp.description = An Epson projector which s # thing types config +thing-type.config.epsonprojector.projector-serial.loadSourceList.label = Load Source List +thing-type.config.epsonprojector.projector-serial.loadSourceList.description = Attempt to load source list options from the Projector when True thing-type.config.epsonprojector.projector-serial.maxVolume.label = Volume Range thing-type.config.epsonprojector.projector-serial.maxVolume.description = Set to Match the Volume Range Seen in the Projector's OSD thing-type.config.epsonprojector.projector-serial.maxVolume.option.20 = Volume range is 0-20 @@ -23,6 +25,8 @@ thing-type.config.epsonprojector.projector-serial.serialPort.label = Serial Port thing-type.config.epsonprojector.projector-serial.serialPort.description = Serial Port to Use for Connecting to the Epson Projector thing-type.config.epsonprojector.projector-tcp.host.label = Host thing-type.config.epsonprojector.projector-tcp.host.description = IP address for the projector or serial over IP device +thing-type.config.epsonprojector.projector-tcp.loadSourceList.label = Load Source List +thing-type.config.epsonprojector.projector-tcp.loadSourceList.description = Attempt to load source list options from the Projector when True thing-type.config.epsonprojector.projector-tcp.maxVolume.label = Volume Range thing-type.config.epsonprojector.projector-tcp.maxVolume.description = Set to Match the Volume Range Seen in the Projector's OSD thing-type.config.epsonprojector.projector-tcp.maxVolume.option.20 = Volume range is 0-20 @@ -134,6 +138,9 @@ channel-type.epsonprojector.source.state.option.14 = Component channel-type.epsonprojector.source.state.option.20 = PC DSUB channel-type.epsonprojector.source.state.option.41 = Video channel-type.epsonprojector.source.state.option.42 = S-Video +channel-type.epsonprojector.source.state.option.52 = USB +channel-type.epsonprojector.source.state.option.53 = LAN +channel-type.epsonprojector.source.state.option.56 = WiFi Direct channel-type.epsonprojector.tint.label = Tint channel-type.epsonprojector.tint.description = Retrieve or Set the Tint channel-type.epsonprojector.verticalkeystone.label = Vertical Keystone diff --git a/bundles/org.openhab.binding.epsonprojector/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.epsonprojector/src/main/resources/OH-INF/thing/thing-types.xml index 4c20c616826f9..8d9153ee4e7b4 100644 --- a/bundles/org.openhab.binding.epsonprojector/src/main/resources/OH-INF/thing/thing-types.xml +++ b/bundles/org.openhab.binding.epsonprojector/src/main/resources/OH-INF/thing/thing-types.xml @@ -51,6 +51,11 @@ Configures How Often to Poll the Projector for Updates (5-60; Default 10) 10 + + + Attempt to load source list options from the Projector when True + true + Set to Match the Volume Range Seen in the Projector's OSD @@ -119,6 +124,11 @@ Configures How Often to Poll the Projector for Updates (5-60; Default 10) 10 + + + Attempt to load source list options from the Projector when True + true + Set to Match the Volume Range Seen in the Projector's OSD @@ -219,6 +229,9 @@ + + +