added migrated 2.x add-ons

Signed-off-by: Kai Kreuzer <kai@openhab.org>
This commit is contained in:
Kai Kreuzer
2020-09-21 01:58:32 +02:00
parent bbf1a7fd29
commit 6df6783b60
11662 changed files with 1302875 additions and 11 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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