Skip to content

Commit

Permalink
[epsonprojector] Add retrieval of source list (#18006)
Browse files Browse the repository at this point in the history
* Add retrieval of source list

Signed-off-by: Michael Lobstein <[email protected]>
  • Loading branch information
mlobstein authored Dec 31, 2024
1 parent 34d8fec commit 0538227
Show file tree
Hide file tree
Showing 9 changed files with 131 additions and 6 deletions.
11 changes: 8 additions & 3 deletions bundles/org.openhab.binding.epsonprojector/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand All @@ -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.
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;

Expand Down Expand Up @@ -652,4 +657,28 @@ public String getErrorString() throws EpsonProjectorCommandException, EpsonProje
int err = queryHexInt("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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,23 +38,26 @@
@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) {
return SUPPORTED_THING_TYPES_UIDS.contains(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
protected @Nullable ThingHandler createHandler(Thing thing) {
ThingTypeUID thingTypeUID = thing.getThingTypeUID();

if (THING_TYPE_PROJECTOR_SERIAL.equals(thingTypeUID) || THING_TYPE_PROJECTOR_TCP.equals(thingTypeUID)) {
return new EpsonProjectorHandler(thing, serialPortManager);
return new EpsonProjectorHandler(thing, serialPortManager, stateDescriptionProvider);
}

return null;
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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<EpsonProjectorDevice> 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
Expand Down Expand Up @@ -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));
Expand Down Expand Up @@ -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<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) {
case AKEYSTONE:
Switch autoKeystone = remoteController.getAutoKeystone();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,11 @@
<description>Configures How Often to Poll the Projector for Updates (5-60; Default 10)</description>
<default>10</default>
</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">
<label>Volume Range</label>
<description>Set to Match the Volume Range Seen in the Projector's OSD</description>
Expand Down Expand Up @@ -119,6 +124,11 @@
<description>Configures How Often to Poll the Projector for Updates (5-60; Default 10)</description>
<default>10</default>
</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">
<label>Volume Range</label>
<description>Set to Match the Volume Range Seen in the Projector's OSD</description>
Expand Down Expand Up @@ -219,6 +229,9 @@
<option value="20">PC DSUB</option>
<option value="41">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>
</state>
</channel-type>
Expand Down

0 comments on commit 0538227

Please sign in to comment.