[epsonprojector] Add retrieval of source list (#18006)

* Add retrieval of source list

Signed-off-by: Michael Lobstein <michael.lobstein@gmail.com>
This commit is contained in:
mlobstein 2024-12-31 13:28:29 -06:00 committed by GitHub
parent 34d8fec597
commit 05382279a9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 131 additions and 6 deletions

View File

@ -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 | | 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 | | 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: 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 | | 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 | | 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 | | 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: 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 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. - 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. - 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. - 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. - 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" { Frame label="Controls" {
Switch item=epsonPower label="Power" 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=epsonFreeze label="Freeze"
Switch item=epsonMute label="AV Mute" Switch item=epsonMute label="AV Mute"
// Volume can be a Setpoint also // Volume can be a Setpoint also

View File

@ -41,6 +41,7 @@ public class EpsonProjectorBindingConstants {
public static final String CHANNEL_TYPE_POWER = "power"; public static final String CHANNEL_TYPE_POWER = "power";
public static final String CHANNEL_TYPE_POWERSTATE = "powerstate"; public static final String CHANNEL_TYPE_POWERSTATE = "powerstate";
public static final String CHANNEL_TYPE_LAMPTIME = "lamptime"; public static final String CHANNEL_TYPE_LAMPTIME = "lamptime";
public static final String CHANNEL_TYPE_SOURCE = "source";
// Config properties // Config properties
public static final String THING_PROPERTY_HOST = "host"; public static final String THING_PROPERTY_HOST = "host";

View File

@ -13,9 +13,13 @@
package org.openhab.binding.epsonprojector.internal; package org.openhab.binding.epsonprojector.internal;
import java.time.Duration; 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.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.stream.IntStream;
import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jdt.annotation.Nullable;
@ -33,6 +37,7 @@ import org.openhab.binding.epsonprojector.internal.enums.PowerStatus;
import org.openhab.binding.epsonprojector.internal.enums.Switch; import org.openhab.binding.epsonprojector.internal.enums.Switch;
import org.openhab.core.cache.ExpiringCache; import org.openhab.core.cache.ExpiringCache;
import org.openhab.core.io.transport.serial.SerialPortManager; import org.openhab.core.io.transport.serial.SerialPortManager;
import org.openhab.core.types.StateOption;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -652,4 +657,28 @@ public class EpsonProjectorDevice {
int err = queryHexInt("ERR?"); int err = queryHexInt("ERR?");
return ErrorMessage.forCode(err); return ErrorMessage.forCode(err);
} }
/*
* Source List
*/
public List<StateOption> getSourceList() throws EpsonProjectorException {
final List<StateOption> 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;
}
} }

View File

