Skip to content

Commit

Permalink
[epsonprojector] Add ESC/VP.net handshake for projectors with built-i…
Browse files Browse the repository at this point in the history
…n ethernet (openhab#9375)

Signed-off-by: Michael Lobstein <michael.lobstein@gmail.com>
  • Loading branch information
mlobstein authored and thinkingstone committed Nov 7, 2021
1 parent 35e8e9a commit f18e444
Show file tree
Hide file tree
Showing 8 changed files with 343 additions and 22 deletions.
18 changes: 10 additions & 8 deletions bundles/org.openhab.binding.epsonprojector/README.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
# Epson Projector Binding

This binding is compatible with Epson projectors that support the ESC/VP21 protocol over a serial port or USB to serial adapter.
Alternatively, you can connect to your projector via a TCP connection using a serial over IP device or by using`ser2net`.
This binding is compatible with Epson projectors that support the ESC/VP21 protocol over the built-in ethernet port, serial port or USB to serial adapter.
If your projector does not have a built-in ethernet port, you can connect to your projector's serial port via a TCP connection using a serial over IP device or by using`ser2net`.

## Supported Things

This binding supports two thing types based on the connection used: `projector-serial` and `projector-tcp`.

## Discovery

The projector thing cannot be auto-discovered, it has to be configured manually.
If the projector has a built-in ethernet port connected to the same network as the openHAB server and the 'AMX Device Discovery' option is present and enabled in the projector's network menu, the thing will be discovered automatically.
Serial port or serial over IP connections must be configured manually.

## Binding Configuration

Expand All @@ -25,8 +26,8 @@ The `projector-serial` thing has the following configuration parameters:

The `projector-tcp` thing has the following configuration parameters:

- _host_: IP address for the serial over IP device
- _port_: Port for the serial over IP device
- _host_: IP address for the projector or serial over IP device
- _port_: Port for the projector or serial over IP device; default 3629 for projectors with built-in ethernet connector
- _pollingInterval_: Polling interval in seconds to update channel states | 5-60 seconds; default 10 seconds

Some notes:
Expand All @@ -38,6 +39,7 @@ Some notes:
* The following channels _aspectratio_, _colormode_, _luminance_, _gamma_ and _background_ are pre-populated with a full set of options and not every option will be useable on all projectors.
* If your projector has an option in one of the above mentioned channels that is not recognized by the binding, the channel will display 'UNKNOWN' if that un-recognized option is selected by the remote control.
* If the projector power is switched to off in the middle of a polling operation, some of the channel values may become undefined until the projector is switched on again.
* If the binding fails to connect to the projector using the direct IP connection, ensure that no password is configured on the projctor.

* On Linux, you may get an error stating the serial port cannot be opened when the epsonprojector binding tries to load.
* You can get around this by adding the `openhab` user to the `dialout` group like this: `usermod -a -G dialout openhab`.
Expand Down Expand Up @@ -86,11 +88,11 @@ Some notes:
things/epson.things:

```java
//serial port connection
// serial port connection
epsonprojector:projector-serial:hometheater "Projector" [ serialPort="COM5", pollingInterval=10 ]

// serial over IP connection
epsonprojector:projector-tcp:hometheater "Projector" [ host="192.168.0.10", port=4444, pollingInterval=10 ]
// direct IP or serial over IP connection
epsonprojector:projector-tcp:hometheater "Projector" [ host="192.168.0.10", port=3629, pollingInterval=10 ]

```

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
*/
package org.openhab.binding.epsonprojector.internal;

import java.util.Set;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.thing.ThingTypeUID;

Expand All @@ -20,18 +22,28 @@
* used across the whole binding.
*
* @author Yannick Schaus - Initial contribution
* @author Michael Lobstein - Updated for OH3
*/
@NonNullByDefault
public class EpsonProjectorBindingConstants {

private static final String BINDING_ID = "epsonprojector";
public static final String BINDING_ID = "epsonprojector";
public static final int DEFAULT_PORT = 3629;

// 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");

public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_PROJECTOR_SERIAL,
THING_TYPE_PROJECTOR_TCP);

// Some Channel types
public static final String CHANNEL_TYPE_POWER = "power";
public static final String CHANNEL_TYPE_POWERSTATE = "powerstate";
public static final String CHANNEL_TYPE_LAMPTIME = "lamptime";

// Config properties
public static final String THING_PROPERTY_HOST = "host";
public static final String THING_PROPERTY_PORT = "port";
public static final String THING_PROPERTY_MAC = "macAddress";
}
Original file line number Diff line number Diff line change
Expand Up @@ -137,12 +137,13 @@ public void setScheduler(ScheduledExecutorService scheduler) {
return response;
}

