added migrated 2.x add-ons
Signed-off-by: Kai Kreuzer <kai@openhab.org>
This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<features name="org.openhab.binding.pioneeravr-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.4.0">
|
||||
<repository>mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features</repository>
|
||||
|
||||
<feature name="openhab-binding-pioneeravr" description="PioneerAVR Binding" version="${project.version}">
|
||||
<feature>openhab-runtime-base</feature>
|
||||
<feature>openhab-transport-serial</feature>
|
||||
<feature>openhab-transport-upnp</feature>
|
||||
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.pioneeravr/${project.version}</bundle>
|
||||
</feature>
|
||||
</features>
|
||||
@@ -0,0 +1,107 @@
|
||||
/**
|
||||
* 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.pioneeravr.internal;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
|
||||
/**
|
||||
* The {@link PioneerAvrBinding} class defines common constants, which are used across the whole binding.
|
||||
*
|
||||
* @author Antoine Besnard - Initial contribution
|
||||
* @author Leroy Foerster - Listening Mode, Playing Listening Mode
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class PioneerAvrBindingConstants {
|
||||
|
||||
public static final String BINDING_ID = "pioneeravr";
|
||||
|
||||
public static final Set<String> SUPPORTED_DEVICE_MODELS = Collections
|
||||
.unmodifiableSet(Stream.of("SC-57", "SC-LX85", "SC-55", "SC-1526", "SC-LX75", "VSX-53", "VSX-1326",
|
||||
"VSX-LX55", "VSX-2021", "VSA-LX55", "VSX-52", "VSX-1126", "VSX-1121", "VSX-51", "VSX-1021",
|
||||
"VSX-1026", "VSA-1021", "VSX-50", "VSX-926", "VSX-921", "VSA-921").collect(Collectors.toSet()));
|
||||
|
||||
public static final Set<String> SUPPORTED_DEVICE_MODELS2014 = Collections
|
||||
.unmodifiableSet(Stream.of("SC-LX87", "SC-LX77", "SC-LX57", "SC-2023", "SC-1223", "VSX-1123", "VSX-923")
|
||||
.collect(Collectors.toSet()));
|
||||
|
||||
public static final Set<String> SUPPORTED_DEVICE_MODELS2015 = Collections.unmodifiableSet(
|
||||
Stream.of("SC-89", "SC-LX88", "SC-87", "SC-LX78", "SC-85", "SC-LX58", "SC-82", "SC-2024", "SC-81", "VSX-80")
|
||||
.collect(Collectors.toSet()));
|
||||
|
||||
public static final Set<String> SUPPORTED_DEVICE_MODELS2016 = Collections
|
||||
.unmodifiableSet(Stream.of("SC-99", "SC-LX89", "SC-97", "SC-LX79", "SC-95", "SC-LX59", "SC-92", "SC-91",
|
||||
"VSX-90", "VSX-45", "VSX-830", "VSX-930", "VSX-1130").collect(Collectors.toSet()));
|
||||
|
||||
public static final Set<String> SUPPORTED_DEVICE_MODELS2017 = Collections.unmodifiableSet(
|
||||
Stream.of("VSX-531", "VSX-531D", "VSX-831", "VSX-1131", "VSX-LX101", "VSX-LX301", "VSX-LX501")
|
||||
.collect(Collectors.toSet()));
|
||||
|
||||
public static final Set<String> SUPPORTED_DEVICE_MODELS2018 = Collections.unmodifiableSet(Stream
|
||||
.of("VSX-532", "VSX-832", "VSX-932", "VSX-LX102", "VSX-LX302", "VSX-LX502").collect(Collectors.toSet()));
|
||||
|
||||
public static final Set<String> SUPPORTED_DEVICE_MODELS2019 = Collections.unmodifiableSet(
|
||||
Stream.of("VSX-833", "VSX-933", "VSX-LX103", "VSX-LX303", "VSX-LX503").collect(Collectors.toSet()));
|
||||
|
||||
public static final Set<String> SUPPORTED_DEVICE_MODELS2020 = Collections.unmodifiableSet(Stream
|
||||
.of("VSX-534", "VSX-534D", "VSX-934", "VSX-LX104", "VSX-LX304", "VSX-LX504").collect(Collectors.toSet()));
|
||||
|
||||
// List of all Thing Type UIDs
|
||||
public static final ThingTypeUID IP_AVR_THING_TYPE = new ThingTypeUID(BINDING_ID, "ipAvr");
|
||||
public static final ThingTypeUID IP_AVR_THING_TYPE2014 = new ThingTypeUID(BINDING_ID, "ipAvr2014");
|
||||
public static final ThingTypeUID IP_AVR_THING_TYPE2015 = new ThingTypeUID(BINDING_ID, "ipAvr2015");
|
||||
public static final ThingTypeUID IP_AVR_THING_TYPE2016 = new ThingTypeUID(BINDING_ID, "ipAvr2016");
|
||||
public static final ThingTypeUID IP_AVR_THING_TYPE2017 = new ThingTypeUID(BINDING_ID, "ipAvr2017");
|
||||
public static final ThingTypeUID IP_AVR_THING_TYPE2018 = new ThingTypeUID(BINDING_ID, "ipAvr2018");
|
||||
public static final ThingTypeUID IP_AVR_THING_TYPE2019 = new ThingTypeUID(BINDING_ID, "ipAvr2019");
|
||||
public static final ThingTypeUID IP_AVR_THING_TYPE2020 = new ThingTypeUID(BINDING_ID, "ipAvr2020");
|
||||
public static final ThingTypeUID IP_AVR_UNSUPPORTED_THING_TYPE = new ThingTypeUID(BINDING_ID, "ipAvrUnsupported");
|
||||
public static final ThingTypeUID SERIAL_AVR_THING_TYPE = new ThingTypeUID(BINDING_ID, "serialAvr");
|
||||
|
||||
// List of thing parameters names
|
||||
public static final String PROTOCOL_PARAMETER = "protocol";
|
||||
public static final String HOST_PARAMETER = "address";
|
||||
public static final String TCP_PORT_PARAMETER = "tcpPort";
|
||||
public static final String SERIAL_PORT_PARAMETER = "serialPort";
|
||||
|
||||
public static final String IP_PROTOCOL_NAME = "IP";
|
||||
public static final String SERIAL_PROTOCOL_NAME = "serial";
|
||||
|
||||
// List of all Channel names
|
||||
public static final String POWER_CHANNEL = "power";
|
||||
public static final String VOLUME_DIMMER_CHANNEL = "volumeDimmer";
|
||||
public static final String VOLUME_DB_CHANNEL = "volumeDb";
|
||||
public static final String MUTE_CHANNEL = "mute";
|
||||
public static final String SET_INPUT_SOURCE_CHANNEL = "setInputSource";
|
||||
public static final String LISTENING_MODE_CHANNEL = "listeningMode";
|
||||
public static final String PLAYING_LISTENING_MODE_CHANNEL = "playingListeningMode";
|
||||
public static final String DISPLAY_INFORMATION_CHANNEL = "displayInformation#displayInformation";
|
||||
|
||||
public static final String GROUP_CHANNEL_PATTERN = "zone%s#%s";
|
||||
public static final Pattern GROUP_CHANNEL_ZONE_PATTERN = Pattern.compile("zone([0-4])#.*");
|
||||
|
||||
// Used for Discovery service
|
||||
public static final String MANUFACTURER = "PIONEER";
|
||||
public static final String UPNP_DEVICE_TYPE = "MediaRenderer";
|
||||
|
||||
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Stream.of(IP_AVR_THING_TYPE,
|
||||
IP_AVR_THING_TYPE2020, IP_AVR_THING_TYPE2019, IP_AVR_THING_TYPE2018, IP_AVR_THING_TYPE2017,
|
||||
IP_AVR_THING_TYPE2016, IP_AVR_THING_TYPE2015, IP_AVR_THING_TYPE2014, IP_AVR_UNSUPPORTED_THING_TYPE)
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
/**
|
||||
* 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.pioneeravr.internal.discovery;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.jupnp.model.meta.RemoteDevice;
|
||||
import org.openhab.binding.pioneeravr.internal.PioneerAvrBindingConstants;
|
||||
import org.openhab.core.config.discovery.DiscoveryResult;
|
||||
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
|
||||
import org.openhab.core.config.discovery.upnp.UpnpDiscoveryParticipant;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
import org.osgi.service.component.ComponentContext;
|
||||
import org.osgi.service.component.annotations.Activate;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* An UpnpDiscoveryParticipant which allows to discover Pioneer AVRs.
|
||||
*
|
||||
* @author Antoine Besnard - Initial contribution
|
||||
*/
|
||||
@Component(immediate = true)
|
||||
public class PioneerAvrDiscoveryParticipant implements UpnpDiscoveryParticipant {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(PioneerAvrDiscoveryParticipant.class);
|
||||
|
||||
private boolean isAutoDiscoveryEnabled;
|
||||
private Set<ThingTypeUID> supportedThingTypes;
|
||||
|
||||
public PioneerAvrDiscoveryParticipant() {
|
||||
this.isAutoDiscoveryEnabled = true;
|
||||
this.supportedThingTypes = PioneerAvrBindingConstants.SUPPORTED_THING_TYPES_UIDS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called at the service activation.
|
||||
*
|
||||
* @param componentContext
|
||||
*/
|
||||
@Activate
|
||||
protected void activate(ComponentContext componentContext) {
|
||||
if (componentContext.getProperties() != null) {
|
||||
String autoDiscoveryPropertyValue = (String) componentContext.getProperties().get("enableAutoDiscovery");
|
||||
if (StringUtils.isNotEmpty(autoDiscoveryPropertyValue)) {
|
||||
isAutoDiscoveryEnabled = Boolean.valueOf(autoDiscoveryPropertyValue);
|
||||
}
|
||||
}
|
||||
supportedThingTypes = isAutoDiscoveryEnabled ? PioneerAvrBindingConstants.SUPPORTED_THING_TYPES_UIDS
|
||||
: new HashSet<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<ThingTypeUID> getSupportedThingTypeUIDs() {
|
||||
return supportedThingTypes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DiscoveryResult createResult(RemoteDevice device) {
|
||||
DiscoveryResult result = null;
|
||||
ThingUID thingUid = getThingUID(device);
|
||||
if (thingUid != null) {
|
||||
String label = StringUtils.isEmpty(device.getDetails().getFriendlyName()) ? device.getDisplayString()
|
||||
: device.getDetails().getFriendlyName();
|
||||
Map<String, Object> properties = new HashMap<>(2, 1);
|
||||
properties.put(PioneerAvrBindingConstants.HOST_PARAMETER,
|
||||
device.getIdentity().getDescriptorURL().getHost());
|
||||
properties.put(PioneerAvrBindingConstants.PROTOCOL_PARAMETER, PioneerAvrBindingConstants.IP_PROTOCOL_NAME);
|
||||
|
||||
result = DiscoveryResultBuilder.create(thingUid).withLabel(label).withProperties(properties).build();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ThingUID getThingUID(RemoteDevice device) {
|
||||
ThingUID result = null;
|
||||
if (isAutoDiscoveryEnabled) {
|
||||
if (StringUtils.containsIgnoreCase(device.getDetails().getManufacturerDetails().getManufacturer(),
|
||||
PioneerAvrBindingConstants.MANUFACTURER)) {
|
||||
logger.debug("Manufacturer matched: search: {}, device value: {}.",
|
||||
PioneerAvrBindingConstants.MANUFACTURER,
|
||||
device.getDetails().getManufacturerDetails().getManufacturer());
|
||||
if (StringUtils.containsIgnoreCase(device.getType().getType(),
|
||||
PioneerAvrBindingConstants.UPNP_DEVICE_TYPE)) {
|
||||
logger.debug("Device type matched: search: {}, device value: {}.",
|
||||
PioneerAvrBindingConstants.UPNP_DEVICE_TYPE, device.getType().getType());
|
||||
|
||||
String deviceModel = device.getDetails().getModelDetails() != null
|
||||
? device.getDetails().getModelDetails().getModelName()
|
||||
: null;
|
||||
|
||||
ThingTypeUID thingTypeUID = PioneerAvrBindingConstants.IP_AVR_THING_TYPE;
|
||||
|
||||
if (isSupportedDeviceModel(deviceModel, PioneerAvrBindingConstants.SUPPORTED_DEVICE_MODELS2020)) {
|
||||
thingTypeUID = PioneerAvrBindingConstants.IP_AVR_THING_TYPE2020;
|
||||
} else if (isSupportedDeviceModel(deviceModel,
|
||||
PioneerAvrBindingConstants.SUPPORTED_DEVICE_MODELS2019)) {
|
||||
thingTypeUID = PioneerAvrBindingConstants.IP_AVR_THING_TYPE2019;
|
||||
} else if (isSupportedDeviceModel(deviceModel,
|
||||
PioneerAvrBindingConstants.SUPPORTED_DEVICE_MODELS2018)) {
|
||||
thingTypeUID = PioneerAvrBindingConstants.IP_AVR_THING_TYPE2018;
|
||||
} else if (isSupportedDeviceModel(deviceModel,
|
||||
PioneerAvrBindingConstants.SUPPORTED_DEVICE_MODELS2017)) {
|
||||
thingTypeUID = PioneerAvrBindingConstants.IP_AVR_THING_TYPE2017;
|
||||
} else if (isSupportedDeviceModel(deviceModel,
|
||||
PioneerAvrBindingConstants.SUPPORTED_DEVICE_MODELS2016)) {
|
||||
thingTypeUID = PioneerAvrBindingConstants.IP_AVR_THING_TYPE2016;
|
||||
} else if (isSupportedDeviceModel(deviceModel,
|
||||
PioneerAvrBindingConstants.SUPPORTED_DEVICE_MODELS2015)) {
|
||||
thingTypeUID = PioneerAvrBindingConstants.IP_AVR_THING_TYPE2015;
|
||||
} else if (isSupportedDeviceModel(deviceModel,
|
||||
PioneerAvrBindingConstants.SUPPORTED_DEVICE_MODELS2014)) {
|
||||
thingTypeUID = PioneerAvrBindingConstants.IP_AVR_THING_TYPE2014;
|
||||
} else if (!isSupportedDeviceModel(deviceModel,
|
||||
PioneerAvrBindingConstants.SUPPORTED_DEVICE_MODELS)) {
|
||||
logger.debug("Device model {} not supported. Odd behaviors may happen.", deviceModel);
|
||||
thingTypeUID = PioneerAvrBindingConstants.IP_AVR_UNSUPPORTED_THING_TYPE;
|
||||
}
|
||||
|
||||
result = new ThingUID(thingTypeUID, device.getIdentity().getUdn().getIdentifierString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true only if the given device model is supported.
|
||||
*
|
||||
* @param deviceModel
|
||||
* @return
|
||||
*/
|
||||
private boolean isSupportedDeviceModel(String deviceModel, Set<String> supportedDeviceModels) {
|
||||
return StringUtils.isNotBlank(deviceModel) && supportedDeviceModels.stream()
|
||||
.anyMatch(input -> StringUtils.startsWithIgnoreCase(deviceModel, input));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,384 @@
|
||||
/**
|
||||
* 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.pioneeravr.internal.handler;
|
||||
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.regex.Matcher;
|
||||
|
||||
import org.openhab.binding.pioneeravr.internal.PioneerAvrBindingConstants;
|
||||
import org.openhab.binding.pioneeravr.internal.protocol.RequestResponseFactory;
|
||||
import org.openhab.binding.pioneeravr.internal.protocol.avr.AvrConnection;
|
||||
import org.openhab.binding.pioneeravr.internal.protocol.avr.AvrConnectionException;
|
||||
import org.openhab.binding.pioneeravr.internal.protocol.avr.AvrResponse;
|
||||
import org.openhab.binding.pioneeravr.internal.protocol.avr.CommandTypeNotSupportedException;
|
||||
import org.openhab.binding.pioneeravr.internal.protocol.event.AvrDisconnectionEvent;
|
||||
import org.openhab.binding.pioneeravr.internal.protocol.event.AvrDisconnectionListener;
|
||||
import org.openhab.binding.pioneeravr.internal.protocol.event.AvrStatusUpdateEvent;
|
||||
import org.openhab.binding.pioneeravr.internal.protocol.event.AvrUpdateListener;
|
||||
import org.openhab.binding.pioneeravr.internal.protocol.states.MuteStateValues;
|
||||
import org.openhab.binding.pioneeravr.internal.protocol.states.PowerStateValues;
|
||||
import org.openhab.binding.pioneeravr.internal.protocol.utils.DisplayInformationConverter;
|
||||
import org.openhab.binding.pioneeravr.internal.protocol.utils.VolumeConverter;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.PercentType;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
import org.openhab.core.thing.binding.BaseThingHandler;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.RefreshType;
|
||||
import org.openhab.core.types.UnDefType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link AbstractAvrHandler} is responsible for handling commands, which are sent to one of the channels through an
|
||||
* AVR connection.
|
||||
*
|
||||
* @author Antoine Besnard - Initial contribution
|
||||
* @author Leroy Foerster - Listening Mode, Playing Listening Mode
|
||||
*/
|
||||
public abstract class AbstractAvrHandler extends BaseThingHandler
|
||||
implements AvrUpdateListener, AvrDisconnectionListener {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(AbstractAvrHandler.class);
|
||||
|
||||
private AvrConnection connection;
|
||||
private ScheduledFuture<?> statusCheckerFuture;
|
||||
|
||||
public AbstractAvrHandler(Thing thing) {
|
||||
super(thing);
|
||||
this.connection = createConnection();
|
||||
|
||||
this.connection.addUpdateListener(this);
|
||||
this.connection.addDisconnectionListener(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new connection to the AVR.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
protected abstract AvrConnection createConnection();
|
||||
|
||||
/**
|
||||
* Initialize the state of the AVR.
|
||||
*/
|
||||
@Override
|
||||
public void initialize() {
|
||||
logger.debug("Initializing handler for Pioneer AVR @{}", connection.getConnectionName());
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
|
||||
// Start the status checker
|
||||
Runnable statusChecker = () -> {
|
||||
try {
|
||||
logger.debug("Checking status of AVR @{}", connection.getConnectionName());
|
||||
checkStatus();
|
||||
} catch (LinkageError e) {
|
||||
logger.warn(
|
||||
"Failed to check the status for AVR @{}. If a Serial link is used to connect to the AVR, please check that the Bundle org.openhab.io.transport.serial is available. Cause: {}",
|
||||
connection.getConnectionName(), e.getMessage());
|
||||
// Stop to check the status of this AVR.
|
||||
if (statusCheckerFuture != null) {
|
||||
statusCheckerFuture.cancel(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
statusCheckerFuture = scheduler.scheduleWithFixedDelay(statusChecker, 1, 10, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the connection and stop the status checker.
|
||||
*/
|
||||
@Override
|
||||
public void dispose() {
|
||||
super.dispose();
|
||||
if (statusCheckerFuture != null) {
|
||||
statusCheckerFuture.cancel(true);
|
||||
}
|
||||
if (connection != null) {
|
||||
connection.close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a Power ON state update is received from the AVR for the given zone.
|
||||
*/
|
||||
public void onPowerOn(int zone) {
|
||||
// When the AVR is Powered ON, query the volume, the mute state and the source input of the zone
|
||||
connection.sendVolumeQuery(zone);
|
||||
connection.sendMuteQuery(zone);
|
||||
connection.sendInputSourceQuery(zone);
|
||||
connection.sendListeningModeQuery(zone);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a Power OFF state update is received from the AVR.
|
||||
*/
|
||||
public void onPowerOff(int zone) {
|
||||
// When the AVR is Powered OFF, update the status of channels to Undefined
|
||||
updateState(getChannelUID(PioneerAvrBindingConstants.MUTE_CHANNEL, zone), UnDefType.UNDEF);
|
||||
updateState(getChannelUID(PioneerAvrBindingConstants.VOLUME_DB_CHANNEL, zone), UnDefType.UNDEF);
|
||||
updateState(getChannelUID(PioneerAvrBindingConstants.VOLUME_DIMMER_CHANNEL, zone), UnDefType.UNDEF);
|
||||
updateState(getChannelUID(PioneerAvrBindingConstants.SET_INPUT_SOURCE_CHANNEL, zone), UnDefType.UNDEF);
|
||||
updateState(getChannelUID(PioneerAvrBindingConstants.LISTENING_MODE_CHANNEL, zone), UnDefType.UNDEF);
|
||||
updateState(getChannelUID(PioneerAvrBindingConstants.PLAYING_LISTENING_MODE_CHANNEL, zone), UnDefType.UNDEF);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the status of the AVR. Return true if the AVR is online, else return false.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
private void checkStatus() {
|
||||
// If the power query request has not been sent, the connection to the
|
||||
// AVR has failed. So update its status to OFFLINE.
|
||||
if (!connection.sendPowerQuery(1)) {
|
||||
updateStatus(ThingStatus.OFFLINE);
|
||||
} else {
|
||||
// If the power query has succeeded, the AVR status is ONLINE.
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
// Then send a power query for zone 2 and 3
|
||||
connection.sendPowerQuery(2);
|
||||
connection.sendPowerQuery(3);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a command to the AVR based on the openHAB command received.
|
||||
*/
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
try {
|
||||
boolean commandSent = false;
|
||||
boolean unknownCommand = false;
|
||||
|
||||
if (channelUID.getId().contains(PioneerAvrBindingConstants.POWER_CHANNEL)) {
|
||||
if (command == RefreshType.REFRESH) {
|
||||
commandSent = connection.sendPowerQuery(getZoneFromChannelUID(channelUID.getId()));
|
||||
} else {
|
||||
commandSent = connection.sendPowerCommand(command, getZoneFromChannelUID(channelUID.getId()));
|
||||
}
|
||||
} else if (channelUID.getId().contains(PioneerAvrBindingConstants.VOLUME_DIMMER_CHANNEL)
|
||||
|| channelUID.getId().contains(PioneerAvrBindingConstants.VOLUME_DB_CHANNEL)) {
|
||||
if (command == RefreshType.REFRESH) {
|
||||
commandSent = connection.sendVolumeQuery(getZoneFromChannelUID(channelUID.getId()));
|
||||
} else {
|
||||
commandSent = connection.sendVolumeCommand(command, getZoneFromChannelUID(channelUID.getId()));
|
||||
}
|
||||
} else if (channelUID.getId().contains(PioneerAvrBindingConstants.SET_INPUT_SOURCE_CHANNEL)) {
|
||||
if (command == RefreshType.REFRESH) {
|
||||
commandSent = connection.sendInputSourceQuery(getZoneFromChannelUID(channelUID.getId()));
|
||||
} else {
|
||||
commandSent = connection.sendInputSourceCommand(command, getZoneFromChannelUID(channelUID.getId()));
|
||||
}
|
||||
} else if (channelUID.getId().contains(PioneerAvrBindingConstants.LISTENING_MODE_CHANNEL)) {
|
||||
if (command == RefreshType.REFRESH) {
|
||||
commandSent = connection.sendListeningModeQuery(getZoneFromChannelUID(channelUID.getId()));
|
||||
} else {
|
||||
commandSent = connection.sendListeningModeCommand(command,
|
||||
getZoneFromChannelUID(channelUID.getId()));
|
||||
}
|
||||
} else if (channelUID.getId().contains(PioneerAvrBindingConstants.MUTE_CHANNEL)) {
|
||||
if (command == RefreshType.REFRESH) {
|
||||
commandSent = connection.sendMuteQuery(getZoneFromChannelUID(channelUID.getId()));
|
||||
} else {
|
||||
commandSent = connection.sendMuteCommand(command, getZoneFromChannelUID(channelUID.getId()));
|
||||
}
|
||||
} else {
|
||||
unknownCommand = true;
|
||||
}
|
||||
|
||||
// If the command is not unknown and has not been sent, the AVR is Offline
|
||||
if (!commandSent && !unknownCommand) {
|
||||
onDisconnection();
|
||||
}
|
||||
} catch (CommandTypeNotSupportedException e) {
|
||||
logger.warn("Unsupported command type received for channel {}.", channelUID.getId());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a status update is received from the AVR.
|
||||
*/
|
||||
@Override
|
||||
public void statusUpdateReceived(AvrStatusUpdateEvent event) {
|
||||
try {
|
||||
AvrResponse response = RequestResponseFactory.getIpControlResponse(event.getData());
|
||||
|
||||
switch (response.getResponseType()) {
|
||||
case POWER_STATE:
|
||||
managePowerStateUpdate(response);
|
||||
break;
|
||||
|
||||
case VOLUME_LEVEL:
|
||||
manageVolumeLevelUpdate(response);
|
||||
break;
|
||||
|
||||
case MUTE_STATE:
|
||||
manageMuteStateUpdate(response);
|
||||
break;
|
||||
|
||||
case INPUT_SOURCE_CHANNEL:
|
||||
manageInputSourceChannelUpdate(response);
|
||||
break;
|
||||
|
||||
case LISTENING_MODE:
|
||||
manageListeningModeUpdate(response);
|
||||
break;
|
||||
|
||||
case PLAYING_LISTENING_MODE:
|
||||
managePlayingListeningModeUpdate(response);
|
||||
break;
|
||||
|
||||
case DISPLAY_INFORMATION:
|
||||
manageDisplayedInformationUpdate(response);
|
||||
break;
|
||||
|
||||
default:
|
||||
logger.debug("Unknown response type from AVR @{}. Response discarded: {}", event.getData(),
|
||||
event.getConnection());
|
||||
}
|
||||
} catch (AvrConnectionException e) {
|
||||
logger.debug("Unknown response type from AVR @{}. Response discarded: {}", event.getData(),
|
||||
event.getConnection());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the AVR is disconnected
|
||||
*/
|
||||
@Override
|
||||
public void onDisconnection(AvrDisconnectionEvent event) {
|
||||
onDisconnection();
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the AVR disconnection.
|
||||
*/
|
||||
private void onDisconnection() {
|
||||
updateStatus(ThingStatus.OFFLINE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify an AVR power state update to openHAB
|
||||
*
|
||||
* @param response
|
||||
*/
|
||||
private void managePowerStateUpdate(AvrResponse response) {
|
||||
OnOffType state = PowerStateValues.ON_VALUE.equals(response.getParameterValue()) ? OnOffType.ON : OnOffType.OFF;
|
||||
|
||||
// When a Power ON state update is received, call the onPowerOn method.
|
||||
if (OnOffType.ON == state) {
|
||||
onPowerOn(response.getZone());
|
||||
} else {
|
||||
onPowerOff(response.getZone());
|
||||
}
|
||||
|
||||
updateState(getChannelUID(PioneerAvrBindingConstants.POWER_CHANNEL, response.getZone()), state);
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify an AVR volume level update to openHAB
|
||||
*
|
||||
* @param response
|
||||
*/
|
||||
private void manageVolumeLevelUpdate(AvrResponse response) {
|
||||
updateState(getChannelUID(PioneerAvrBindingConstants.VOLUME_DB_CHANNEL, response.getZone()), new DecimalType(
|
||||
VolumeConverter.convertFromIpControlVolumeToDb(response.getParameterValue(), response.getZone())));
|
||||
updateState(getChannelUID(PioneerAvrBindingConstants.VOLUME_DIMMER_CHANNEL, response.getZone()),
|
||||
new PercentType((int) VolumeConverter.convertFromIpControlVolumeToPercent(response.getParameterValue(),
|
||||
response.getZone())));
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify an AVR mute state update to openHAB
|
||||
*
|
||||
* @param response
|
||||
*/
|
||||
private void manageMuteStateUpdate(AvrResponse response) {
|
||||
updateState(getChannelUID(PioneerAvrBindingConstants.MUTE_CHANNEL, response.getZone()),
|
||||
response.getParameterValue().equals(MuteStateValues.OFF_VALUE) ? OnOffType.OFF : OnOffType.ON);
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify an AVR input source channel update to openHAB
|
||||
*
|
||||
* @param response
|
||||
*/
|
||||
private void manageInputSourceChannelUpdate(AvrResponse response) {
|
||||
updateState(getChannelUID(PioneerAvrBindingConstants.SET_INPUT_SOURCE_CHANNEL, response.getZone()),
|
||||
new StringType(response.getParameterValue()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify an AVR now-playing, in-effect listening mode (audio output format) update to openHAB
|
||||
*
|
||||
* @param response
|
||||
*/
|
||||
private void managePlayingListeningModeUpdate(AvrResponse response) {
|
||||
updateState(getChannelUID(PioneerAvrBindingConstants.PLAYING_LISTENING_MODE_CHANNEL, response.getZone()),
|
||||
new StringType(response.getParameterValue()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify an AVR set listening mode (user-selected audio mode) update to openHAB
|
||||
*
|
||||
* @param response
|
||||
*/
|
||||
private void manageListeningModeUpdate(AvrResponse response) {
|
||||
updateState(getChannelUID(PioneerAvrBindingConstants.LISTENING_MODE_CHANNEL, response.getZone()),
|
||||
new StringType(response.getParameterValue()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify an AVR displayed information update to openHAB
|
||||
*
|
||||
* @param response
|
||||
*/
|
||||
private void manageDisplayedInformationUpdate(AvrResponse response) {
|
||||
updateState(PioneerAvrBindingConstants.DISPLAY_INFORMATION_CHANNEL,
|
||||
new StringType(DisplayInformationConverter.convertMessageFromIpControl(response.getParameterValue())));
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the channelUID from the channel name and the zone number.
|
||||
*
|
||||
* @param channelName
|
||||
* @param zone
|
||||
* @return
|
||||
*/
|
||||
protected String getChannelUID(String channelName, int zone) {
|
||||
return String.format(PioneerAvrBindingConstants.GROUP_CHANNEL_PATTERN, zone, channelName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the zone from the given channelUID.
|
||||
*
|
||||
* Return 0 if the zone cannot be extracted from the channelUID.
|
||||
*
|
||||
* @param channelUID
|
||||
* @return
|
||||
*/
|
||||
protected int getZoneFromChannelUID(String channelUID) {
|
||||
int zone = 0;
|
||||
Matcher matcher = PioneerAvrBindingConstants.GROUP_CHANNEL_ZONE_PATTERN.matcher(channelUID);
|
||||
if (matcher.find()) {
|
||||
zone = Integer.valueOf(matcher.group(1));
|
||||
}
|
||||
return zone;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
/**
|
||||
* 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.pioneeravr.internal.handler;
|
||||
|
||||
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.pioneeravr.internal.PioneerAvrBindingConstants;
|
||||
import org.openhab.core.io.transport.serial.SerialPortManager;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
|
||||
import org.openhab.core.thing.binding.ThingHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandlerFactory;
|
||||
import org.osgi.service.component.ComponentContext;
|
||||
import org.osgi.service.component.annotations.Activate;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.osgi.service.component.annotations.Reference;
|
||||
|
||||
/**
|
||||
* The {@link AvrHandlerFactory} is responsible for creating things and thing handlers.
|
||||
*
|
||||
* @author Antoine Besnard - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@Component(service = ThingHandlerFactory.class, configurationPid = "binding.pioneeravr")
|
||||
public class AvrHandlerFactory extends BaseThingHandlerFactory {
|
||||
|
||||
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Collections.unmodifiableSet(
|
||||
Stream.of(PioneerAvrBindingConstants.IP_AVR_THING_TYPE, PioneerAvrBindingConstants.IP_AVR_THING_TYPE2014,
|
||||
PioneerAvrBindingConstants.IP_AVR_THING_TYPE2015, PioneerAvrBindingConstants.IP_AVR_THING_TYPE2016,
|
||||
PioneerAvrBindingConstants.IP_AVR_THING_TYPE2017, PioneerAvrBindingConstants.IP_AVR_THING_TYPE2018,
|
||||
PioneerAvrBindingConstants.IP_AVR_THING_TYPE2019, PioneerAvrBindingConstants.IP_AVR_THING_TYPE2020,
|
||||
PioneerAvrBindingConstants.IP_AVR_UNSUPPORTED_THING_TYPE,
|
||||
PioneerAvrBindingConstants.SERIAL_AVR_THING_TYPE).collect(Collectors.toSet()));
|
||||
|
||||
private SerialPortManager serialPortManager;
|
||||
|
||||
@Activate
|
||||
public AvrHandlerFactory(ComponentContext componentContext, final @Reference SerialPortManager serialPortManager) {
|
||||
super.activate(componentContext);
|
||||
this.serialPortManager = serialPortManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
|
||||
return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @Nullable ThingHandler createHandler(Thing thing) {
|
||||
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
|
||||
|
||||
if (thingTypeUID.equals(PioneerAvrBindingConstants.IP_AVR_THING_TYPE)
|
||||
|| thingTypeUID.equals(PioneerAvrBindingConstants.IP_AVR_THING_TYPE2014)
|
||||
|| thingTypeUID.equals(PioneerAvrBindingConstants.IP_AVR_THING_TYPE2015)
|
||||
|| thingTypeUID.equals(PioneerAvrBindingConstants.IP_AVR_THING_TYPE2016)
|
||||
|| thingTypeUID.equals(PioneerAvrBindingConstants.IP_AVR_THING_TYPE2017)
|
||||
|| thingTypeUID.equals(PioneerAvrBindingConstants.IP_AVR_THING_TYPE2018)
|
||||
|| thingTypeUID.equals(PioneerAvrBindingConstants.IP_AVR_THING_TYPE2019)
|
||||
|| thingTypeUID.equals(PioneerAvrBindingConstants.IP_AVR_THING_TYPE2020)
|
||||
|| thingTypeUID.equals(PioneerAvrBindingConstants.IP_AVR_UNSUPPORTED_THING_TYPE)) {
|
||||
return new IpAvrHandler(thing);
|
||||
} else if (thingTypeUID.equals(PioneerAvrBindingConstants.SERIAL_AVR_THING_TYPE)) {
|
||||
return new SerialAvrHandler(thing, serialPortManager);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* 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.pioneeravr.internal.handler;
|
||||
|
||||
import org.openhab.binding.pioneeravr.internal.PioneerAvrBindingConstants;
|
||||
import org.openhab.binding.pioneeravr.internal.protocol.avr.AvrConnection;
|
||||
import org.openhab.binding.pioneeravr.internal.protocol.ip.IpAvrConnection;
|
||||
import org.openhab.core.thing.Thing;
|
||||
|
||||
/**
|
||||
* An handler of an AVR connected through an IP connection.
|
||||
*
|
||||
* @author Antoine Besnard - Initial contribution
|
||||
*/
|
||||
public class IpAvrHandler extends AbstractAvrHandler {
|
||||
|
||||
public IpAvrHandler(Thing thing) {
|
||||
super(thing);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected AvrConnection createConnection() {
|
||||
String host = (String) this.getConfig().get(PioneerAvrBindingConstants.HOST_PARAMETER);
|
||||
Integer tcpPort = ((Number) this.getConfig().get(PioneerAvrBindingConstants.TCP_PORT_PARAMETER)).intValue();
|
||||
|
||||
return new IpAvrConnection(host, tcpPort);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* 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.pioneeravr.internal.handler;
|
||||
|
||||
import org.openhab.binding.pioneeravr.internal.PioneerAvrBindingConstants;
|
||||
import org.openhab.binding.pioneeravr.internal.protocol.avr.AvrConnection;
|
||||
import org.openhab.binding.pioneeravr.internal.protocol.serial.SerialAvrConnection;
|
||||
import org.openhab.core.io.transport.serial.SerialPortManager;
|
||||
import org.openhab.core.thing.Thing;
|
||||
|
||||
/**
|
||||
* An handler of an AVR connected through a serial port.
|
||||
*
|
||||
* @author Antoine Besnard - Initial contribution
|
||||
*/
|
||||
public class SerialAvrHandler extends AbstractAvrHandler {
|
||||
|
||||
private SerialPortManager serialPortManager;
|
||||
|
||||
public SerialAvrHandler(Thing thing, SerialPortManager serialPortManager) {
|
||||
super(thing);
|
||||
this.serialPortManager = serialPortManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected AvrConnection createConnection() {
|
||||
String serialPort = (String) this.getConfig().get(PioneerAvrBindingConstants.SERIAL_PORT_PARAMETER);
|
||||
|
||||
return new SerialAvrConnection(serialPort, serialPortManager);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
/**
|
||||
* 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.pioneeravr.internal.protocol;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Represents the display status message send by the Pioneer AV receiver
|
||||
* (response to "?FL" request)
|
||||
*
|
||||
* @author Rainer Ostendorf - Initial contribution
|
||||
*/
|
||||
public class DisplayInformation {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(DisplayInformation.class);
|
||||
|
||||
private Boolean volumeDisplay; // 1-light, 0-OFF
|
||||
private Boolean guidIcon; // 1-light, 0-OFF
|
||||
private String infoText = ""; // the actual display text
|
||||
|
||||
/**
|
||||
* parse the display status text send from the receiver
|
||||
*
|
||||
* @param responsePayload the responses payload, that is without the leading "FL"
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public DisplayInformation(String responsePayload) {
|
||||
volumeDisplay = false;
|
||||
guidIcon = false;
|
||||
infoText = "";
|
||||
|
||||
// Example from Pioneer docs: When " [)(]DIGITAL EX " is displayed, response command is:
|
||||
// FL000005064449474954414C00455800<CR+LF>
|
||||
|
||||
// first byte holds the two special flags
|
||||
byte firstByte = (byte) Integer.parseInt(responsePayload.substring(0, 1), 16);
|
||||
|
||||
if ((firstByte & (1 << 0)) == (1 << 0)) {
|
||||
guidIcon = true;
|
||||
}
|
||||
if ((firstByte & (1 << 1)) == (1 << 1)) {
|
||||
volumeDisplay = true;
|
||||
}
|
||||
|
||||
// convert the ascii values back to string
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 2; i < responsePayload.length() - 1; i += 2) {
|
||||
String hexAsciiValue = responsePayload.substring(i, i + 2);
|
||||
try {
|
||||
sb.append((char) Integer.parseInt(hexAsciiValue, 16));
|
||||
} catch (Exception e) {
|
||||
logger.error("parsing string failed '{}'", responsePayload, e);
|
||||
}
|
||||
}
|
||||
infoText = sb.toString();
|
||||
}
|
||||
|
||||
public String getInfoText() {
|
||||
return infoText;
|
||||
}
|
||||
|
||||
public void setInfoText(String infoText) {
|
||||
this.infoText = infoText;
|
||||
}
|
||||
|
||||
public Boolean getVolumeDisplay() {
|
||||
return volumeDisplay;
|
||||
}
|
||||
|
||||
public Boolean getGuidIcon() {
|
||||
return guidIcon;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
/**
|
||||
* 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.pioneeravr.internal.protocol;
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.openhab.binding.pioneeravr.internal.protocol.avr.AvrCommand;
|
||||
import org.openhab.binding.pioneeravr.internal.protocol.avr.AvrConnectionException;
|
||||
|
||||
/**
|
||||
* A command which accept a parameter.
|
||||
*
|
||||
* @author Antoine Besnard - Initial contribution
|
||||
* @author Leroy Foerster - Listening Mode, Playing Listening Mode
|
||||
*/
|
||||
public class ParameterizedCommand extends SimpleCommand {
|
||||
|
||||
/**
|
||||
* List of the commands with a parameter.
|
||||
*/
|
||||
public enum ParameterizedCommandType implements AvrCommand.CommandType {
|
||||
|
||||
VOLUME_SET("[0-9]{2,3}", "VL", "ZV", "YV", "HZV"),
|
||||
INPUT_CHANNEL_SET("[0-9]{2}", "FN", "ZS", "ZT", "ZEA"),
|
||||
LISTENING_MODE_SET("[0-9]{4}", "SR");
|
||||
|
||||
private String[] zoneCommands;
|
||||
private String parameterPattern;
|
||||
|
||||
private ParameterizedCommandType(String parameterPattern, String... zoneCommands) {
|
||||
this.zoneCommands = zoneCommands;
|
||||
this.parameterPattern = parameterPattern;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCommand(int zone) {
|
||||
return zoneCommands[zone - 1];
|
||||
}
|
||||
|
||||
public String getParameterPattern() {
|
||||
return parameterPattern;
|
||||
}
|
||||
}
|
||||
|
||||
private String parameter;
|
||||
|
||||
private String parameterPattern;
|
||||
|
||||
protected ParameterizedCommand(ParameterizedCommandType command, int zone) {
|
||||
super(command, zone);
|
||||
this.parameterPattern = command.getParameterPattern();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the command to send to the AVR with the parameter value configured.
|
||||
*
|
||||
* throws {@link AvrConnectionException} if the parameter is null, empty or has a bad format.
|
||||
*/
|
||||
@Override
|
||||
public String getCommand() throws AvrConnectionException {
|
||||
if (parameter == null) {
|
||||
throw new AvrConnectionException(
|
||||
"The parameter of the command " + super.getCommand() + " must not be null.");
|
||||
}
|
||||
|
||||
if (StringUtils.isNotEmpty(parameterPattern) && !parameter.matches(parameterPattern)) {
|
||||
throw new AvrConnectionException("The parameter value " + parameter + " of the command "
|
||||
+ super.getCommand() + " does not match the pattern " + parameterPattern);
|
||||
}
|
||||
|
||||
return parameter + super.getCommand();
|
||||
}
|
||||
|
||||
public ParameterizedCommand setParameter(String parameter) {
|
||||
this.parameter = parameter;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getParameter() {
|
||||
return this.parameter;
|
||||
}
|
||||
|
||||
public String getParameterPattern() {
|
||||
return parameterPattern;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
/**
|
||||
* 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.pioneeravr.internal.protocol;
|
||||
|
||||
import org.openhab.binding.pioneeravr.internal.protocol.ParameterizedCommand.ParameterizedCommandType;
|
||||
import org.openhab.binding.pioneeravr.internal.protocol.SimpleCommand.SimpleCommandType;
|
||||
import org.openhab.binding.pioneeravr.internal.protocol.ip.IpAvrConnection;
|
||||
|
||||
/**
|
||||
* Factory that allows to build IpControl commands/responses.
|
||||
*
|
||||
* @author Antoine Besnard - Initial contribution
|
||||
*/
|
||||
public final class RequestResponseFactory {
|
||||
|
||||
/**
|
||||
* Return a connection to the AVR with the given host and port.
|
||||
*
|
||||
* @param host
|
||||
* @param port
|
||||
* @return
|
||||
*/
|
||||
public static IpAvrConnection getConnection(String host, Integer port) {
|
||||
return new IpAvrConnection(host, port);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a ParameterizedCommand of the type given in parameter and for the given zone.
|
||||
*
|
||||
* @param command
|
||||
* @param zone
|
||||
* @return
|
||||
*/
|
||||
public static SimpleCommand getIpControlCommand(SimpleCommandType command, int zone) {
|
||||
SimpleCommand result = new SimpleCommand(command, zone);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a ParameterizedCommand of the type given in parameter. The
|
||||
* parameter of the command has to be set before send.
|
||||
*
|
||||
* @param command
|
||||
* @param zone
|
||||
* @return
|
||||
*/
|
||||
public static ParameterizedCommand getIpControlCommand(ParameterizedCommandType command, int zone) {
|
||||
ParameterizedCommand result = new ParameterizedCommand(command, zone);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a ParameterizedCommand of the type given in parameter. The
|
||||
* parameter of the command is set with the given paramter value.
|
||||
*
|
||||
* @param command
|
||||
* @param parameter
|
||||
* @param zone
|
||||
* @return
|
||||
*/
|
||||
public static ParameterizedCommand getIpControlCommand(ParameterizedCommandType command, String parameter,
|
||||
int zone) {
|
||||
ParameterizedCommand result = getIpControlCommand(command, zone);
|
||||
result.setParameter(parameter);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a IpControlResponse object based on the given response data.
|
||||
*
|
||||
* @param responseData
|
||||
* @return
|
||||
*/
|
||||
public static Response getIpControlResponse(String responseData) {
|
||||
return new Response(responseData);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
/**
|
||||
* 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.pioneeravr.internal.protocol;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.openhab.binding.pioneeravr.internal.protocol.avr.AvrConnectionException;
|
||||
import org.openhab.binding.pioneeravr.internal.protocol.avr.AvrResponse;
|
||||
|
||||
/**
|
||||
* Represent an AVR response.
|
||||
*
|
||||
* @author Antoine Besnard - Initial contribution
|
||||
* @author Leroy Foerster - Listening Mode, Playing Listening Mode
|
||||
*/
|
||||
public class Response implements AvrResponse {
|
||||
|
||||
/**
|
||||
* List of all supported responses coming from AVR.
|
||||
*/
|
||||
public enum ResponseType implements AvrResponse.AvrResponseType {
|
||||
POWER_STATE("[0-2]", "PWR", "APR", "BPR", "ZEP"),
|
||||
VOLUME_LEVEL("[0-9]{2,3}", "VOL", "ZV", "YV", "HZV"),
|
||||
MUTE_STATE("[0-1]", "MUT", "Z2MUT", "Z3MUT", "HZM"),
|
||||
INPUT_SOURCE_CHANNEL("[0-9]{2}", "FN", "Z2F", "Z3F", "ZEA"),
|
||||
LISTENING_MODE("[0-9]{4}", "SR"),
|
||||
PLAYING_LISTENING_MODE("[0-9a-f]{4}", "LM"),
|
||||
DISPLAY_INFORMATION("[0-9a-fA-F]{30}", "FL");
|
||||
|
||||
private String[] responsePrefixZone;
|
||||
|
||||
private String parameterPattern;
|
||||
|
||||
private Pattern[] matchPatternZone;
|
||||
|
||||
private ResponseType(String parameterPattern, String... responsePrefixZone) {
|
||||
this.responsePrefixZone = responsePrefixZone;
|
||||
this.parameterPattern = parameterPattern;
|
||||
|
||||
matchPatternZone = new Pattern[responsePrefixZone.length];
|
||||
|
||||
for (int zoneIndex = 0; zoneIndex < responsePrefixZone.length; zoneIndex++) {
|
||||
String responsePrefix = responsePrefixZone[zoneIndex];
|
||||
matchPatternZone[zoneIndex] = Pattern.compile(responsePrefix + "("
|
||||
+ (StringUtils.isNotEmpty(parameterPattern) ? parameterPattern : "") + ")");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getResponsePrefix(int zone) {
|
||||
return responsePrefixZone[zone - 1];
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasParameter() {
|
||||
return StringUtils.isNotEmpty(parameterPattern);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getParameterPattern() {
|
||||
return parameterPattern;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer match(String responseData) {
|
||||
Integer zone = null;
|
||||
// Check the response data against all zone prefixes.
|
||||
for (int zoneIndex = 0; zoneIndex < matchPatternZone.length; zoneIndex++) {
|
||||
if (matchPatternZone[zoneIndex].matcher(responseData).matches()) {
|
||||
zone = zoneIndex + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return zone;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the parameter value of the given responseData.
|
||||
*
|
||||
* @param responseData
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public String parseParameter(String responseData) {
|
||||
String result = null;
|
||||
// Check the response data against all zone prefixes.
|
||||
for (int zoneIndex = 0; zoneIndex < matchPatternZone.length; zoneIndex++) {
|
||||
Matcher matcher = matchPatternZone[zoneIndex].matcher(responseData);
|
||||
if (matcher.find()) {
|
||||
result = matcher.group(1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
private ResponseType responseType;
|
||||
|
||||
private Integer zone;
|
||||
|
||||
private String parameter;
|
||||
|
||||
public Response(String responseData) throws AvrConnectionException {
|
||||
if (StringUtils.isEmpty(responseData)) {
|
||||
throw new AvrConnectionException("responseData is empty. Cannot parse the response.");
|
||||
}
|
||||
|
||||
parseResponseType(responseData);
|
||||
|
||||
if (this.responseType == null) {
|
||||
throw new AvrConnectionException("Cannot find the responseType of the responseData " + responseData);
|
||||
}
|
||||
|
||||
if (this.responseType.hasParameter()) {
|
||||
this.parameter = this.responseType.parseParameter(responseData);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the given response data and fill the
|
||||
*
|
||||
* @param responseData
|
||||
* @return
|
||||
*/
|
||||
private void parseResponseType(String responseData) {
|
||||
for (ResponseType responseType : ResponseType.values()) {
|
||||
zone = responseType.match(responseData);
|
||||
if (zone != null) {
|
||||
this.responseType = responseType;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResponseType getResponseType() {
|
||||
return this.responseType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getParameterValue() {
|
||||
return parameter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasParameter() {
|
||||
return responseType.hasParameter();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer getZone() {
|
||||
return this.zone;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
/**
|
||||
* 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.pioneeravr.internal.protocol;
|
||||
|
||||
import org.openhab.binding.pioneeravr.internal.protocol.avr.AvrCommand;
|
||||
|
||||
/**
|
||||
* A simple command without parameters.
|
||||
*
|
||||
* @author Antoine Besnard - Initial contribution
|
||||
* @author Leroy Foerster - Listening Mode, Playing Listening Mode
|
||||
*/
|
||||
public class SimpleCommand implements AvrCommand {
|
||||
|
||||
/**
|
||||
* List of the simple command types.
|
||||
*/
|
||||
public enum SimpleCommandType implements AvrCommand.CommandType {
|
||||
|
||||
POWER_ON("PO", "APO", "BPO", "ZEO"),
|
||||
POWER_OFF("PF", "APF", "BPF", "ZEF"),
|
||||
POWER_QUERY("?P", "?AP", "?BP", "?ZEP"),
|
||||
VOLUME_UP("VU", "ZU", "YU", "HZU"),
|
||||
VOLUME_DOWN("VD", "ZD", "YD", "HZD"),
|
||||
VOLUME_QUERY("?V", "?ZV", "?YV", "?HZV"),
|
||||
MUTE_ON("MO", "Z2MO", "Z3MO", "HZMO"),
|
||||
MUTE_OFF("MF", "Z2MF", "Z3MF", "HZMF"),
|
||||
MUTE_QUERY("?M", "?Z2M", "?Z3M", "?HZM"),
|
||||
INPUT_CHANGE_CYCLIC("FU"),
|
||||
INPUT_CHANGE_REVERSE("FD"),
|
||||
LISTENING_MODE_CHANGE_CYCLIC("0010SR"),
|
||||
LISTENING_MODE_QUERY("?S"),
|
||||
INPUT_QUERY("?F", "?ZS", "?ZT", "?ZEA");
|
||||
|
||||
private String zoneCommands[];
|
||||
|
||||
private SimpleCommandType(String... command) {
|
||||
this.zoneCommands = command;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCommand(int zone) {
|
||||
return zoneCommands[zone - 1];
|
||||
}
|
||||
}
|
||||
|
||||
private CommandType commandType;
|
||||
private int zone;
|
||||
|
||||
public SimpleCommand(CommandType commandType, int zone) {
|
||||
this.commandType = commandType;
|
||||
this.zone = zone;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCommand() {
|
||||
return commandType.getCommand(zone) + "\r";
|
||||
}
|
||||
|
||||
@Override
|
||||
public CommandType getCommandType() {
|
||||
return commandType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getZone() {
|
||||
return zone;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,386 @@
|
||||
/**
|
||||
* 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.pioneeravr.internal.protocol;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.openhab.binding.pioneeravr.internal.protocol.ParameterizedCommand.ParameterizedCommandType;
|
||||
import org.openhab.binding.pioneeravr.internal.protocol.SimpleCommand.SimpleCommandType;
|
||||
import org.openhab.binding.pioneeravr.internal.protocol.avr.AvrCommand;
|
||||
import org.openhab.binding.pioneeravr.internal.protocol.avr.AvrConnection;
|
||||
import org.openhab.binding.pioneeravr.internal.protocol.avr.CommandTypeNotSupportedException;
|
||||
import org.openhab.binding.pioneeravr.internal.protocol.event.AvrDisconnectionEvent;
|
||||
import org.openhab.binding.pioneeravr.internal.protocol.event.AvrDisconnectionListener;
|
||||
import org.openhab.binding.pioneeravr.internal.protocol.event.AvrStatusUpdateEvent;
|
||||
import org.openhab.binding.pioneeravr.internal.protocol.event.AvrUpdateListener;
|
||||
import org.openhab.binding.pioneeravr.internal.protocol.utils.VolumeConverter;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.library.types.IncreaseDecreaseType;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.PercentType;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.util.HexUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
*
|
||||
* A class that wraps the communication to Pioneer AVR devices by using Input/Ouptut streams.
|
||||
*
|
||||
* see {@link http ://www.pioneerelectronics.com/StaticFiles/PUSA/Files/Home%20Custom %20Install/VSX-1120-K-RS232.PDF}
|
||||
* for the protocol specs
|
||||
*
|
||||
* Based on the Onkyo binding by Pauli Anttila and others.
|
||||
*
|
||||
* @author Antoine Besnard - Initial contribution
|
||||
* @author Rainer Ostendorf - Initial contribution
|
||||
* @author Leroy Foerster - Listening Mode, Playing Listening Mode
|
||||
*/
|
||||
public abstract class StreamAvrConnection implements AvrConnection {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(StreamAvrConnection.class);
|
||||
|
||||
// The maximum time to wait incoming messages.
|
||||
private static final Integer READ_TIMEOUT = 1000;
|
||||
|
||||
private List<AvrUpdateListener> updateListeners;
|
||||
private List<AvrDisconnectionListener> disconnectionListeners;
|
||||
|
||||
private IpControlInputStreamReader inputStreamReader;
|
||||
private DataOutputStream outputStream;
|
||||
|
||||
public StreamAvrConnection() {
|
||||
this.updateListeners = new ArrayList<>();
|
||||
this.disconnectionListeners = new ArrayList<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addUpdateListener(AvrUpdateListener listener) {
|
||||
synchronized (updateListeners) {
|
||||
updateListeners.add(listener);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addDisconnectionListener(AvrDisconnectionListener listener) {
|
||||
synchronized (disconnectionListeners) {
|
||||
disconnectionListeners.add(listener);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean connect() {
|
||||
if (!isConnected()) {
|
||||
try {
|
||||
openConnection();
|
||||
|
||||
// Start the inputStream reader.
|
||||
inputStreamReader = new IpControlInputStreamReader(getInputStream());
|
||||
inputStreamReader.start();
|
||||
|
||||
// Get Output stream
|
||||
outputStream = new DataOutputStream(getOutputStream());
|
||||
} catch (IOException ioException) {
|
||||
logger.debug("Can't connect to {}. Cause: {}", getConnectionName(), ioException.getMessage());
|
||||
}
|
||||
}
|
||||
return isConnected();
|
||||
}
|
||||
|
||||
/**
|
||||
* Open the connection to the AVR.
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
protected abstract void openConnection() throws IOException;
|
||||
|
||||
/**
|
||||
* Return the inputStream to read responses.
|
||||
*
|
||||
* @return
|
||||
* @throws IOException
|
||||
*/
|
||||
protected abstract InputStream getInputStream() throws IOException;
|
||||
|
||||
/**
|
||||
* Return the outputStream to send commands.
|
||||
*
|
||||
* @return
|
||||
* @throws IOException
|
||||
*/
|
||||
protected abstract OutputStream getOutputStream() throws IOException;
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
if (inputStreamReader != null) {
|
||||
// This method block until the reader is really stopped.
|
||||
inputStreamReader.stopReader();
|
||||
inputStreamReader = null;
|
||||
logger.debug("Stream reader stopped!");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends to command to the receiver. It does not wait for a reply.
|
||||
*
|
||||
* @param ipControlCommand
|
||||
* the command to send.
|
||||
**/
|
||||
protected boolean sendCommand(AvrCommand ipControlCommand) {
|
||||
boolean isSent = false;
|
||||
if (connect()) {
|
||||
String command = ipControlCommand.getCommand();
|
||||
try {
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("Sending {} bytes: {}", command.length(), HexUtils.bytesToHex(command.getBytes()));
|
||||
}
|
||||
outputStream.writeBytes(command);
|
||||
outputStream.flush();
|
||||
isSent = true;
|
||||
} catch (IOException ioException) {
|
||||
logger.error("Error occurred when sending command", ioException);
|
||||
// If an error occurs, close the connection
|
||||
close();
|
||||
}
|
||||
|
||||
logger.debug("Command sent to AVR @{}: {}", getConnectionName(), command);
|
||||
}
|
||||
|
||||
return isSent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean sendPowerQuery(int zone) {
|
||||
return sendCommand(RequestResponseFactory.getIpControlCommand(SimpleCommandType.POWER_QUERY, zone));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean sendVolumeQuery(int zone) {
|
||||
return sendCommand(RequestResponseFactory.getIpControlCommand(SimpleCommandType.VOLUME_QUERY, zone));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean sendMuteQuery(int zone) {
|
||||
return sendCommand(RequestResponseFactory.getIpControlCommand(SimpleCommandType.MUTE_QUERY, zone));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean sendInputSourceQuery(int zone) {
|
||||
return sendCommand(RequestResponseFactory.getIpControlCommand(SimpleCommandType.INPUT_QUERY, zone));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean sendListeningModeQuery(int zone) {
|
||||
return sendCommand(RequestResponseFactory.getIpControlCommand(SimpleCommandType.LISTENING_MODE_QUERY, zone));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean sendPowerCommand(Command command, int zone) throws CommandTypeNotSupportedException {
|
||||
AvrCommand commandToSend = null;
|
||||
|
||||
if (command == OnOffType.ON) {
|
||||
commandToSend = RequestResponseFactory.getIpControlCommand(SimpleCommandType.POWER_ON, zone);
|
||||
// Send the first Power ON command.
|
||||
sendCommand(commandToSend);
|
||||
|
||||
// According to the Pioneer Specs, the first request only wakeup the
|
||||
// AVR CPU, the second one Power ON the AVR. Still according to the Pioneer Specs, the second
|
||||
// request has to be delayed of 100 ms.
|
||||
try {
|
||||
TimeUnit.MILLISECONDS.sleep(100);
|
||||
} catch (InterruptedException ex) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
} else if (command == OnOffType.OFF) {
|
||||
commandToSend = RequestResponseFactory.getIpControlCommand(SimpleCommandType.POWER_OFF, zone);
|
||||
} else {
|
||||
throw new CommandTypeNotSupportedException("Command type not supported.");
|
||||
}
|
||||
|
||||
return sendCommand(commandToSend);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean sendVolumeCommand(Command command, int zone) throws CommandTypeNotSupportedException {
|
||||
boolean commandSent = false;
|
||||
|
||||
// The OnOffType for volume is equal to the Mute command
|
||||
if (command instanceof OnOffType) {
|
||||
commandSent = sendMuteCommand(command, zone);
|
||||
} else {
|
||||
AvrCommand commandToSend = null;
|
||||
|
||||
if (command == IncreaseDecreaseType.DECREASE) {
|
||||
commandToSend = RequestResponseFactory.getIpControlCommand(SimpleCommandType.VOLUME_DOWN, zone);
|
||||
} else if (command == IncreaseDecreaseType.INCREASE) {
|
||||
commandToSend = RequestResponseFactory.getIpControlCommand(SimpleCommandType.VOLUME_UP, zone);
|
||||
} else if (command instanceof PercentType) {
|
||||
String ipControlVolume = VolumeConverter
|
||||
.convertFromPercentToIpControlVolume(((PercentType) command).doubleValue(), zone);
|
||||
commandToSend = RequestResponseFactory.getIpControlCommand(ParameterizedCommandType.VOLUME_SET, zone)
|
||||
.setParameter(ipControlVolume);
|
||||
} else if (command instanceof DecimalType) {
|
||||
String ipControlVolume = VolumeConverter
|
||||
.convertFromDbToIpControlVolume(((DecimalType) command).doubleValue(), zone);
|
||||
commandToSend = RequestResponseFactory.getIpControlCommand(ParameterizedCommandType.VOLUME_SET, zone)
|
||||
.setParameter(ipControlVolume);
|
||||
} else {
|
||||
throw new CommandTypeNotSupportedException("Command type not supported.");
|
||||
}
|
||||
|
||||
commandSent = sendCommand(commandToSend);
|
||||
}
|
||||
return commandSent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean sendInputSourceCommand(Command command, int zone) throws CommandTypeNotSupportedException {
|
||||
AvrCommand commandToSend = null;
|
||||
|
||||
if (command == IncreaseDecreaseType.INCREASE) {
|
||||
commandToSend = RequestResponseFactory.getIpControlCommand(SimpleCommandType.INPUT_CHANGE_CYCLIC, zone);
|
||||
} else if (command == IncreaseDecreaseType.DECREASE) {
|
||||
commandToSend = RequestResponseFactory.getIpControlCommand(SimpleCommandType.INPUT_CHANGE_REVERSE, zone);
|
||||
} else if (command instanceof StringType) {
|
||||
String inputSourceValue = ((StringType) command).toString();
|
||||
commandToSend = RequestResponseFactory.getIpControlCommand(ParameterizedCommandType.INPUT_CHANNEL_SET, zone)
|
||||
.setParameter(inputSourceValue);
|
||||
} else {
|
||||
throw new CommandTypeNotSupportedException("Command type not supported.");
|
||||
}
|
||||
|
||||
return sendCommand(commandToSend);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean sendListeningModeCommand(Command command, int zone) throws CommandTypeNotSupportedException {
|
||||
AvrCommand commandToSend = null;
|
||||
|
||||
if (command == IncreaseDecreaseType.INCREASE) {
|
||||
commandToSend = RequestResponseFactory.getIpControlCommand(SimpleCommandType.LISTENING_MODE_CHANGE_CYCLIC,
|
||||
zone);
|
||||
} else if (command instanceof StringType) {
|
||||
String listeningModeValue = ((StringType) command).toString();
|
||||
commandToSend = RequestResponseFactory
|
||||
.getIpControlCommand(ParameterizedCommandType.LISTENING_MODE_SET, zone)
|
||||
.setParameter(listeningModeValue);
|
||||
} else {
|
||||
throw new CommandTypeNotSupportedException("Command type not supported.");
|
||||
}
|
||||
|
||||
return sendCommand(commandToSend);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean sendMuteCommand(Command command, int zone) throws CommandTypeNotSupportedException {
|
||||
AvrCommand commandToSend = null;
|
||||
|
||||
if (command == OnOffType.ON) {
|
||||
commandToSend = RequestResponseFactory.getIpControlCommand(SimpleCommandType.MUTE_ON, zone);
|
||||
} else if (command == OnOffType.OFF) {
|
||||
commandToSend = RequestResponseFactory.getIpControlCommand(SimpleCommandType.MUTE_OFF, zone);
|
||||
} else {
|
||||
throw new CommandTypeNotSupportedException("Command type not supported.");
|
||||
}
|
||||
|
||||
return sendCommand(commandToSend);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read incoming data from the AVR and notify listeners for dataReceived and disconnection.
|
||||
*
|
||||
* @author Antoine Besnard
|
||||
*
|
||||
*/
|
||||
private class IpControlInputStreamReader extends Thread {
|
||||
|
||||
private BufferedReader bufferedReader;
|
||||
|
||||
private volatile boolean stopReader;
|
||||
|
||||
// This latch is used to block the stop method until the reader is really stopped.
|
||||
private CountDownLatch stopLatch;
|
||||
|
||||
/**
|
||||
* Construct a reader that read the given inputStream
|
||||
*
|
||||
* @param ipControlSocket
|
||||
* @throws IOException
|
||||
*/
|
||||
public IpControlInputStreamReader(InputStream inputStream) {
|
||||
this.bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
|
||||
this.stopLatch = new CountDownLatch(1);
|
||||
|
||||
this.setDaemon(true);
|
||||
this.setName("IpControlInputStreamReader-" + getConnectionName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
while (!stopReader && !Thread.currentThread().isInterrupted()) {
|
||||
String receivedData = null;
|
||||
try {
|
||||
receivedData = bufferedReader.readLine();
|
||||
} catch (SocketTimeoutException e) {
|
||||
// Do nothing. Just happen to allow the thread to check if it has to stop.
|
||||
}
|
||||
|
||||
if (receivedData != null) {
|
||||
logger.debug("Data received from AVR @{}: {}", getConnectionName(), receivedData);
|
||||
AvrStatusUpdateEvent event = new AvrStatusUpdateEvent(StreamAvrConnection.this, receivedData);
|
||||
synchronized (updateListeners) {
|
||||
for (AvrUpdateListener pioneerAvrEventListener : updateListeners) {
|
||||
pioneerAvrEventListener.statusUpdateReceived(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
logger.warn("The AVR @{} is disconnected.", getConnectionName(), e);
|
||||
AvrDisconnectionEvent event = new AvrDisconnectionEvent(StreamAvrConnection.this, e);
|
||||
for (AvrDisconnectionListener pioneerAvrDisconnectionListener : disconnectionListeners) {
|
||||
pioneerAvrDisconnectionListener.onDisconnection(event);
|
||||
}
|
||||
}
|
||||
|
||||
// Notify the stopReader method caller that the reader is stopped.
|
||||
this.stopLatch.countDown();
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop this reader. Block until the reader is really stopped.
|
||||
*/
|
||||
public void stopReader() {
|
||||
this.stopReader = true;
|
||||
try {
|
||||
this.stopLatch.await(READ_TIMEOUT, TimeUnit.MILLISECONDS);
|
||||
} catch (InterruptedException e) {
|
||||
// Do nothing. The timeout is just here for safety and to be sure that the call to this method will not
|
||||
// block the caller indefinitely.
|
||||
// This exception should never happen.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
/**
|
||||
* 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.pioneeravr.internal.protocol.avr;
|
||||
|
||||
/**
|
||||
* The base interface for an AVR command.
|
||||
*
|
||||
* @author Antoine Besnard - Initial contribution
|
||||
*/
|
||||
public interface AvrCommand {
|
||||
|
||||
/**
|
||||
* Represent a CommandType of command requests
|
||||
*/
|
||||
public interface CommandType {
|
||||
|
||||
/**
|
||||
* Return the command of this command type for the given zone.
|
||||
*
|
||||
* The first zone number is 1.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public String getCommand(int zone);
|
||||
|
||||
/**
|
||||
* Return the name of the command type
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public String name();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the command to send to the AVR.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public String getCommand();
|
||||
|
||||
/**
|
||||
* Return the number of the zone this command will be sent to.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public int getZone();
|
||||
|
||||
/**
|
||||
* Return the the command type of this command.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public CommandType getCommandType();
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
/**
|
||||
* 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.pioneeravr.internal.protocol.avr;
|
||||
|
||||
import org.openhab.binding.pioneeravr.internal.protocol.event.AvrDisconnectionListener;
|
||||
import org.openhab.binding.pioneeravr.internal.protocol.event.AvrUpdateListener;
|
||||
import org.openhab.core.types.Command;
|
||||
|
||||
/**
|
||||
* Represent a connection to a remote Pioneer AVR.
|
||||
*
|
||||
* @author Antoine Besnard - Initial contribution
|
||||
* @author Leroy Foerster - Listening Mode, Playing Listening Mode
|
||||
*/
|
||||
public interface AvrConnection {
|
||||
|
||||
/**
|
||||
* Add an update listener. It is notified when an update is received from the AVR.
|
||||
*
|
||||
* @param listener
|
||||
*/
|
||||
public void addUpdateListener(AvrUpdateListener listener);
|
||||
|
||||
/**
|
||||
* Add a disconnection listener. It is notified when the AVR is disconnected.
|
||||
*
|
||||
* @param listener
|
||||
*/
|
||||
public void addDisconnectionListener(AvrDisconnectionListener listener);
|
||||
|
||||
/**
|
||||
* Connect to the receiver. Return true if the connection has succeeded or if already connected.
|
||||
*
|
||||
**/
|
||||
public boolean connect();
|
||||
|
||||
/**
|
||||
* Return true if this manager is connected to the AVR.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public boolean isConnected();
|
||||
|
||||
/**
|
||||
* Closes the connection.
|
||||
**/
|
||||
public void close();
|
||||
|
||||
/**
|
||||
* Send a power state query to the AVR
|
||||
*
|
||||
* @param zone
|
||||
* @return
|
||||
*/
|
||||
public boolean sendPowerQuery(int zone);
|
||||
|
||||
/**
|
||||
* Send a volume level query to the AVR
|
||||
*
|
||||
* @param zone
|
||||
* @return
|
||||
*/
|
||||
public boolean sendVolumeQuery(int zone);
|
||||
|
||||
/**
|
||||
* Send a mute state query to the AVR
|
||||
*
|
||||
* @param zone
|
||||
* @return
|
||||
*/
|
||||
public boolean sendMuteQuery(int zone);
|
||||
|
||||
/**
|
||||
* Send a source input state query to the AVR
|
||||
*
|
||||
* @param zone
|
||||
* @return
|
||||
*/
|
||||
public boolean sendInputSourceQuery(int zone);
|
||||
|
||||
/**
|
||||
* Send a listening mode state query to the AVR
|
||||
*
|
||||
* @param zone
|
||||
* @return
|
||||
*/
|
||||
public boolean sendListeningModeQuery(int zone);
|
||||
|
||||
/**
|
||||
* Send a power command ot the AVR based on the openHAB command
|
||||
*
|
||||
* @param command
|
||||
* @param zone
|
||||
* @return
|
||||
*/
|
||||
public boolean sendPowerCommand(Command command, int zone) throws CommandTypeNotSupportedException;
|
||||
|
||||
/**
|
||||
* Send a volume command to the AVR based on the openHAB command
|
||||
*
|
||||
* @param command
|
||||
* @param zone
|
||||
* @return
|
||||
*/
|
||||
public boolean sendVolumeCommand(Command command, int zone) throws CommandTypeNotSupportedException;
|
||||
|
||||
/**
|
||||
* Send a source input selection command to the AVR based on the openHAB command
|
||||
*
|
||||
* @param command
|
||||
* @param zone
|
||||
* @return
|
||||
*/
|
||||
public boolean sendInputSourceCommand(Command command, int zone) throws CommandTypeNotSupportedException;
|
||||
|
||||
/**
|
||||
* Send a listening mode selection command to the AVR based on the openHAB command
|
||||
*
|
||||
* @param command
|
||||
* @param zone
|
||||
* @return
|
||||
*/
|
||||
public boolean sendListeningModeCommand(Command command, int zone) throws CommandTypeNotSupportedException;
|
||||
|
||||
/**
|
||||
* Send a mute command to the AVR based on the openHAB command
|
||||
*
|
||||
* @param command
|
||||
* @param zone
|
||||
* @return
|
||||
*/
|
||||
public boolean sendMuteCommand(Command command, int zone) throws CommandTypeNotSupportedException;
|
||||
|
||||
/**
|
||||
* Return the connection name
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public String getConnectionName();
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* 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.pioneeravr.internal.protocol.avr;
|
||||
|
||||
/**
|
||||
* Exception for eISCP errors.
|
||||
*
|
||||
* Based on the Onkyo binding by Pauli Anttila and others.
|
||||
*
|
||||
* @author Rainer Ostendorf - Initial contribution
|
||||
*/
|
||||
public class AvrConnectionException extends RuntimeException {
|
||||
|
||||
private static final long serialVersionUID = -7970958467980752003L;
|
||||
|
||||
public AvrConnectionException() {
|
||||
super();
|
||||
}
|
||||
|
||||
public AvrConnectionException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public AvrConnectionException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public AvrConnectionException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
/**
|
||||
* 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.pioneeravr.internal.protocol.avr;
|
||||
|
||||
import org.openhab.binding.pioneeravr.internal.protocol.Response.ResponseType;
|
||||
|
||||
/**
|
||||
* Represent a response of the AVR.
|
||||
*
|
||||
* @author Antoine Besnard - Initial contribution
|
||||
*/
|
||||
public interface AvrResponse {
|
||||
|
||||
/**
|
||||
* Represent the type of a response.
|
||||
*/
|
||||
public interface AvrResponseType {
|
||||
|
||||
/**
|
||||
* Return the prefix of the command of this type.
|
||||
*
|
||||
* @param zone
|
||||
* @return
|
||||
*/
|
||||
public String getResponsePrefix(int zone);
|
||||
|
||||
/**
|
||||
* Return true if the responses of this type has to have a parameter.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public boolean hasParameter();
|
||||
|
||||
/**
|
||||
* Return the parameter pattern (RegEx) of the response.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public String getParameterPattern();
|
||||
|
||||
/**
|
||||
* Return the zone number if the responseData matches a zone of this responseType.
|
||||
*
|
||||
* If any zone matches, return null.
|
||||
*
|
||||
* @param responseData
|
||||
* @return
|
||||
*/
|
||||
public Integer match(String responseData);
|
||||
|
||||
/**
|
||||
* Return the parameter value of the given responseData.
|
||||
*
|
||||
* @param responseData
|
||||
* @return
|
||||
*/
|
||||
public String parseParameter(String responseData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the response type of this response
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public ResponseType getResponseType();
|
||||
|
||||
/**
|
||||
* Return the parameter of this response or null if the resposne has no parameter.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public String getParameterValue();
|
||||
|
||||
/**
|
||||
* Return true if this response has a parameter.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public boolean hasParameter();
|
||||
|
||||
/**
|
||||
* Return the zone number which is concerned by this response.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public Integer getZone();
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* 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.pioneeravr.internal.protocol.avr;
|
||||
|
||||
/**
|
||||
* Thrown when a command type is not supported by the channel
|
||||
*
|
||||
* @author Antoine Besnard - Initial contribution
|
||||
*/
|
||||
public class CommandTypeNotSupportedException extends Exception {
|
||||
|
||||
private static final long serialVersionUID = -7970958467980752003L;
|
||||
|
||||
public CommandTypeNotSupportedException() {
|
||||
super();
|
||||
}
|
||||
|
||||
public CommandTypeNotSupportedException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public CommandTypeNotSupportedException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public CommandTypeNotSupportedException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* 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.pioneeravr.internal.protocol.event;
|
||||
|
||||
import org.openhab.binding.pioneeravr.internal.protocol.avr.AvrConnection;
|
||||
|
||||
/**
|
||||
* An event fired when an AVR is disconnected.
|
||||
*
|
||||
* @author Antoine Besnard - Initial contribution
|
||||
*/
|
||||
public class AvrDisconnectionEvent {
|
||||
|
||||
private AvrConnection connection;
|
||||
private Throwable cause;
|
||||
|
||||
public AvrDisconnectionEvent(AvrConnection connection, Throwable cause) {
|
||||
this.connection = connection;
|
||||
this.cause = cause;
|
||||
}
|
||||
|
||||
public AvrConnection getConnection() {
|
||||
return connection;
|
||||
}
|
||||
|
||||
public Throwable getCause() {
|
||||
return cause;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* 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.pioneeravr.internal.protocol.event;
|
||||
|
||||
/**
|
||||
* A listener which is notified when an AVR is disconnected.
|
||||
*
|
||||
* @author Antoine Besnard - Initial contribution
|
||||
*/
|
||||
public interface AvrDisconnectionListener {
|
||||
|
||||
/**
|
||||
* Called when an AVR is disconnected.
|
||||
*
|
||||
* @param event
|
||||
*/
|
||||
public void onDisconnection(AvrDisconnectionEvent event);
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* 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.pioneeravr.internal.protocol.event;
|
||||
|
||||
import org.openhab.binding.pioneeravr.internal.protocol.avr.AvrConnection;
|
||||
|
||||
/**
|
||||
* The event fired when a status is received from the AVR.
|
||||
*
|
||||
* @author Antoine Besnard - Initial contribution
|
||||
*/
|
||||
public class AvrStatusUpdateEvent {
|
||||
|
||||
private AvrConnection connection;
|
||||
private String data;
|
||||
|
||||
public AvrStatusUpdateEvent(AvrConnection connection, String data) {
|
||||
this.connection = connection;
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public AvrConnection getConnection() {
|
||||
return connection;
|
||||
}
|
||||
|
||||
public String getData() {
|
||||
return data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
* 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.pioneeravr.internal.protocol.event;
|
||||
|
||||
import java.util.EventListener;
|
||||
|
||||
/**
|
||||
* This interface defines interface to receive status updates from pioneerav receiver.
|
||||
*
|
||||
* Based on the Onkyo binding by Pauli Anttila and others
|
||||
*
|
||||
* @author Antoine Besnard - Initial contribution
|
||||
* @author Rainer Ostendorf - Initial contribution
|
||||
*/
|
||||
public interface AvrUpdateListener extends EventListener {
|
||||
|
||||
/**
|
||||
* Procedure for receive status update from Pioneer receiver.
|
||||
*/
|
||||
public void statusUpdateReceived(AvrStatusUpdateEvent event);
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
/**
|
||||
* 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.pioneeravr.internal.protocol.ip;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.Socket;
|
||||
|
||||
import org.openhab.binding.pioneeravr.internal.protocol.StreamAvrConnection;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
*
|
||||
* A class that wraps the communication to a Pioneer AVR devices using an IP connection.
|
||||
*
|
||||
* @author Antoine Besnard - Initial contribution
|
||||
*/
|
||||
public class IpAvrConnection extends StreamAvrConnection {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(IpAvrConnection.class);
|
||||
|
||||
/** default port for IP communication **/
|
||||
public static final int DEFAULT_IPCONTROL_PORT = 8102;
|
||||
|
||||
/** Connection timeout in milliseconds **/
|
||||
private static final int CONNECTION_TIMEOUT = 3000;
|
||||
|
||||
/** Socket read timeout in milliseconds **/
|
||||
private static final int SOCKET_READ_TIMEOUT = 1000;
|
||||
|
||||
private int receiverPort;
|
||||
private String receiverHost;
|
||||
|
||||
private Socket ipControlSocket;
|
||||
|
||||
public IpAvrConnection(String receiverHost) {
|
||||
this(receiverHost, null);
|
||||
}
|
||||
|
||||
public IpAvrConnection(String receiverHost, Integer ipControlPort) {
|
||||
this.receiverHost = receiverHost;
|
||||
this.receiverPort = ipControlPort != null && ipControlPort >= 1 ? ipControlPort : DEFAULT_IPCONTROL_PORT;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void openConnection() throws IOException {
|
||||
ipControlSocket = new Socket();
|
||||
|
||||
// Set this timeout to unblock a blocking read when no data is received. It is useful to check if the
|
||||
// reading thread has to be stopped (it implies a latency of SOCKET_READ_TIMEOUT at most before the
|
||||
// thread is really stopped)
|
||||
ipControlSocket.setSoTimeout(SOCKET_READ_TIMEOUT);
|
||||
|
||||
// Enable tcpKeepAlive to detect premature disconnection
|
||||
// and prevent disconnection because of inactivity
|
||||
ipControlSocket.setKeepAlive(true);
|
||||
|
||||
// Connect to the AVR with a connection timeout.
|
||||
ipControlSocket.connect(new InetSocketAddress(receiverHost, receiverPort), CONNECTION_TIMEOUT);
|
||||
|
||||
logger.debug("Connected to {}:{}", receiverHost, receiverPort);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isConnected() {
|
||||
return ipControlSocket != null && ipControlSocket.isConnected() && !ipControlSocket.isClosed();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
super.close();
|
||||
try {
|
||||
if (ipControlSocket != null) {
|
||||
ipControlSocket.close();
|
||||
ipControlSocket = null;
|
||||
logger.debug("Closed socket!");
|
||||
}
|
||||
} catch (IOException ioException) {
|
||||
logger.error("Closing connection throws an exception!", ioException);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getConnectionName() {
|
||||
return receiverHost + ":" + receiverPort;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected InputStream getInputStream() throws IOException {
|
||||
return ipControlSocket.getInputStream();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected OutputStream getOutputStream() throws IOException {
|
||||
return ipControlSocket.getOutputStream();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
/**
|
||||
* 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.pioneeravr.internal.protocol.serial;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.pioneeravr.internal.protocol.StreamAvrConnection;
|
||||
import org.openhab.core.io.transport.serial.PortInUseException;
|
||||
import org.openhab.core.io.transport.serial.SerialPort;
|
||||
import org.openhab.core.io.transport.serial.SerialPortIdentifier;
|
||||
import org.openhab.core.io.transport.serial.SerialPortManager;
|
||||
import org.openhab.core.io.transport.serial.UnsupportedCommOperationException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* A class that wraps the communication to a Pioneer AVR devices through a serial port
|
||||
*
|
||||
* @author Antoine Besnard - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SerialAvrConnection extends StreamAvrConnection {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(SerialAvrConnection.class);
|
||||
|
||||
private static final Integer LINK_SPEED = 9600;
|
||||
|
||||
private final String portName;
|
||||
|
||||
private @Nullable SerialPort serialPort;
|
||||
|
||||
private final SerialPortManager serialPortManager;
|
||||
|
||||
public SerialAvrConnection(String portName, SerialPortManager serialPortManager) {
|
||||
this.portName = portName;
|
||||
this.serialPortManager = serialPortManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void openConnection() throws IOException {
|
||||
SerialPortIdentifier serialPortIdentifier = serialPortManager.getIdentifier(portName);
|
||||
if (serialPortIdentifier == null) {
|
||||
String availablePorts = serialPortManager.getIdentifiers().map(id -> id.getName())
|
||||
.collect(Collectors.joining(", "));
|
||||
throw new IOException(
|
||||
"Serial port with name " + portName + " does not exist. Available port names: " + availablePorts);
|
||||
|
||||
}
|
||||
|
||||
try {
|
||||
SerialPort localSerialPort = serialPortIdentifier.open(SerialAvrConnection.class.getSimpleName(), 2000);
|
||||
localSerialPort.setSerialPortParams(LINK_SPEED, SerialPort.DATABITS_8, SerialPort.STOPBITS_1,
|
||||
SerialPort.PARITY_NONE);
|
||||
serialPort = localSerialPort;
|
||||
} catch (PortInUseException | UnsupportedCommOperationException e) {
|
||||
throw new IOException("Failed to connect on port " + portName);
|
||||
}
|
||||
|
||||
logger.debug("Connected to {}", getConnectionName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isConnected() {
|
||||
return serialPort != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
super.close();
|
||||
SerialPort localSerialPort = serialPort;
|
||||
if (localSerialPort != null) {
|
||||
localSerialPort.close();
|
||||
serialPort = null;
|
||||
logger.debug("Closed port {}", portName);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getConnectionName() {
|
||||
return portName;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @Nullable InputStream getInputStream() throws IOException {
|
||||
SerialPort localSerialPort = serialPort;
|
||||
return localSerialPort != null ? localSerialPort.getInputStream() : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @Nullable OutputStream getOutputStream() throws IOException {
|
||||
SerialPort localSerialPort = serialPort;
|
||||
return localSerialPort != null ? localSerialPort.getOutputStream() : null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
/**
|
||||
* 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.pioneeravr.internal.protocol.states;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Antoine Besnard - Initial contribution
|
||||
*/
|
||||
public interface MuteStateValues {
|
||||
|
||||
public static final String ON_VALUE = "0";
|
||||
public static final String OFF_VALUE = "1";
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
/**
|
||||
* 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.pioneeravr.internal.protocol.states;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Antoine Besnard - Initial contribution
|
||||
*/
|
||||
public interface PowerStateValues {
|
||||
|
||||
public static final String ON_VALUE = "0";
|
||||
public static final String OFF_VALUE = "1";
|
||||
public static final String NETWORK_STANDBY_VALUE = "2";
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/**
|
||||
* 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.pioneeravr.internal.protocol.utils;
|
||||
|
||||
import org.openhab.binding.pioneeravr.internal.protocol.avr.AvrConnectionException;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Antoine Besnard - Initial contribution
|
||||
*/
|
||||
public class DisplayInformationConverter {
|
||||
|
||||
/**
|
||||
* Convert an IpControl information message payload to a readable String.
|
||||
*
|
||||
* @param responsePayload
|
||||
* @return
|
||||
* @throws AvrConnectionException
|
||||
*/
|
||||
public static String convertMessageFromIpControl(String responsePayload) throws AvrConnectionException {
|
||||
// Example from Pioneer docs: When " [)(]DIGITAL EX " is displayed,
|
||||
// response command is:
|
||||
// FL000005064449474954414C00455800<CR+LF>
|
||||
|
||||
// First byte holds the two special flags. Do not use it to parse the
|
||||
// message.
|
||||
// Convert the ASCII values back to string
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 2; i < responsePayload.length() - 1; i += 2) {
|
||||
String hexAsciiValue = responsePayload.substring(i, i + 2);
|
||||
try {
|
||||
sb.append((char) Integer.parseInt(hexAsciiValue, 16));
|
||||
} catch (Exception e) {
|
||||
throw new AvrConnectionException(
|
||||
"Failed to parse the reponsePayload as an IpControl information message.", e);
|
||||
}
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
/**
|
||||
* 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.pioneeravr.internal.protocol.utils;
|
||||
|
||||
import java.text.DecimalFormat;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Antoine Besnard - Initial contribution
|
||||
*/
|
||||
public final class VolumeConverter {
|
||||
|
||||
// Volume Format / MAX_IP / Min_DB for Zones
|
||||
// Zone1 Command ***VL 000 to 185 (-80.0dB - +12.0dB - 1step = 0.5dB)
|
||||
// Zone2 Command **ZV 00 to 81 (-80.0dB - + 0.0dB - 1step = 1.0dB)
|
||||
// Zone3 Command **YV 00 to 81 (-80.0dB - + 0.0dB - 1step = 1.0dB)
|
||||
// HDZone Command **YV 00 to 81 (-80.0dB - + 0.0dB - 1step = 1.0dB)
|
||||
private static final String[] IP_CONTROL_VOLUME_FORMAT = { "000", "00", "00", "00" };
|
||||
private static final String[] IP_CONTROL_VOLUME_DEFAULT_VALUE = { "000", "00", "00", "00" };
|
||||
|
||||
private static final double[] MAX_IP_CONTROL_VOLUME = { 184, 80, 80, 80 };
|
||||
private static final double[] MIN_DB_VOLUME = { 80, 80, 80, 80 };
|
||||
|
||||
/**
|
||||
* Return the double value of the volume from the value received in the IpControl response.
|
||||
*
|
||||
* @param ipControlVolume
|
||||
* @return the volume in Db
|
||||
*/
|
||||
public static double convertFromIpControlVolumeToDb(String ipControlVolume, int zone) {
|
||||
validateZone(zone - 1);
|
||||
double ipControlVolumeInt = Double.parseDouble(ipControlVolume);
|
||||
return ((ipControlVolumeInt - 1d) / 2d) - MIN_DB_VOLUME[zone - 1];
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the string parameter to send to the AVR based on the given volume.
|
||||
*
|
||||
* @param volumeDb
|
||||
* @return the volume for IpControlRequest
|
||||
*/
|
||||
public static String convertFromDbToIpControlVolume(double volumeDb, int zone) {
|
||||
validateZone(zone - 1);
|
||||
double ipControlVolume = ((MIN_DB_VOLUME[zone - 1] + volumeDb) * 2d) + 1d;
|
||||
return formatIpControlVolume(ipControlVolume, zone);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the String parameter to send to the AVR based on the given persentage of the max volume level.
|
||||
*
|
||||
* @param volumePercent
|
||||
* @return the volume for IpControlRequest
|
||||
*/
|
||||
public static String convertFromPercentToIpControlVolume(double volumePercent, int zone) {
|
||||
validateZone(zone - 1);
|
||||
double ipControlVolume = 1 + (volumePercent * MAX_IP_CONTROL_VOLUME[zone - 1] / 100);
|
||||
return formatIpControlVolume(ipControlVolume, zone);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the percentage of the max volume levelfrom the value received in the IpControl response.
|
||||
*
|
||||
* @param ipControlVolume
|
||||
* @return the volume percentage
|
||||
*/
|
||||
public static double convertFromIpControlVolumeToPercent(String ipControlVolume, int zone) {
|
||||
validateZone(zone - 1);
|
||||
double ipControlVolumeInt = Double.parseDouble(ipControlVolume);
|
||||
return ((ipControlVolumeInt - 1d) * 100d) / MAX_IP_CONTROL_VOLUME[zone - 1];
|
||||
}
|
||||
|
||||
/**
|
||||
* Format the given double value to an IpControl volume.
|
||||
*
|
||||
* @param ipControlVolume
|
||||
* @return
|
||||
*/
|
||||
private static String formatIpControlVolume(double ipControlVolume, int zone) {
|
||||
validateZone(zone - 1);
|
||||
DecimalFormat formatter = new DecimalFormat(IP_CONTROL_VOLUME_FORMAT[zone - 1]);
|
||||
return formatter.format(Math.round(ipControlVolume));
|
||||
}
|
||||
|
||||
private static void validateZone(int zone) {
|
||||
if (zone < 0 || zone > 3) {
|
||||
throw new IllegalArgumentException("An unexpected zone was received, the value should be in the range 0-3");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<binding:binding id="pioneeravr" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:binding="https://openhab.org/schemas/binding/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/binding/v1.0.0 https://openhab.org/schemas/binding-1.0.0.xsd">
|
||||
|
||||
<name>PioneerAvr Binding</name>
|
||||
<description>A binding for Pioneer AVRs.</description>
|
||||
<author>Antoine Besnard</author>
|
||||
|
||||
</binding:binding>
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user