@ -38,6 +38,7 @@ import org.osgi.service.component.annotations.Reference;
@Component(configurationPid = "binding.epsonprojector", service = ThingHandlerFactory.class) @Component(configurationPid = "binding.epsonprojector", service = ThingHandlerFactory.class)
public class EpsonProjectorHandlerFactory extends BaseThingHandlerFactory { public class EpsonProjectorHandlerFactory extends BaseThingHandlerFactory {
private final SerialPortManager serialPortManager; private final SerialPortManager serialPortManager;
private final EpsonStateDescriptionOptionProvider stateDescriptionProvider;
@Override @Override
public boolean supportsThingType(ThingTypeUID thingTypeUID) { public boolean supportsThingType(ThingTypeUID thingTypeUID) {
@ -45,8 +46,10 @@ public class EpsonProjectorHandlerFactory extends BaseThingHandlerFactory {
} }
@Activate @Activate
public EpsonProjectorHandlerFactory(final @Reference SerialPortManager serialPortManager) { public EpsonProjectorHandlerFactory(final @Reference SerialPortManager serialPortManager,
final @Reference EpsonStateDescriptionOptionProvider provider) {
this.serialPortManager = serialPortManager; this.serialPortManager = serialPortManager;
this.stateDescriptionProvider = provider;
} }
@Override @Override
@ -54,7 +57,7 @@ public class EpsonProjectorHandlerFactory extends BaseThingHandlerFactory {
ThingTypeUID thingTypeUID = thing.getThingTypeUID(); ThingTypeUID thingTypeUID = thing.getThingTypeUID();
if (THING_TYPE_PROJECTOR_SERIAL.equals(thingTypeUID) || THING_TYPE_PROJECTOR_TCP.equals(thingTypeUID)) { 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; return null;

View File

@ -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;
}
}

View File

@ -46,4 +46,9 @@ public class EpsonProjectorConfiguration {
* Maximum volume setting of this projector, ie 20, 40, etc. * Maximum volume setting of this projector, ie 20, 40, etc.
*/ */
public int maxVolume = 20; public int maxVolume = 20;
/**
* Boolean to indicate if source list should be retrieved from projector
*/
public boolean loadSourceList;
} }

View File

@ -26,6 +26,7 @@ import org.openhab.binding.epsonprojector.internal.EpsonProjectorCommandExceptio
import org.openhab.binding.epsonprojector.internal.EpsonProjectorCommandType; import org.openhab.binding.epsonprojector.internal.EpsonProjectorCommandType;
import org.openhab.binding.epsonprojector.internal.EpsonProjectorDevice; import org.openhab.binding.epsonprojector.internal.EpsonProjectorDevice;
import org.openhab.binding.epsonprojector.internal.EpsonProjectorException; 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.configuration.EpsonProjectorConfiguration;
import org.openhab.binding.epsonprojector.internal.enums.AspectRatio; import org.openhab.binding.epsonprojector.internal.enums.AspectRatio;
import org.openhab.binding.epsonprojector.internal.enums.Background; import org.openhab.binding.epsonprojector.internal.enums.Background;
@ -49,6 +50,7 @@ import org.openhab.core.thing.binding.BaseThingHandler;
import org.openhab.core.types.Command; import org.openhab.core.types.Command;
import org.openhab.core.types.RefreshType; import org.openhab.core.types.RefreshType;
import org.openhab.core.types.State; import org.openhab.core.types.State;
import org.openhab.core.types.StateOption;
import org.openhab.core.types.UnDefType; import org.openhab.core.types.UnDefType;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -66,18 +68,23 @@ public class EpsonProjectorHandler extends BaseThingHandler {
private final Logger logger = LoggerFactory.getLogger(EpsonProjectorHandler.class); private final Logger logger = LoggerFactory.getLogger(EpsonProjectorHandler.class);
private final SerialPortManager serialPortManager; private final SerialPortManager serialPortManager;
private final EpsonStateDescriptionOptionProvider stateDescriptionProvider;
private @Nullable ScheduledFuture<?> pollingJob; private @Nullable ScheduledFuture<?> pollingJob;
private Optional<EpsonProjectorDevice> device = Optional.empty(); private Optional<EpsonProjectorDevice> device = Optional.empty();
private boolean loadSourceList = false;
private boolean isSourceListLoaded = false;
private boolean isPowerOn = false; private boolean isPowerOn = false;
private int maxVolume = 20; private int maxVolume = 20;
private int curVolumeStep = -1; private int curVolumeStep = -1;
private int pollingInterval = DEFAULT_POLLING_INTERVAL_SEC; private int pollingInterval = DEFAULT_POLLING_INTERVAL_SEC;
public EpsonProjectorHandler(Thing thing, SerialPortManager serialPortManager) { public EpsonProjectorHandler(Thing thing, SerialPortManager serialPortManager,
EpsonStateDescriptionOptionProvider stateDescriptionProvider) {
super(thing); super(thing);
this.serialPortManager = serialPortManager; this.serialPortManager = serialPortManager;
this.stateDescriptionProvider = stateDescriptionProvider;
} }
@Override @Override
@ -107,6 +114,7 @@ public class EpsonProjectorHandler extends BaseThingHandler {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR); updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR);
} }
loadSourceList = config.loadSourceList;
maxVolume = config.maxVolume; maxVolume = config.maxVolume;
pollingInterval = config.pollingInterval; pollingInterval = config.pollingInterval;
device.ifPresent(dev -> dev.setScheduler(scheduler)); device.ifPresent(dev -> dev.setScheduler(scheduler));
@ -188,6 +196,18 @@ public class EpsonProjectorHandler extends BaseThingHandler {
return null; return null;
} }
// When polling for PWR status, also try to get SOURCELIST when enabled until successful
if (EpsonProjectorCommandType.POWER == commandType && loadSourceList && !isSourceListLoaded) {
final List<StateOption> 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) { switch (commandType) {
case AKEYSTONE: case AKEYSTONE:
Switch autoKeystone = remoteController.getAutoKeystone(); Switch autoKeystone = remoteController.getAutoKeystone();

View File

@ -13,6 +13,8 @@ thing-type.epsonprojector.projector-tcp.description = An Epson projector which s
# thing types config # 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.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.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 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-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.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.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.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.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 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.20 = PC DSUB
channel-type.epsonprojector.source.state.option.41 = Video channel-type.epsonprojector.source.state.option.41 = Video
channel-type.epsonprojector.source.state.option.42 = S-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.label = Tint
channel-type.epsonprojector.tint.description = Retrieve or Set the Tint channel-type.epsonprojector.tint.description = Retrieve or Set the Tint
channel-type.epsonprojector.verticalkeystone.label = Vertical Keystone channel-type.epsonprojector.verticalkeystone.label = Vertical Keystone

View File

@ -51,6 +51,11 @@
<description>Configures How Often to Poll the Projector for Updates (5-60; Default 10)</description> <description>Configures How Often to Poll the Projector for Updates (5-60; Default 10)</description>
<default>10</default> <default>10</default>
</parameter> </parameter>
<parameter name="loadSourceList" type="boolean">
<label>Load Source List</label>
<description>Attempt to load source list options from the Projector when True</description>
<default>true</default>
</parameter>
<parameter name="maxVolume" type="integer"> <parameter name="maxVolume" type="integer">
<label>Volume Range</label> <label>Volume Range</label>
<description>Set to Match the Volume Range Seen in the Projector's OSD</description> <description>Set to Match the Volume Range Seen in the Projector's OSD</description>
@ -119,6 +124,11 @@
<description>Configures How Often to Poll the Projector for Updates (5-60; Default 10)</description> <description>Configures How Often to Poll the Projector for Updates (5-60; Default 10)</description>
<default>10</default> <default>10</default>
</parameter> </parameter>
<parameter name="loadSourceList" type="boolean">
<label>Load Source List</label>
<description>Attempt to load source list options from the Projector when True</description>
<default>true</default>
</parameter>
<parameter name="maxVolume" type="integer"> <parameter name="maxVolume" type="integer">
<label>Volume Range</label> <label>Volume Range</label>
<description>Set to Match the Volume Range Seen in the Projector's OSD</description> <description>Set to Match the Volume Range Seen in the Projector's OSD</description>
@ -219,6 +229,9 @@
<option value="20">PC DSUB</option> <option value="20">PC DSUB</option>
<option value="41">Video</option> <option value="41">Video</option>
<option value="42">S-Video</option> <option value="42">S-Video</option>
<option value="52">USB</option>
<option value="53">LAN</option>
<option value="56">WiFi Direct</option>
</options> </options>
</state> </state>
</channel-type> </channel-type>