private String splitResponse(@Nullable String response) throws EpsonProjectorException {
private String splitResponse(@Nullable String response)
throws EpsonProjectorCommandException, EpsonProjectorException {
if (response != null && !"".equals(response)) {
String[] pieces = response.split("=");

if (pieces.length < 2) {
throw new EpsonProjectorException("Invalid response from projector: " + response);
throw new EpsonProjectorCommandException("Invalid response from projector: " + response);
}

return pieces[1].trim();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,6 @@

import static org.openhab.binding.epsonprojector.internal.EpsonProjectorBindingConstants.*;

import java.util.Collections;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.epsonprojector.internal.handler.EpsonProjectorHandler;
Expand All @@ -42,9 +37,6 @@
@NonNullByDefault
@Component(configurationPid = "binding.epsonprojector", service = ThingHandlerFactory.class)
public class EpsonProjectorHandlerFactory extends BaseThingHandlerFactory {

private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Collections.unmodifiableSet(
Stream.of(THING_TYPE_PROJECTOR_SERIAL, THING_TYPE_PROJECTOR_TCP).collect(Collectors.toSet()));
private final SerialPortManager serialPortManager;

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
*/
package org.openhab.binding.epsonprojector.internal.connector;

import static org.openhab.binding.epsonprojector.internal.EpsonProjectorBindingConstants.DEFAULT_PORT;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
Expand All @@ -32,6 +34,7 @@
*/
@NonNullByDefault
public class EpsonProjectorTcpConnector implements EpsonProjectorConnector {
private static final String ESC_VP_HANDSHAKE = "ESC/VP.net\u0010\u0003\u0000\u0000\u0000\u0000";

private final Logger logger = LoggerFactory.getLogger(EpsonProjectorTcpConnector.class);
private final String ip;
Expand All @@ -58,6 +61,16 @@ public void connect() throws EpsonProjectorException {
} catch (IOException e) {
throw new EpsonProjectorException(e);
}

// Projectors with built in Ethernet listen on 3629, we must send the handshake to initialize the connection
if (port == DEFAULT_PORT) {
try {
String response = sendMessage(ESC_VP_HANDSHAKE, 5000);
logger.debug("Response to initialisation of ESC/VP.net is: {}", response);
} catch (EpsonProjectorException e) {
logger.debug("Error within initialisation of ESC/VP.net: {}", e.getMessage());
}
}
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.epsonprojector.internal.discovery;

import static org.openhab.binding.epsonprojector.internal.EpsonProjectorBindingConstants.*;

import java.io.IOException;
import java.net.SocketException;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
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.core.config.discovery.AbstractDiscoveryService;
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
import org.openhab.core.config.discovery.DiscoveryService;
import org.openhab.core.net.NetworkAddressService;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.ThingUID;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* The {@link EpsonProjectoreDiscoveryService} class implements a service
* for discovering Epson projectors using the AMX Device Discovery protocol.
*
* @author Mark Hilbush - Initial contribution
* @author Michael Lobstein - Adapted for the Epson Projector binding
*/
@NonNullByDefault
@Component(service = DiscoveryService.class, configurationPid = "discovery.epsonprojector")
public class EpsonProjectorDiscoveryService extends AbstractDiscoveryService {
private final Logger logger = LoggerFactory.getLogger(EpsonProjectorDiscoveryService.class);
private @Nullable ScheduledFuture<?> epsonDiscoveryJob;

// Discovery parameters
public static final boolean BACKGROUND_DISCOVERY_ENABLED = true;
public static final int BACKGROUND_DISCOVERY_DELAY_TIMEOUT_SEC = 10;

private NetworkAddressService networkAddressService;

private boolean terminate = false;

@Activate
public EpsonProjectorDiscoveryService(@Reference NetworkAddressService networkAddressService) {
super(SUPPORTED_THING_TYPES_UIDS, 0, BACKGROUND_DISCOVERY_ENABLED);
this.networkAddressService = networkAddressService;
epsonDiscoveryJob = null;
terminate = false;
}

@Override
public Set<ThingTypeUID> getSupportedThingTypes() {
return SUPPORTED_THING_TYPES_UIDS;
}

@Override
protected void startBackgroundDiscovery() {
if (epsonDiscoveryJob == null) {
terminate = false;
logger.debug("Starting background discovery job in {} seconds", BACKGROUND_DISCOVERY_DELAY_TIMEOUT_SEC);
epsonDiscoveryJob = scheduler.schedule(this::discover, BACKGROUND_DISCOVERY_DELAY_TIMEOUT_SEC,
TimeUnit.SECONDS);
}
}

@Override
protected void stopBackgroundDiscovery() {
ScheduledFuture<?> epsonDiscoveryJob = this.epsonDiscoveryJob;
if (epsonDiscoveryJob != null) {
terminate = true;
epsonDiscoveryJob.cancel(false);
this.epsonDiscoveryJob = null;
}
}

@Override
public void startScan() {
}

@Override
public void stopScan() {
}

private synchronized void discover() {
logger.debug("Discovery job is running");
MulticastListener epsonMulticastListener;
String local = "127.0.0.1";

try {
String ip = networkAddressService.getPrimaryIpv4HostAddress();
epsonMulticastListener = new MulticastListener((ip != null ? ip : local));
} catch (SocketException se) {
logger.debug("Discovery job got Socket exception creating multicast socket: {}", se.getMessage());
return;
} catch (IOException ioe) {
logger.debug("Discovery job got IO exception creating multicast socket: {}", ioe.getMessage());
return;
}

while (!terminate) {
boolean beaconReceived;
try {
// Wait for a discovery beacon.
beaconReceived = epsonMulticastListener.waitForBeacon();
} catch (IOException ioe) {
logger.debug("Discovery job got exception waiting for beacon: {}", ioe.getMessage());
beaconReceived = false;
}

if (beaconReceived) {
// We got a discovery beacon. Process it as a potential new thing
Map<String, Object> properties = new HashMap<>();
String uid = epsonMulticastListener.getUID();

properties.put(THING_PROPERTY_HOST, epsonMulticastListener.getIPAddress());
properties.put(THING_PROPERTY_PORT, DEFAULT_PORT);

logger.trace("Projector with UID {} discovered at IP: {}", uid, epsonMulticastListener.getIPAddress());

ThingUID thingUid = new ThingUID(THING_TYPE_PROJECTOR_TCP, uid);
logger.trace("Creating epson projector discovery result for: {}, IP={}", uid,
epsonMulticastListener.getIPAddress());
thingDiscovered(DiscoveryResultBuilder.create(thingUid).withProperties(properties)
.withLabel("Epson Projector " + uid).withProperty(THING_PROPERTY_MAC, uid)
.withRepresentationProperty(THING_PROPERTY_MAC).build());
}
}
epsonMulticastListener.shutdown();
logger.debug("Discovery job is exiting");
}
}
Loading

0 comments on commit f18e444

Please sign in to comment.