[sonyprojector] Add discovery through SDDP (#16849)

Only applicable to projector models having a network connector

Signed-off-by: Laurent Garnier <lg.hc@free.fr>
This commit is contained in:
lolodomo 2024-06-14 17:48:02 +02:00 committed by GitHub
parent 67e6f8bf30
commit cc71808b78
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 149 additions and 27 deletions

View File

@ -94,8 +94,8 @@ This binding supports the following thing types:
## Discovery ## Discovery
Discovery is not supported at the moment. If the projector is connected via Ethernet and the 'Start SDDP Service' option is present and enabled in the projector Advanced Settings->Service page, the Thing using Ethernet connection and PJ Talk will be discovered automatically.
You have to add all things manually. Serial or Serial over IP connections must be configured manually.
## Binding Configuration ## Binding Configuration

View File

@ -5,6 +5,7 @@
<feature name="openhab-binding-sonyprojector" description="Sony Projector Binding" version="${project.version}"> <feature name="openhab-binding-sonyprojector" description="Sony Projector Binding" version="${project.version}">
<feature>openhab-runtime-base</feature> <feature>openhab-runtime-base</feature>
<feature>openhab-transport-serial</feature> <feature>openhab-transport-serial</feature>
<feature>openhab-core-config-discovery-sddp</feature>
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.sonyprojector/${project.version}</bundle> <bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.sonyprojector/${project.version}</bundle>
</feature> </feature>
</features> </features>

View File

@ -143,6 +143,9 @@ public enum SonyProjectorItem {
SERIAL_NUMBER("Serial Number", new byte[] { (byte) 0x80, 0x02 }), SERIAL_NUMBER("Serial Number", new byte[] { (byte) 0x80, 0x02 }),
INSTALLATION_LOCATION("Installation Location", new byte[] { (byte) 0x80, 0x03 }), INSTALLATION_LOCATION("Installation Location", new byte[] { (byte) 0x80, 0x03 }),
MAC_ADDRESS("MAC Address", new byte[] { (byte) 0x90, 0x00 }),
IP_ADDRESS("IP Address", new byte[] { (byte) 0x90, 0x01 }),
MENU("Menu", null, new byte[] { 0x17, 0x29 }), MENU("Menu", null, new byte[] { 0x17, 0x29 }),
UP("Cursor UP", null, new byte[] { 0x17, 0x35 }), UP("Cursor UP", null, new byte[] { 0x17, 0x35 }),
DOWN("Cursor DOWN", null, new byte[] { 0x17, 0x36 }), DOWN("Cursor DOWN", null, new byte[] { 0x17, 0x36 }),

View File

@ -311,4 +311,15 @@ public class SonyProjectorSdcpConnector extends SonyProjectorConnector {
public String getModelName() throws SonyProjectorException { public String getModelName() throws SonyProjectorException {
return new String(getSetting(SonyProjectorItem.MODEL_NAME), StandardCharsets.UTF_8); return new String(getSetting(SonyProjectorItem.MODEL_NAME), StandardCharsets.UTF_8);
} }
/**
* Request the MAC address
*
* @return the MAC address
*
* @throws SonyProjectorException in case of any problem
*/
public String getMacAddress() throws SonyProjectorException {
return new String(getSetting(SonyProjectorItem.MAC_ADDRESS), StandardCharsets.UTF_8);
}
} }

View File

@ -13,7 +13,6 @@
package org.openhab.binding.sonyprojector.internal.configuration; package org.openhab.binding.sonyprojector.internal.configuration;
import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/** /**
* The {@link SonyProjectorEthernetConfiguration} class contains fields mapping thing configuration parameters. * The {@link SonyProjectorEthernetConfiguration} class contains fields mapping thing configuration parameters.
@ -23,9 +22,12 @@ import org.eclipse.jdt.annotation.Nullable;
*/ */
@NonNullByDefault @NonNullByDefault
public class SonyProjectorEthernetConfiguration { public class SonyProjectorEthernetConfiguration {
public static final int DEFAULT_PORT = 53484;
private static final String DEFAULT_COMMUNITY = "SONY";
public static final String MODEL_AUTO = "AUTO";
public @NonNullByDefault({}) String host; public String host = "";
public @Nullable Integer port; public int port = DEFAULT_PORT;
public @Nullable String community; public String community = DEFAULT_COMMUNITY;
public @Nullable String model; public String model = MODEL_AUTO;
} }

View File

@ -13,6 +13,7 @@
package org.openhab.binding.sonyprojector.internal.configuration; package org.openhab.binding.sonyprojector.internal.configuration;
import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.sonyprojector.internal.handler.SonyProjectorHandler;
/** /**
* The {@link SonyProjectorSerialConfiguration} class contains fields mapping thing configuration parameters. * The {@link SonyProjectorSerialConfiguration} class contains fields mapping thing configuration parameters.
@ -21,7 +22,6 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
*/ */
@NonNullByDefault @NonNullByDefault
public class SonyProjectorSerialConfiguration { public class SonyProjectorSerialConfiguration {
public String port = "";
public @NonNullByDefault({}) String port; public String model = SonyProjectorHandler.DEFAULT_MODEL.getName();
public @NonNullByDefault({}) String model;
} }

View File

@ -13,6 +13,7 @@
package org.openhab.binding.sonyprojector.internal.configuration; package org.openhab.binding.sonyprojector.internal.configuration;
import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.sonyprojector.internal.handler.SonyProjectorHandler;
/** /**
* The {@link SonyProjectorSerialOverIpConfiguration} class contains fields mapping thing configuration parameters. * The {@link SonyProjectorSerialOverIpConfiguration} class contains fields mapping thing configuration parameters.
@ -21,8 +22,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
*/ */
@NonNullByDefault @NonNullByDefault
public class SonyProjectorSerialOverIpConfiguration { public class SonyProjectorSerialOverIpConfiguration {
public String host = "";
public @NonNullByDefault({}) String host; public int port;
public @NonNullByDefault({}) Integer port; public String model = SonyProjectorHandler.DEFAULT_MODEL.getName();
public @NonNullByDefault({}) String model;
} }

View File

@ -0,0 +1,82 @@
/**
* 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.sonyprojector.internal.discovery;
import static org.openhab.binding.sonyprojector.internal.SonyProjectorBindingConstants.THING_TYPE_ETHERNET;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.sonyprojector.internal.configuration.SonyProjectorEthernetConfiguration;
import org.openhab.core.config.discovery.DiscoveryResult;
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
import org.openhab.core.config.discovery.sddp.SddpDevice;
import org.openhab.core.config.discovery.sddp.SddpDiscoveryParticipant;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.ThingUID;
import org.osgi.service.component.annotations.Component;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Discovery Service for Sony Projectors that support SDDP.
*
* @author Laurent Garnier - Initial contribution
*
*/
@NonNullByDefault
@Component(immediate = true)
public class SonyProjectorDiscoveryParticipant implements SddpDiscoveryParticipant {
private final Logger logger = LoggerFactory.getLogger(SonyProjectorDiscoveryParticipant.class);
private static final String SONY = "SONY";
private static final String TYPE_PROJECTOR = "PROJECTOR";
@Override
public Set<ThingTypeUID> getSupportedThingTypeUIDs() {
return Set.of(THING_TYPE_ETHERNET);
}
@Override
public @Nullable DiscoveryResult createResult(SddpDevice device) {
final ThingUID uid = getThingUID(device);
if (uid != null) {
final String label = device.manufacturer + " " + device.model;
final Map<String, Object> properties = Map.of("host", device.ipAddress, //
"port", SonyProjectorEthernetConfiguration.DEFAULT_PORT, //
"model", SonyProjectorEthernetConfiguration.MODEL_AUTO, //
Thing.PROPERTY_MAC_ADDRESS, device.macAddress);
logger.debug("Created a DiscoveryResult for device '{}' with UID '{}'", label, uid.getId());
return DiscoveryResultBuilder.create(uid).withProperties(properties)
.withRepresentationProperty(Thing.PROPERTY_MAC_ADDRESS).withLabel(label).build();
} else {
return null;
}
}
@Override
public @Nullable ThingUID getThingUID(SddpDevice device) {
if (device.manufacturer.toUpperCase(Locale.ENGLISH).contains(SONY)
&& device.type.toUpperCase(Locale.ENGLISH).contains(TYPE_PROJECTOR) && !device.macAddress.isBlank()
&& !device.ipAddress.isBlank()) {
logger.debug("Sony projector with mac {} found at {}", device.macAddress, device.ipAddress);
return new ThingUID(THING_TYPE_ETHERNET, device.macAddress);
}
return null;
}
}

View File

@ -13,6 +13,7 @@
package org.openhab.binding.sonyprojector.internal.handler; package org.openhab.binding.sonyprojector.internal.handler;
import static org.openhab.binding.sonyprojector.internal.SonyProjectorBindingConstants.*; import static org.openhab.binding.sonyprojector.internal.SonyProjectorBindingConstants.*;
import static org.openhab.binding.sonyprojector.internal.configuration.SonyProjectorEthernetConfiguration.MODEL_AUTO;
import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@ -67,7 +68,7 @@ import org.slf4j.LoggerFactory;
@NonNullByDefault @NonNullByDefault
public class SonyProjectorHandler extends BaseThingHandler { public class SonyProjectorHandler extends BaseThingHandler {
private static final SonyProjectorModel DEFAULT_MODEL = SonyProjectorModel.VW520; public static final SonyProjectorModel DEFAULT_MODEL = SonyProjectorModel.VW528;
private static final long POLLING_INTERVAL = TimeUnit.SECONDS.toSeconds(15); private static final long POLLING_INTERVAL = TimeUnit.SECONDS.toSeconds(15);
private final Logger logger = LoggerFactory.getLogger(SonyProjectorHandler.class); private final Logger logger = LoggerFactory.getLogger(SonyProjectorHandler.class);
@ -83,6 +84,7 @@ public class SonyProjectorHandler extends BaseThingHandler {
private @Nullable ScheduledFuture<?> refreshJob; private @Nullable ScheduledFuture<?> refreshJob;
private boolean identifyMac;
private boolean identifyProjector; private boolean identifyProjector;
private SonyProjectorModel projectorModel = DEFAULT_MODEL; private SonyProjectorModel projectorModel = DEFAULT_MODEL;
private SonyProjectorConnector connector = new SonyProjectorSdcpSimuConnector(DEFAULT_MODEL); private SonyProjectorConnector connector = new SonyProjectorSdcpSimuConnector(DEFAULT_MODEL);
@ -321,10 +323,13 @@ public class SonyProjectorHandler extends BaseThingHandler {
logger.debug("Ethernet config port {}", config.port); logger.debug("Ethernet config port {}", config.port);
logger.debug("Ethernet config model {}", configModel); logger.debug("Ethernet config model {}", configModel);
logger.debug("Ethernet config community {}", config.community); logger.debug("Ethernet config community {}", config.community);
if (config.host == null || config.host.isEmpty()) { if (config.host.isBlank()) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"@text/offline.config-error-unknown-host"); "@text/offline.config-error-unknown-host");
} else if (configModel == null || configModel.isEmpty()) { } else if (config.port <= 0) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"@text/offline.config-error-invalid-port");
} else if (configModel.isBlank()) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"@text/offline.config-error-unknown-model"); "@text/offline.config-error-unknown-model");
} else { } else {
@ -332,8 +337,9 @@ public class SonyProjectorHandler extends BaseThingHandler {
connector = simu ? new SonyProjectorSdcpSimuConnector(DEFAULT_MODEL) connector = simu ? new SonyProjectorSdcpSimuConnector(DEFAULT_MODEL)
: new SonyProjectorSdcpConnector(config.host, config.port, config.community, DEFAULT_MODEL); : new SonyProjectorSdcpConnector(config.host, config.port, config.community, DEFAULT_MODEL);
identifyProjector = "AUTO".equals(configModel); identifyMac = getThing().getProperties().get(Thing.PROPERTY_MAC_ADDRESS) == null;
projectorModel = switchToModel("AUTO".equals(configModel) ? null : configModel, true); identifyProjector = MODEL_AUTO.equals(configModel);
projectorModel = switchToModel(identifyProjector ? null : configModel, true);
updateStatus(ThingStatus.UNKNOWN); updateStatus(ThingStatus.UNKNOWN);
} }
@ -342,13 +348,13 @@ public class SonyProjectorHandler extends BaseThingHandler {
String configModel = config.model; String configModel = config.model;
logger.debug("Serial config port {}", config.port); logger.debug("Serial config port {}", config.port);
logger.debug("Serial config model {}", configModel); logger.debug("Serial config model {}", configModel);
if (config.port == null || config.port.isEmpty()) { if (config.port.isBlank()) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"@text/offline.config-error-unknown-port"); "@text/offline.config-error-unknown-port");
} else if (config.port.toLowerCase().startsWith("rfc2217")) { } else if (config.port.toLowerCase().startsWith("rfc2217")) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"@text/offline.config-error-invalid-thing-type"); "@text/offline.config-error-invalid-thing-type");
} else if (configModel == null || configModel.isEmpty()) { } else if (configModel.isBlank()) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"@text/offline.config-error-unknown-model"); "@text/offline.config-error-unknown-model");
} else { } else {
@ -356,6 +362,7 @@ public class SonyProjectorHandler extends BaseThingHandler {
connector = simu ? new SonyProjectorSerialSimuConnector(serialPortManager, DEFAULT_MODEL) connector = simu ? new SonyProjectorSerialSimuConnector(serialPortManager, DEFAULT_MODEL)
: new SonyProjectorSerialConnector(serialPortManager, config.port, DEFAULT_MODEL); : new SonyProjectorSerialConnector(serialPortManager, config.port, DEFAULT_MODEL);
identifyMac = false;
identifyProjector = false; identifyProjector = false;
projectorModel = switchToModel(configModel, true); projectorModel = switchToModel(configModel, true);
@ -367,16 +374,13 @@ public class SonyProjectorHandler extends BaseThingHandler {
logger.debug("Serial over IP config host {}", config.host); logger.debug("Serial over IP config host {}", config.host);
logger.debug("Serial over IP config port {}", config.port); logger.debug("Serial over IP config port {}", config.port);
logger.debug("Serial over IP config model {}", configModel); logger.debug("Serial over IP config model {}", configModel);
if (config.host == null || config.host.isEmpty()) { if (config.host.isBlank()) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"@text/offline.config-error-unknown-host"); "@text/offline.config-error-unknown-host");
} else if (config.port == null) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"@text/offline.config-error-unknown-port");
} else if (config.port <= 0) { } else if (config.port <= 0) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"@text/offline.config-error-invalid-port"); "@text/offline.config-error-invalid-port");
} else if (configModel == null || configModel.isEmpty()) { } else if (configModel.isBlank()) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"@text/offline.config-error-unknown-model"); "@text/offline.config-error-unknown-model");
} else { } else {
@ -385,6 +389,7 @@ public class SonyProjectorHandler extends BaseThingHandler {
connector = simu ? new SonyProjectorSerialSimuConnector(serialPortManager, DEFAULT_MODEL) connector = simu ? new SonyProjectorSerialSimuConnector(serialPortManager, DEFAULT_MODEL)
: new SonyProjectorSerialOverIpConnector(serialPortManager, config.host, config.port, : new SonyProjectorSerialOverIpConnector(serialPortManager, config.host, config.port,
DEFAULT_MODEL); DEFAULT_MODEL);
identifyMac = false;
identifyProjector = false; identifyProjector = false;
projectorModel = switchToModel(configModel, true); projectorModel = switchToModel(configModel, true);
@ -432,6 +437,7 @@ public class SonyProjectorHandler extends BaseThingHandler {
boolean isOn = refreshPowerState(); boolean isOn = refreshPowerState();
refreshModel(); refreshModel();
refreshMacAddress();
refreshChannel(CHANNEL_INPUT, isOn); refreshChannel(CHANNEL_INPUT, isOn);
refreshChannel(CHANNEL_CALIBRATION_PRESET, isOn); refreshChannel(CHANNEL_CALIBRATION_PRESET, isOn);
refreshChannel(CHANNEL_CONTRAST, isOn); refreshChannel(CHANNEL_CONTRAST, isOn);
@ -537,6 +543,19 @@ public class SonyProjectorHandler extends BaseThingHandler {
return model; return model;
} }
private void refreshMacAddress() {
if (identifyMac && getThing().getThingTypeUID().equals(THING_TYPE_ETHERNET)) {
try {
String mac = ((SonyProjectorSdcpConnector) connector).getMacAddress();
logger.debug("getMacAddress returned {}", mac);
getThing().setProperty(Thing.PROPERTY_MAC_ADDRESS, mac);
identifyMac = false;
} catch (SonyProjectorException e) {
logger.debug("getMacAddress failed: {}", e.getMessage());
}
}
}
private boolean refreshPowerState() { private boolean refreshPowerState() {
boolean on = false; boolean on = false;
State state = UnDefType.UNDEF; State state = UnDefType.UNDEF;

View File

@ -12,9 +12,13 @@
<discovery-method> <discovery-method>
<service-type>sddp</service-type> <service-type>sddp</service-type>
<match-properties> <match-properties>
<match-property>
<name>manufacturer</name>
<regex>(?i).*sony.*</regex>
</match-property>
<match-property> <match-property>
<name>type</name> <name>type</name>
<regex>(?i)sony:projector.*</regex> <regex>(?i).*projector.*</regex>
</match-property> </match-property>
</match-properties> </match-properties>
</discovery-method> </discovery-method>