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.denonmarantz-${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-denonmarantz" description="Denon / Marantz Binding" version="${project.version}">
|
||||
<feature>openhab-runtime-base</feature>
|
||||
<feature>openhab-transport-mdns</feature>
|
||||
<feature dependency="true">openhab.tp-jaxb</feature>
|
||||
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.denonmarantz/${project.version}</bundle>
|
||||
</feature>
|
||||
</features>
|
||||
@@ -0,0 +1,129 @@
|
||||
/**
|
||||
* 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.denonmarantz.internal;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.type.ChannelTypeUID;
|
||||
|
||||
/**
|
||||
* The {@link DenonMarantzBinding} class defines common constants, which are
|
||||
* used across the whole binding.
|
||||
*
|
||||
* @author Jan-Willem Veldhuis - Initial contribution
|
||||
*/
|
||||
public class DenonMarantzBindingConstants {
|
||||
|
||||
public static final String BINDING_ID = "denonmarantz";
|
||||
|
||||
// List of all Thing Type UIDs
|
||||
public static final ThingTypeUID THING_TYPE_AVR = new ThingTypeUID(BINDING_ID, "avr");
|
||||
|
||||
// List of thing Parameters names
|
||||
public static final String PARAMETER_ZONE_COUNT = "zoneCount";
|
||||
public static final String PARAMETER_HOST = "host";
|
||||
public static final String PARAMETER_TELNET_ENABLED = "telnetEnabled";
|
||||
public static final String PARAMETER_TELNET_PORT = "telnetPort";
|
||||
public static final String PARAMETER_HTTP_PORT = "httpPort";
|
||||
public static final String PARAMETER_POLLING_INTERVAL = "httpPollingInterval";
|
||||
|
||||
// List of all Channel ids
|
||||
public static final String CHANNEL_POWER = "general#power";
|
||||
public static final String CHANNEL_SURROUND_PROGRAM = "general#surroundProgram";
|
||||
public static final String CHANNEL_COMMAND = "general#command";
|
||||
public static final String CHANNEL_NOW_PLAYING_ARTIST = "general#artist";
|
||||
public static final String CHANNEL_NOW_PLAYING_ALBUM = "general#album";
|
||||
public static final String CHANNEL_NOW_PLAYING_TRACK = "general#track";
|
||||
|
||||
public static final String CHANNEL_MAIN_ZONE_POWER = "mainZone#power";
|
||||
public static final String CHANNEL_MAIN_VOLUME = "mainZone#volume";
|
||||
public static final String CHANNEL_MAIN_VOLUME_DB = "mainZone#volumeDB";
|
||||
public static final String CHANNEL_MUTE = "mainZone#mute";
|
||||
public static final String CHANNEL_INPUT = "mainZone#input";
|
||||
|
||||
public static final String CHANNEL_ZONE2_POWER = "zone2#power";
|
||||
public static final String CHANNEL_ZONE2_VOLUME = "zone2#volume";
|
||||
public static final String CHANNEL_ZONE2_VOLUME_DB = "zone2#volumeDB";
|
||||
public static final String CHANNEL_ZONE2_MUTE = "zone2#mute";
|
||||
public static final String CHANNEL_ZONE2_INPUT = "zone2#input";
|
||||
|
||||
public static final String CHANNEL_ZONE3_POWER = "zone3#power";
|
||||
public static final String CHANNEL_ZONE3_VOLUME = "zone3#volume";
|
||||
public static final String CHANNEL_ZONE3_VOLUME_DB = "zone3#volumeDB";
|
||||
public static final String CHANNEL_ZONE3_MUTE = "zone3#mute";
|
||||
public static final String CHANNEL_ZONE3_INPUT = "zone3#input";
|
||||
|
||||
public static final String CHANNEL_ZONE4_POWER = "zone4#power";
|
||||
public static final String CHANNEL_ZONE4_VOLUME = "zone4#volume";
|
||||
public static final String CHANNEL_ZONE4_VOLUME_DB = "zone4#volumeDB";
|
||||
public static final String CHANNEL_ZONE4_MUTE = "zone4#mute";
|
||||
public static final String CHANNEL_ZONE4_INPUT = "zone4#input";
|
||||
|
||||
// Map of Zone2 Channel Type UIDs (to be added to Thing later when needed)
|
||||
public static final Map<String, ChannelTypeUID> ZONE2_CHANNEL_TYPES = new LinkedHashMap<>();
|
||||
static {
|
||||
ZONE2_CHANNEL_TYPES.put(CHANNEL_ZONE2_POWER, new ChannelTypeUID(BINDING_ID, "zonePower"));
|
||||
ZONE2_CHANNEL_TYPES.put(CHANNEL_ZONE2_VOLUME, new ChannelTypeUID(BINDING_ID, "volume"));
|
||||
ZONE2_CHANNEL_TYPES.put(CHANNEL_ZONE2_VOLUME_DB, new ChannelTypeUID(BINDING_ID, "volumeDB"));
|
||||
ZONE2_CHANNEL_TYPES.put(CHANNEL_ZONE2_MUTE, new ChannelTypeUID(BINDING_ID, "mute"));
|
||||
ZONE2_CHANNEL_TYPES.put(CHANNEL_ZONE2_INPUT, new ChannelTypeUID(BINDING_ID, "input"));
|
||||
}
|
||||
|
||||
// Map of Zone3 Channel Type UIDs (to be added to Thing later when needed)
|
||||
public static final Map<String, ChannelTypeUID> ZONE3_CHANNEL_TYPES = new LinkedHashMap<>();
|
||||
static {
|
||||
ZONE3_CHANNEL_TYPES.put(CHANNEL_ZONE3_POWER, new ChannelTypeUID(BINDING_ID, "zonePower"));
|
||||
ZONE3_CHANNEL_TYPES.put(CHANNEL_ZONE3_VOLUME, new ChannelTypeUID(BINDING_ID, "volume"));
|
||||
ZONE3_CHANNEL_TYPES.put(CHANNEL_ZONE3_VOLUME_DB, new ChannelTypeUID(BINDING_ID, "volumeDB"));
|
||||
ZONE3_CHANNEL_TYPES.put(CHANNEL_ZONE3_MUTE, new ChannelTypeUID(BINDING_ID, "mute"));
|
||||
ZONE3_CHANNEL_TYPES.put(CHANNEL_ZONE3_INPUT, new ChannelTypeUID(BINDING_ID, "input"));
|
||||
}
|
||||
|
||||
// Map of Zone4 Channel Type UIDs (to be added to Thing later when needed)
|
||||
public static final Map<String, ChannelTypeUID> ZONE4_CHANNEL_TYPES = new LinkedHashMap<>();
|
||||
static {
|
||||
ZONE4_CHANNEL_TYPES.put(CHANNEL_ZONE4_POWER, new ChannelTypeUID(BINDING_ID, "zonePower"));
|
||||
ZONE4_CHANNEL_TYPES.put(CHANNEL_ZONE4_VOLUME, new ChannelTypeUID(BINDING_ID, "volume"));
|
||||
ZONE4_CHANNEL_TYPES.put(CHANNEL_ZONE4_VOLUME_DB, new ChannelTypeUID(BINDING_ID, "volumeDB"));
|
||||
ZONE4_CHANNEL_TYPES.put(CHANNEL_ZONE4_MUTE, new ChannelTypeUID(BINDING_ID, "mute"));
|
||||
ZONE4_CHANNEL_TYPES.put(CHANNEL_ZONE4_INPUT, new ChannelTypeUID(BINDING_ID, "input"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Static mapping of ChannelType-to-ItemType (workaround while waiting for
|
||||
* https://github.com/eclipse/smarthome/issues/4950 as yet there is no convenient way to extract the item type from
|
||||
* thing-types.xml)
|
||||
* See https://github.com/eclipse/smarthome/pull/4787#issuecomment-362287430
|
||||
*/
|
||||
public static final Map<String, String> CHANNEL_ITEM_TYPES = new HashMap<>();
|
||||
static {
|
||||
CHANNEL_ITEM_TYPES.put(CHANNEL_ZONE2_POWER, "Switch");
|
||||
CHANNEL_ITEM_TYPES.put(CHANNEL_ZONE2_VOLUME, "Dimmer");
|
||||
CHANNEL_ITEM_TYPES.put(CHANNEL_ZONE2_VOLUME_DB, "Number");
|
||||
CHANNEL_ITEM_TYPES.put(CHANNEL_ZONE2_MUTE, "Switch");
|
||||
CHANNEL_ITEM_TYPES.put(CHANNEL_ZONE2_INPUT, "String");
|
||||
|
||||
CHANNEL_ITEM_TYPES.put(CHANNEL_ZONE3_POWER, "Switch");
|
||||
CHANNEL_ITEM_TYPES.put(CHANNEL_ZONE3_VOLUME, "Dimmer");
|
||||
CHANNEL_ITEM_TYPES.put(CHANNEL_ZONE3_VOLUME_DB, "Number");
|
||||
CHANNEL_ITEM_TYPES.put(CHANNEL_ZONE3_MUTE, "Switch");
|
||||
CHANNEL_ITEM_TYPES.put(CHANNEL_ZONE3_INPUT, "String");
|
||||
}
|
||||
|
||||
// Offset in dB from the actual dB value to the volume as presented by the AVR (0 == -80 dB)
|
||||
public static final BigDecimal DB_OFFSET = new BigDecimal("80");
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
/**
|
||||
* 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.denonmarantz.internal;
|
||||
|
||||
import static org.openhab.binding.denonmarantz.internal.DenonMarantzBindingConstants.THING_TYPE_AVR;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.openhab.binding.denonmarantz.internal.handler.DenonMarantzHandler;
|
||||
import org.openhab.core.io.net.http.HttpClientFactory;
|
||||
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.annotations.Activate;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.osgi.service.component.annotations.Reference;
|
||||
|
||||
/**
|
||||
* The {@link DenonMarantzHandlerFactory} is responsible for creating things and thing
|
||||
* handlers.
|
||||
*
|
||||
* @author Jan-Willem Veldhuis - Initial contribution
|
||||
*/
|
||||
@Component(service = ThingHandlerFactory.class, configurationPid = "binding.denonmarantz")
|
||||
@NonNullByDefault
|
||||
public class DenonMarantzHandlerFactory extends BaseThingHandlerFactory {
|
||||
|
||||
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Collections.singleton(THING_TYPE_AVR);
|
||||
|
||||
private final HttpClient httpClient;
|
||||
|
||||
@Activate
|
||||
public DenonMarantzHandlerFactory(@Reference final HttpClientFactory httpClientFactory) {
|
||||
this.httpClient = httpClientFactory.getCommonHttpClient();
|
||||
}
|
||||
|
||||
@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(THING_TYPE_AVR)) {
|
||||
return new DenonMarantzHandler(thing, httpClient);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,317 @@
|
||||
/**
|
||||
* 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.denonmarantz.internal;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
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.types.State;
|
||||
|
||||
/**
|
||||
* Represents the state of the handled DenonMarantz AVR
|
||||
*
|
||||
* @author Jan-Willem Veldhuis - Initial contribution
|
||||
*
|
||||
*/
|
||||
public class DenonMarantzState {
|
||||
|
||||
private State power;
|
||||
private State mainZonePower;
|
||||
private State mute;
|
||||
private State mainVolume;
|
||||
private State mainVolumeDB;
|
||||
private State input;
|
||||
private State surroundProgram;
|
||||
|
||||
private State artist;
|
||||
private State album;
|
||||
private State track;
|
||||
|
||||
// ------ Zones ------
|
||||
private State zone2Power;
|
||||
private State zone2Volume;
|
||||
private State zone2VolumeDB;
|
||||
private State zone2Mute;
|
||||
private State zone2Input;
|
||||
|
||||
private State zone3Power;
|
||||
private State zone3Volume;
|
||||
private State zone3VolumeDB;
|
||||
private State zone3Mute;
|
||||
private State zone3Input;
|
||||
|
||||
private State zone4Power;
|
||||
private State zone4Volume;
|
||||
private State zone4VolumeDB;
|
||||
private State zone4Mute;
|
||||
private State zone4Input;
|
||||
|
||||
private DenonMarantzStateChangedListener handler;
|
||||
|
||||
public DenonMarantzState(DenonMarantzStateChangedListener handler) {
|
||||
this.handler = handler;
|
||||
}
|
||||
|
||||
public void connectionError(String errorMessage) {
|
||||
handler.connectionError(errorMessage);
|
||||
}
|
||||
|
||||
public State getStateForChannelID(String channelID) {
|
||||
switch (channelID) {
|
||||
case DenonMarantzBindingConstants.CHANNEL_POWER:
|
||||
return power;
|
||||
case DenonMarantzBindingConstants.CHANNEL_MAIN_ZONE_POWER:
|
||||
return mainZonePower;
|
||||
case DenonMarantzBindingConstants.CHANNEL_MUTE:
|
||||
return mute;
|
||||
case DenonMarantzBindingConstants.CHANNEL_MAIN_VOLUME:
|
||||
return mainVolume;
|
||||
case DenonMarantzBindingConstants.CHANNEL_MAIN_VOLUME_DB:
|
||||
return mainVolumeDB;
|
||||
case DenonMarantzBindingConstants.CHANNEL_INPUT:
|
||||
return input;
|
||||
case DenonMarantzBindingConstants.CHANNEL_SURROUND_PROGRAM:
|
||||
return surroundProgram;
|
||||
|
||||
case DenonMarantzBindingConstants.CHANNEL_NOW_PLAYING_ARTIST:
|
||||
return artist;
|
||||
case DenonMarantzBindingConstants.CHANNEL_NOW_PLAYING_ALBUM:
|
||||
return album;
|
||||
case DenonMarantzBindingConstants.CHANNEL_NOW_PLAYING_TRACK:
|
||||
return track;
|
||||
|
||||
case DenonMarantzBindingConstants.CHANNEL_ZONE2_POWER:
|
||||
return zone2Power;
|
||||
case DenonMarantzBindingConstants.CHANNEL_ZONE2_VOLUME:
|
||||
return zone2Volume;
|
||||
case DenonMarantzBindingConstants.CHANNEL_ZONE2_VOLUME_DB:
|
||||
return zone2VolumeDB;
|
||||
case DenonMarantzBindingConstants.CHANNEL_ZONE2_MUTE:
|
||||
return zone2Mute;
|
||||
case DenonMarantzBindingConstants.CHANNEL_ZONE2_INPUT:
|
||||
return zone2Input;
|
||||
|
||||
case DenonMarantzBindingConstants.CHANNEL_ZONE3_POWER:
|
||||
return zone3Power;
|
||||
case DenonMarantzBindingConstants.CHANNEL_ZONE3_VOLUME:
|
||||
return zone3Volume;
|
||||
case DenonMarantzBindingConstants.CHANNEL_ZONE3_VOLUME_DB:
|
||||
return zone3VolumeDB;
|
||||
case DenonMarantzBindingConstants.CHANNEL_ZONE3_MUTE:
|
||||
return zone3Mute;
|
||||
case DenonMarantzBindingConstants.CHANNEL_ZONE3_INPUT:
|
||||
return zone3Input;
|
||||
|
||||
case DenonMarantzBindingConstants.CHANNEL_ZONE4_POWER:
|
||||
return zone4Power;
|
||||
case DenonMarantzBindingConstants.CHANNEL_ZONE4_VOLUME:
|
||||
return zone4Volume;
|
||||
case DenonMarantzBindingConstants.CHANNEL_ZONE4_VOLUME_DB:
|
||||
return zone4VolumeDB;
|
||||
case DenonMarantzBindingConstants.CHANNEL_ZONE4_MUTE:
|
||||
return zone4Mute;
|
||||
case DenonMarantzBindingConstants.CHANNEL_ZONE4_INPUT:
|
||||
return zone4Input;
|
||||
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public void setPower(boolean power) {
|
||||
OnOffType newVal = power ? OnOffType.ON : OnOffType.OFF;
|
||||
if (newVal != this.power) {
|
||||
this.power = newVal;
|
||||
handler.stateChanged(DenonMarantzBindingConstants.CHANNEL_POWER, this.power);
|
||||
}
|
||||
}
|
||||
|
||||
public void setMainZonePower(boolean mainPower) {
|
||||
OnOffType newVal = mainPower ? OnOffType.ON : OnOffType.OFF;
|
||||
if (newVal != this.mainZonePower) {
|
||||
this.mainZonePower = newVal;
|
||||
handler.stateChanged(DenonMarantzBindingConstants.CHANNEL_MAIN_ZONE_POWER, this.mainZonePower);
|
||||
}
|
||||
}
|
||||
|
||||
public void setMute(boolean mute) {
|
||||
OnOffType newVal = mute ? OnOffType.ON : OnOffType.OFF;
|
||||
if (newVal != this.mute) {
|
||||
this.mute = newVal;
|
||||
handler.stateChanged(DenonMarantzBindingConstants.CHANNEL_MUTE, this.mute);
|
||||
}
|
||||
}
|
||||
|
||||
public void setMainVolume(BigDecimal volume) {
|
||||
PercentType newVal = new PercentType(volume);
|
||||
if (!newVal.equals(this.mainVolume)) {
|
||||
this.mainVolume = newVal;
|
||||
handler.stateChanged(DenonMarantzBindingConstants.CHANNEL_MAIN_VOLUME, this.mainVolume);
|
||||
// update the main volume in dB too
|
||||
this.mainVolumeDB = DecimalType.valueOf(volume.subtract(DenonMarantzBindingConstants.DB_OFFSET).toString());
|
||||
handler.stateChanged(DenonMarantzBindingConstants.CHANNEL_MAIN_VOLUME_DB, this.mainVolumeDB);
|
||||
}
|
||||
}
|
||||
|
||||
public void setInput(String input) {
|
||||
StringType newVal = StringType.valueOf(input);
|
||||
if (!newVal.equals(this.input)) {
|
||||
this.input = newVal;
|
||||
handler.stateChanged(DenonMarantzBindingConstants.CHANNEL_INPUT, this.input);
|
||||
}
|
||||
}
|
||||
|
||||
public void setSurroundProgram(String surroundProgram) {
|
||||
StringType newVal = StringType.valueOf(surroundProgram);
|
||||
if (!newVal.equals(this.surroundProgram)) {
|
||||
this.surroundProgram = newVal;
|
||||
handler.stateChanged(DenonMarantzBindingConstants.CHANNEL_SURROUND_PROGRAM, this.surroundProgram);
|
||||
}
|
||||
}
|
||||
|
||||
public void setNowPlayingArtist(String artist) {
|
||||
StringType newVal = StringUtils.isBlank(artist) ? StringType.EMPTY : StringType.valueOf(artist);
|
||||
if (!newVal.equals(this.artist)) {
|
||||
this.artist = newVal;
|
||||
handler.stateChanged(DenonMarantzBindingConstants.CHANNEL_NOW_PLAYING_ARTIST, this.artist);
|
||||
}
|
||||
}
|
||||
|
||||
public void setNowPlayingAlbum(String album) {
|
||||
StringType newVal = StringUtils.isBlank(album) ? StringType.EMPTY : StringType.valueOf(album);
|
||||
if (!newVal.equals(this.album)) {
|
||||
this.album = newVal;
|
||||
handler.stateChanged(DenonMarantzBindingConstants.CHANNEL_NOW_PLAYING_ALBUM, this.album);
|
||||
}
|
||||
}
|
||||
|
||||
public void setNowPlayingTrack(String track) {
|
||||
StringType newVal = StringUtils.isBlank(track) ? StringType.EMPTY : StringType.valueOf(track);
|
||||
if (!newVal.equals(this.track)) {
|
||||
this.track = newVal;
|
||||
handler.stateChanged(DenonMarantzBindingConstants.CHANNEL_NOW_PLAYING_TRACK, this.track);
|
||||
}
|
||||
}
|
||||
|
||||
public void setZone2Power(boolean power) {
|
||||
OnOffType newVal = power ? OnOffType.ON : OnOffType.OFF;
|
||||
if (newVal != this.zone2Power) {
|
||||
this.zone2Power = newVal;
|
||||
handler.stateChanged(DenonMarantzBindingConstants.CHANNEL_ZONE2_POWER, this.zone2Power);
|
||||
}
|
||||
}
|
||||
|
||||
public void setZone2Volume(BigDecimal volume) {
|
||||
PercentType newVal = new PercentType(volume);
|
||||
if (!newVal.equals(this.zone2Volume)) {
|
||||
this.zone2Volume = newVal;
|
||||
handler.stateChanged(DenonMarantzBindingConstants.CHANNEL_ZONE2_VOLUME, this.zone2Volume);
|
||||
// update the volume in dB too
|
||||
this.zone2VolumeDB = DecimalType
|
||||
.valueOf(volume.subtract(DenonMarantzBindingConstants.DB_OFFSET).toString());
|
||||
handler.stateChanged(DenonMarantzBindingConstants.CHANNEL_ZONE2_VOLUME_DB, this.zone2VolumeDB);
|
||||
}
|
||||
}
|
||||
|
||||
public void setZone2Mute(boolean mute) {
|
||||
OnOffType newVal = mute ? OnOffType.ON : OnOffType.OFF;
|
||||
if (newVal != this.zone2Mute) {
|
||||
this.zone2Mute = newVal;
|
||||
handler.stateChanged(DenonMarantzBindingConstants.CHANNEL_ZONE2_MUTE, this.zone2Mute);
|
||||
}
|
||||
}
|
||||
|
||||
public void setZone2Input(String zone2Input) {
|
||||
StringType newVal = StringType.valueOf(zone2Input);
|
||||
if (!newVal.equals(this.zone2Input)) {
|
||||
this.zone2Input = newVal;
|
||||
handler.stateChanged(DenonMarantzBindingConstants.CHANNEL_ZONE2_INPUT, this.zone2Input);
|
||||
}
|
||||
}
|
||||
|
||||
public void setZone3Power(boolean power) {
|
||||
OnOffType newVal = power ? OnOffType.ON : OnOffType.OFF;
|
||||
if (newVal != this.zone3Power) {
|
||||
this.zone3Power = newVal;
|
||||
handler.stateChanged(DenonMarantzBindingConstants.CHANNEL_ZONE3_POWER, this.zone3Power);
|
||||
}
|
||||
}
|
||||
|
||||
public void setZone3Volume(BigDecimal volume) {
|
||||
PercentType newVal = new PercentType(volume);
|
||||
if (!newVal.equals(this.zone3Volume)) {
|
||||
this.zone3Volume = newVal;
|
||||
handler.stateChanged(DenonMarantzBindingConstants.CHANNEL_ZONE3_VOLUME, this.zone3Volume);
|
||||
// update the volume in dB too
|
||||
this.zone3VolumeDB = DecimalType
|
||||
.valueOf(volume.subtract(DenonMarantzBindingConstants.DB_OFFSET).toString());
|
||||
handler.stateChanged(DenonMarantzBindingConstants.CHANNEL_ZONE3_VOLUME_DB, this.zone3VolumeDB);
|
||||
}
|
||||
}
|
||||
|
||||
public void setZone3Mute(boolean mute) {
|
||||
OnOffType newVal = mute ? OnOffType.ON : OnOffType.OFF;
|
||||
if (newVal != this.zone3Mute) {
|
||||
this.zone3Mute = newVal;
|
||||
handler.stateChanged(DenonMarantzBindingConstants.CHANNEL_ZONE3_MUTE, this.zone3Mute);
|
||||
}
|
||||
}
|
||||
|
||||
public void setZone3Input(String zone3Input) {
|
||||
StringType newVal = StringType.valueOf(zone3Input);
|
||||
if (!newVal.equals(this.zone3Input)) {
|
||||
this.zone3Input = newVal;
|
||||
handler.stateChanged(DenonMarantzBindingConstants.CHANNEL_ZONE2_INPUT, this.zone3Input);
|
||||
}
|
||||
}
|
||||
|
||||
public void setZone4Power(boolean power) {
|
||||
OnOffType newVal = power ? OnOffType.ON : OnOffType.OFF;
|
||||
if (newVal != this.zone4Power) {
|
||||
this.zone4Power = newVal;
|
||||
handler.stateChanged(DenonMarantzBindingConstants.CHANNEL_ZONE4_POWER, this.zone4Power);
|
||||
}
|
||||
}
|
||||
|
||||
public void setZone4Volume(BigDecimal volume) {
|
||||
PercentType newVal = new PercentType(volume);
|
||||
if (!newVal.equals(this.zone4Volume)) {
|
||||
this.zone4Volume = newVal;
|
||||
handler.stateChanged(DenonMarantzBindingConstants.CHANNEL_ZONE4_VOLUME, this.zone4Volume);
|
||||
// update the volume in dB too
|
||||
this.zone4VolumeDB = DecimalType
|
||||
.valueOf(volume.subtract(DenonMarantzBindingConstants.DB_OFFSET).toString());
|
||||
handler.stateChanged(DenonMarantzBindingConstants.CHANNEL_ZONE4_VOLUME_DB, this.zone4VolumeDB);
|
||||
}
|
||||
}
|
||||
|
||||
public void setZone4Mute(boolean mute) {
|
||||
OnOffType newVal = mute ? OnOffType.ON : OnOffType.OFF;
|
||||
if (newVal != this.zone4Mute) {
|
||||
this.zone4Mute = newVal;
|
||||
handler.stateChanged(DenonMarantzBindingConstants.CHANNEL_ZONE4_MUTE, this.zone4Mute);
|
||||
}
|
||||
}
|
||||
|
||||
public void setZone4Input(String zone4Input) {
|
||||
StringType newVal = StringType.valueOf(zone4Input);
|
||||
if (!newVal.equals(this.zone4Input)) {
|
||||
this.zone4Input = newVal;
|
||||
handler.stateChanged(DenonMarantzBindingConstants.CHANNEL_ZONE4_INPUT, this.zone4Input);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.denonmarantz.internal;
|
||||
|
||||
import org.openhab.binding.denonmarantz.internal.handler.DenonMarantzHandler;
|
||||
import org.openhab.core.types.State;
|
||||
|
||||
/**
|
||||
* Interface to notify the {@link DenonMarantzHandler} about state changes.
|
||||
*
|
||||
* @author Jan-Willem Veldhuis - Initial contribution
|
||||
*
|
||||
*/
|
||||
public interface DenonMarantzStateChangedListener {
|
||||
/**
|
||||
* Update was received.
|
||||
*
|
||||
* @param channelID the channel for which its state changed
|
||||
* @param state the new state of the channel
|
||||
*/
|
||||
void stateChanged(String channelID, State state);
|
||||
|
||||
/**
|
||||
* A connection error occurred
|
||||
*
|
||||
* @param errorMessage the error message
|
||||
*/
|
||||
void connectionError(String errorMessage);
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* 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.denonmarantz.internal;
|
||||
|
||||
/**
|
||||
* Exception thrown when an unsupported command type is sent to a channel.
|
||||
*
|
||||
* @author Jan-Willem Veldhuis - Initial contribution
|
||||
*
|
||||
*/
|
||||
public class UnsupportedCommandTypeException extends Exception {
|
||||
|
||||
private static final long serialVersionUID = 42L;
|
||||
|
||||
public UnsupportedCommandTypeException() {
|
||||
super();
|
||||
}
|
||||
|
||||
public UnsupportedCommandTypeException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
/**
|
||||
* 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.denonmarantz.internal.config;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
|
||||
import org.openhab.binding.denonmarantz.internal.connector.DenonMarantzConnector;
|
||||
|
||||
/**
|
||||
* Configuration class for the Denon Marantz binding.
|
||||
*
|
||||
* @author Jan-Willem Veldhuis - Initial contribution
|
||||
*
|
||||
*/
|
||||
public class DenonMarantzConfiguration {
|
||||
|
||||
/**
|
||||
* The hostname (or IP Address) of the Denon Marantz AVR
|
||||
*/
|
||||
public String host;
|
||||
|
||||
/**
|
||||
* Whether Telnet communication is enabled
|
||||
*/
|
||||
public Boolean telnetEnabled;
|
||||
|
||||
/**
|
||||
* The telnet port
|
||||
*/
|
||||
public Integer telnetPort;
|
||||
|
||||
/**
|
||||
* The HTTP port
|
||||
*/
|
||||
public Integer httpPort;
|
||||
|
||||
/**
|
||||
* The interval to poll the AVR over HTTP for changes
|
||||
*/
|
||||
public Integer httpPollingInterval;
|
||||
|
||||
// Default maximum volume
|
||||
public static final BigDecimal MAX_VOLUME = new BigDecimal("98");
|
||||
|
||||
private DenonMarantzConnector connector;
|
||||
|
||||
private Integer zoneCount;
|
||||
|
||||
private BigDecimal mainVolumeMax = MAX_VOLUME;
|
||||
|
||||
public List<String> inputOptions;
|
||||
|
||||
public String getHost() {
|
||||
return host;
|
||||
}
|
||||
|
||||
public void setHost(String host) {
|
||||
this.host = host;
|
||||
}
|
||||
|
||||
public Boolean isTelnet() {
|
||||
return telnetEnabled;
|
||||
}
|
||||
|
||||
public void setTelnet(boolean telnet) {
|
||||
this.telnetEnabled = telnet;
|
||||
}
|
||||
|
||||
public Integer getTelnetPort() {
|
||||
return telnetPort;
|
||||
}
|
||||
|
||||
public void setTelnetPort(Integer telnetPort) {
|
||||
this.telnetPort = telnetPort;
|
||||
}
|
||||
|
||||
public Integer getHttpPort() {
|
||||
return httpPort;
|
||||
}
|
||||
|
||||
public void setHttpPort(Integer httpPort) {
|
||||
this.httpPort = httpPort;
|
||||
}
|
||||
|
||||
public DenonMarantzConnector getConnector() {
|
||||
return connector;
|
||||
}
|
||||
|
||||
public void setConnector(DenonMarantzConnector connector) {
|
||||
this.connector = connector;
|
||||
}
|
||||
|
||||
public BigDecimal getMainVolumeMax() {
|
||||
return mainVolumeMax;
|
||||
}
|
||||
|
||||
public void setMainVolumeMax(BigDecimal mainVolumeMax) {
|
||||
this.mainVolumeMax = mainVolumeMax;
|
||||
}
|
||||
|
||||
public Integer getZoneCount() {
|
||||
return zoneCount;
|
||||
}
|
||||
|
||||
public void setZoneCount(Integer count) {
|
||||
Integer zoneCount = count;
|
||||
this.zoneCount = zoneCount;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,208 @@
|
||||
/**
|
||||
* 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.denonmarantz.internal.connector;
|
||||
|
||||
import static org.openhab.binding.denonmarantz.internal.DenonMarantzBindingConstants.DB_OFFSET;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
|
||||
import org.openhab.binding.denonmarantz.internal.DenonMarantzState;
|
||||
import org.openhab.binding.denonmarantz.internal.UnsupportedCommandTypeException;
|
||||
import org.openhab.binding.denonmarantz.internal.config.DenonMarantzConfiguration;
|
||||
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.types.RefreshType;
|
||||
|
||||
/**
|
||||
* Abstract class containing common functionality for the connectors.
|
||||
*
|
||||
* @author Jan-Willem Veldhuis - Initial contribution
|
||||
*/
|
||||
public abstract class DenonMarantzConnector {
|
||||
|
||||
private static final BigDecimal POINTFIVE = new BigDecimal("0.5");
|
||||
protected ScheduledExecutorService scheduler;
|
||||
protected DenonMarantzState state;
|
||||
protected DenonMarantzConfiguration config;
|
||||
|
||||
public abstract void connect();
|
||||
|
||||
public abstract void dispose();
|
||||
|
||||
protected abstract void internalSendCommand(String command);
|
||||
|
||||
public void sendCustomCommand(Command command) throws UnsupportedCommandTypeException {
|
||||
String cmd;
|
||||
if (command instanceof StringType) {
|
||||
cmd = command.toString();
|
||||
} else {
|
||||
throw new UnsupportedCommandTypeException();
|
||||
}
|
||||
internalSendCommand(cmd);
|
||||
}
|
||||
|
||||
public void sendInputCommand(Command command, int zone) throws UnsupportedCommandTypeException {
|
||||
String zonePrefix;
|
||||
switch (zone) {
|
||||
case 1:
|
||||
zonePrefix = "SI";
|
||||
break;
|
||||
case 2:
|
||||
case 3:
|
||||
case 4:
|
||||
zonePrefix = "Z" + zone;
|
||||
break;
|
||||
default:
|
||||
throw new UnsupportedCommandTypeException("Zone must be in range [1-4], zone: " + zone);
|
||||
}
|
||||
String cmd = zonePrefix;
|
||||
if (command instanceof StringType) {
|
||||
cmd += command.toString();
|
||||
} else if (command instanceof RefreshType) {
|
||||
cmd += "?";
|
||||
} else {
|
||||
throw new UnsupportedCommandTypeException();
|
||||
}
|
||||
internalSendCommand(cmd);
|
||||
}
|
||||
|
||||
public void sendSurroundProgramCommand(Command command) throws UnsupportedCommandTypeException {
|
||||
String cmd = "MS";
|
||||
if (command instanceof RefreshType) {
|
||||
cmd += "?";
|
||||
} else {
|
||||
throw new UnsupportedCommandTypeException();
|
||||
}
|
||||
internalSendCommand(cmd);
|
||||
}
|
||||
|
||||
public void sendMuteCommand(Command command, int zone) throws UnsupportedCommandTypeException {
|
||||
if (zone < 1 || zone > 4) {
|
||||
throw new UnsupportedCommandTypeException("Zone must be in range [1-4], zone: " + zone);
|
||||
}
|
||||
StringBuilder sb = new StringBuilder();
|
||||
if (zone != 1) {
|
||||
sb.append("Z").append(zone);
|
||||
}
|
||||
sb.append("MU");
|
||||
String cmd = sb.toString();
|
||||
if (command == OnOffType.ON) {
|
||||
cmd += "ON";
|
||||
} else if (command == OnOffType.OFF) {
|
||||
cmd += "OFF";
|
||||
} else if (command instanceof RefreshType) {
|
||||
cmd += "?";
|
||||
} else {
|
||||
throw new UnsupportedCommandTypeException();
|
||||
}
|
||||
internalSendCommand(cmd);
|
||||
}
|
||||
|
||||
public void sendPowerCommand(Command command, int zone) throws UnsupportedCommandTypeException {
|
||||
String zonePrefix;
|
||||
switch (zone) {
|
||||
case 0:
|
||||
zonePrefix = "PW";
|
||||
break;
|
||||
case 1:
|
||||
zonePrefix = "ZM";
|
||||
break;
|
||||
case 2:
|
||||
case 3:
|
||||
case 4:
|
||||
zonePrefix = "Z" + zone;
|
||||
break;
|
||||
default:
|
||||
throw new UnsupportedCommandTypeException("Zone must be in range [0-4], zone: " + zone);
|
||||
}
|
||||
String cmd = zonePrefix;
|
||||
if (command == OnOffType.ON) {
|
||||
cmd += "ON";
|
||||
} else if (command == OnOffType.OFF) {
|
||||
cmd += (zone == 0) ? "STANDBY" : "OFF";
|
||||
} else if (command instanceof RefreshType) {
|
||||
cmd += "?";
|
||||
} else {
|
||||
throw new UnsupportedCommandTypeException();
|
||||
}
|
||||
internalSendCommand(cmd);
|
||||
}
|
||||
|
||||
public void sendVolumeCommand(Command command, int zone) throws UnsupportedCommandTypeException {
|
||||
String zonePrefix;
|
||||
switch (zone) {
|
||||
case 1:
|
||||
zonePrefix = "MV";
|
||||
break;
|
||||
case 2:
|
||||
case 3:
|
||||
case 4:
|
||||
zonePrefix = "Z" + zone;
|
||||
break;
|
||||
default:
|
||||
throw new UnsupportedCommandTypeException("Zone must be in range [1-4], zone: " + zone);
|
||||
}
|
||||
String cmd = zonePrefix;
|
||||
if (command instanceof RefreshType) {
|
||||
cmd += "?";
|
||||
} else if (command == IncreaseDecreaseType.INCREASE) {
|
||||
cmd += "UP";
|
||||
} else if (command == IncreaseDecreaseType.DECREASE) {
|
||||
cmd += "DOWN";
|
||||
} else if (command instanceof DecimalType) {
|
||||
cmd += toDenonValue(((DecimalType) command));
|
||||
} else if (command instanceof PercentType) {
|
||||
cmd += percentToDenonValue(((PercentType) command).toBigDecimal());
|
||||
} else {
|
||||
throw new UnsupportedCommandTypeException();
|
||||
}
|
||||
internalSendCommand(cmd);
|
||||
}
|
||||
|
||||
public void sendVolumeDbCommand(Command command, int zone) throws UnsupportedCommandTypeException {
|
||||
Command dbCommand = command;
|
||||
if (dbCommand instanceof PercentType) {
|
||||
throw new UnsupportedCommandTypeException();
|
||||
} else if (dbCommand instanceof DecimalType) {
|
||||
// convert dB to 'normal' volume by adding the offset of 80
|
||||
dbCommand = new DecimalType(((DecimalType) command).toBigDecimal().add(DB_OFFSET));
|
||||
}
|
||||
sendVolumeCommand(dbCommand, zone);
|
||||
}
|
||||
|
||||
protected String toDenonValue(DecimalType number) {
|
||||
String dbString = String.valueOf(number.intValue());
|
||||
BigDecimal num = number.toBigDecimal();
|
||||
if (num.compareTo(BigDecimal.TEN) == -1) {
|
||||
dbString = "0" + dbString;
|
||||
}
|
||||
if (num.remainder(BigDecimal.ONE).equals(POINTFIVE)) {
|
||||
dbString = dbString + "5";
|
||||
}
|
||||
return dbString;
|
||||
}
|
||||
|
||||
protected String percentToDenonValue(BigDecimal pct) {
|
||||
// Round to nearest number divisible by 0.5
|
||||
BigDecimal percent = pct.divide(POINTFIVE).setScale(0, RoundingMode.UP).multiply(POINTFIVE)
|
||||
.min(config.getMainVolumeMax()).max(BigDecimal.ZERO);
|
||||
|
||||
return toDenonValue(new DecimalType(percent));
|
||||
}
|
||||
}
|
||||
@@ -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.denonmarantz.internal.connector;
|
||||
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.openhab.binding.denonmarantz.internal.DenonMarantzState;
|
||||
import org.openhab.binding.denonmarantz.internal.config.DenonMarantzConfiguration;
|
||||
import org.openhab.binding.denonmarantz.internal.connector.http.DenonMarantzHttpConnector;
|
||||
import org.openhab.binding.denonmarantz.internal.connector.telnet.DenonMarantzTelnetConnector;
|
||||
|
||||
/**
|
||||
* Returns the connector based on the configuration.
|
||||
* Currently there are 2 types: HTTP and Telnet
|
||||
*
|
||||
* @author Jan-Willem Veldhuis - Initial contribution
|
||||
*/
|
||||
public class DenonMarantzConnectorFactory {
|
||||
|
||||
public DenonMarantzConnector getConnector(DenonMarantzConfiguration config, DenonMarantzState state,
|
||||
ScheduledExecutorService scheduler, HttpClient httpClient) {
|
||||
if (config.isTelnet()) {
|
||||
return new DenonMarantzTelnetConnector(config, state, scheduler);
|
||||
} else {
|
||||
return new DenonMarantzHttpConnector(config, state, scheduler, httpClient);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,373 @@
|
||||
/**
|
||||
* 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.denonmarantz.internal.connector.http;
|
||||
|
||||
import java.beans.Introspector;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.StringWriter;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import javax.xml.bind.JAXBContext;
|
||||
import javax.xml.bind.JAXBException;
|
||||
import javax.xml.bind.Marshaller;
|
||||
import javax.xml.bind.UnmarshalException;
|
||||
import javax.xml.stream.XMLInputFactory;
|
||||
import javax.xml.stream.XMLStreamException;
|
||||
import javax.xml.stream.XMLStreamReader;
|
||||
import javax.xml.stream.util.StreamReaderDelegate;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.eclipse.jetty.client.api.Response;
|
||||
import org.eclipse.jetty.client.api.Result;
|
||||
import org.openhab.binding.denonmarantz.internal.DenonMarantzState;
|
||||
import org.openhab.binding.denonmarantz.internal.config.DenonMarantzConfiguration;
|
||||
import org.openhab.binding.denonmarantz.internal.connector.DenonMarantzConnector;
|
||||
import org.openhab.binding.denonmarantz.internal.xml.entities.Deviceinfo;
|
||||
import org.openhab.binding.denonmarantz.internal.xml.entities.Main;
|
||||
import org.openhab.binding.denonmarantz.internal.xml.entities.ZoneStatus;
|
||||
import org.openhab.binding.denonmarantz.internal.xml.entities.ZoneStatusLite;
|
||||
import org.openhab.binding.denonmarantz.internal.xml.entities.commands.AppCommandRequest;
|
||||
import org.openhab.binding.denonmarantz.internal.xml.entities.commands.AppCommandResponse;
|
||||
import org.openhab.binding.denonmarantz.internal.xml.entities.commands.CommandRx;
|
||||
import org.openhab.binding.denonmarantz.internal.xml.entities.commands.CommandTx;
|
||||
import org.openhab.core.io.net.http.HttpUtil;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* This class makes the connection to the receiver and manages it.
|
||||
* It is also responsible for sending commands to the receiver.
|
||||
* *
|
||||
*
|
||||
* @author Jeroen Idserda - Initial Contribution (1.x Binding)
|
||||
* @author Jan-Willem Veldhuis - Refactored for 2.x
|
||||
*/
|
||||
public class DenonMarantzHttpConnector extends DenonMarantzConnector {
|
||||
|
||||
private Logger logger = LoggerFactory.getLogger(DenonMarantzHttpConnector.class);
|
||||
|
||||
private static final int REQUEST_TIMEOUT_MS = 5000; // 5 seconds
|
||||
|
||||
// Main URL for the receiver
|
||||
private static final String URL_MAIN = "formMainZone_MainZoneXml.xml";
|
||||
|
||||
// Main Zone Status URL
|
||||
private static final String URL_ZONE_MAIN = "formMainZone_MainZoneXmlStatus.xml";
|
||||
|
||||
// Secondary zone lite status URL (contains less info)
|
||||
private static final String URL_ZONE_SECONDARY_LITE = "formZone%d_Zone%dXmlStatusLite.xml";
|
||||
|
||||
// Device info URL
|
||||
private static final String URL_DEVICE_INFO = "Deviceinfo.xml";
|
||||
|
||||
// URL to send app commands to
|
||||
private static final String URL_APP_COMMAND = "AppCommand.xml";
|
||||
|
||||
private static final String CONTENT_TYPE_XML = "application/xml";
|
||||
|
||||
private final String cmdUrl;
|
||||
|
||||
private final String statusUrl;
|
||||
|
||||
private final HttpClient httpClient;
|
||||
|
||||
private ScheduledFuture<?> pollingJob;
|
||||
|
||||
public DenonMarantzHttpConnector(DenonMarantzConfiguration config, DenonMarantzState state,
|
||||
ScheduledExecutorService scheduler, HttpClient httpClient) {
|
||||
this.config = config;
|
||||
this.scheduler = scheduler;
|
||||
this.state = state;
|
||||
this.cmdUrl = String.format("http://%s:%d/goform/formiPhoneAppDirect.xml?", config.getHost(),
|
||||
config.getHttpPort());
|
||||
this.statusUrl = String.format("http://%s:%d/goform/", config.getHost(), config.getHttpPort());
|
||||
this.httpClient = httpClient;
|
||||
}
|
||||
|
||||
public DenonMarantzState getState() {
|
||||
return state;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up the connection to the receiver by starting to poll the HTTP API.
|
||||
*/
|
||||
@Override
|
||||
public void connect() {
|
||||
if (!isPolling()) {
|
||||
logger.debug("HTTP polling started.");
|
||||
try {
|
||||
setConfigProperties();
|
||||
} catch (IOException e) {
|
||||
logger.debug("IO error while retrieving document:", e);
|
||||
state.connectionError("IO error while connecting to AVR: " + e.getMessage());
|
||||
return;
|
||||
}
|
||||
|
||||
pollingJob = scheduler.scheduleWithFixedDelay(() -> {
|
||||
try {
|
||||
refreshHttpProperties();
|
||||
} catch (IOException e) {
|
||||
logger.debug("IO error while retrieving document", e);
|
||||
state.connectionError("IO error while connecting to AVR: " + e.getMessage());
|
||||
stopPolling();
|
||||
} catch (RuntimeException e) {
|
||||
/**
|
||||
* We need to catch this RuntimeException, as otherwise the polling stops.
|
||||
* Log as error as it could be a user configuration error.
|
||||
*/
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (StackTraceElement s : e.getStackTrace()) {
|
||||
sb.append(s.toString()).append("\n");
|
||||
}
|
||||
logger.error("Error while polling Http: \"{}\". Stacktrace: \n{}", e.getMessage(), sb.toString());
|
||||
}
|
||||
}, 0, config.httpPollingInterval, TimeUnit.SECONDS);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isPolling() {
|
||||
return pollingJob != null && !pollingJob.isCancelled();
|
||||
}
|
||||
|
||||
private void stopPolling() {
|
||||
if (isPolling()) {
|
||||
pollingJob.cancel(true);
|
||||
logger.debug("HTTP polling stopped.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Shutdown the http client
|
||||
*/
|
||||
@Override
|
||||
public void dispose() {
|
||||
logger.debug("disposing connector");
|
||||
|
||||
stopPolling();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void internalSendCommand(String command) {
|
||||
logger.debug("Sending command '{}'", command);
|
||||
if (StringUtils.isBlank(command)) {
|
||||
logger.warn("Trying to send empty command");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
String url = cmdUrl + URLEncoder.encode(command, Charset.defaultCharset().displayName());
|
||||
logger.trace("Calling url {}", url);
|
||||
|
||||
httpClient.newRequest(url).timeout(5, TimeUnit.SECONDS).send(new Response.CompleteListener() {
|
||||
@Override
|
||||
public void onComplete(Result result) {
|
||||
if (result.getResponse().getStatus() != 200) {
|
||||
logger.warn("Error {} while sending command", result.getResponse().getReason());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
logger.warn("Error sending command", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateMain() throws IOException {
|
||||
String url = statusUrl + URL_MAIN;
|
||||
logger.trace("Refreshing URL: {}", url);
|
||||
|
||||
Main statusMain = getDocument(url, Main.class);
|
||||
if (statusMain != null) {
|
||||
state.setPower(statusMain.getPower().getValue());
|
||||
}
|
||||
}
|
||||
|
||||
private void updateMainZone() throws IOException {
|
||||
String url = statusUrl + URL_ZONE_MAIN;
|
||||
logger.trace("Refreshing URL: {}", url);
|
||||
|
||||
ZoneStatus mainZone = getDocument(url, ZoneStatus.class);
|
||||
if (mainZone != null) {
|
||||
state.setInput(mainZone.getInputFuncSelect().getValue());
|
||||
state.setMainVolume(mainZone.getMasterVolume().getValue());
|
||||
state.setMainZonePower(mainZone.getPower().getValue());
|
||||
state.setMute(mainZone.getMute().getValue());
|
||||
|
||||
if (config.inputOptions == null) {
|
||||
config.inputOptions = mainZone.getInputFuncList();
|
||||
}
|
||||
|
||||
if (mainZone.getSurrMode() == null) {
|
||||
logger.debug("Unable to get the SURROUND_MODE. MainZone update may not be correct.");
|
||||
} else {
|
||||
state.setSurroundProgram(mainZone.getSurrMode().getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void updateSecondaryZones() throws IOException {
|
||||
for (int i = 2; i <= config.getZoneCount(); i++) {
|
||||
String url = String.format("%s" + URL_ZONE_SECONDARY_LITE, statusUrl, i, i);
|
||||
logger.trace("Refreshing URL: {}", url);
|
||||
ZoneStatusLite zoneSecondary = getDocument(url, ZoneStatusLite.class);
|
||||
if (zoneSecondary != null) {
|
||||
switch (i) {
|
||||
// maximum 2 secondary zones are supported
|
||||
case 2:
|
||||
state.setZone2Power(zoneSecondary.getPower().getValue());
|
||||
state.setZone2Volume(zoneSecondary.getMasterVolume().getValue());
|
||||
state.setZone2Mute(zoneSecondary.getMute().getValue());
|
||||
state.setZone2Input(zoneSecondary.getInputFuncSelect().getValue());
|
||||
break;
|
||||
case 3:
|
||||
state.setZone3Power(zoneSecondary.getPower().getValue());
|
||||
state.setZone3Volume(zoneSecondary.getMasterVolume().getValue());
|
||||
state.setZone3Mute(zoneSecondary.getMute().getValue());
|
||||
state.setZone3Input(zoneSecondary.getInputFuncSelect().getValue());
|
||||
break;
|
||||
case 4:
|
||||
state.setZone4Power(zoneSecondary.getPower().getValue());
|
||||
state.setZone4Volume(zoneSecondary.getMasterVolume().getValue());
|
||||
state.setZone4Mute(zoneSecondary.getMute().getValue());
|
||||
state.setZone4Input(zoneSecondary.getInputFuncSelect().getValue());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void updateDisplayInfo() throws IOException {
|
||||
String url = statusUrl + URL_APP_COMMAND;
|
||||
logger.trace("Refreshing URL: {}", url);
|
||||
|
||||
AppCommandRequest request = AppCommandRequest.of(CommandTx.CMD_NET_STATUS);
|
||||
AppCommandResponse response = postDocument(url, AppCommandResponse.class, request);
|
||||
|
||||
if (response != null) {
|
||||
CommandRx titleInfo = response.getCommands().get(0);
|
||||
state.setNowPlayingArtist(titleInfo.getText("artist"));
|
||||
state.setNowPlayingAlbum(titleInfo.getText("album"));
|
||||
state.setNowPlayingTrack(titleInfo.getText("track"));
|
||||
}
|
||||
}
|
||||
|
||||
private boolean setConfigProperties() throws IOException {
|
||||
String url = statusUrl + URL_DEVICE_INFO;
|
||||
logger.debug("Refreshing URL: {}", url);
|
||||
|
||||
Deviceinfo deviceinfo = getDocument(url, Deviceinfo.class);
|
||||
if (deviceinfo != null) {
|
||||
config.setZoneCount(deviceinfo.getDeviceZones());
|
||||
}
|
||||
|
||||
/**
|
||||
* The maximum volume is received from the telnet connection in the
|
||||
* form of the MVMAX property. It is not always received reliable however,
|
||||
* so we're using a default for now.
|
||||
*/
|
||||
config.setMainVolumeMax(DenonMarantzConfiguration.MAX_VOLUME);
|
||||
|
||||
// if deviceinfo is null, something went wrong (and is logged in getDocument catch blocks)
|
||||
return (deviceinfo != null);
|
||||
}
|
||||
|
||||
private void refreshHttpProperties() throws IOException {
|
||||
logger.trace("Refreshing Denon status");
|
||||
|
||||
updateMain();
|
||||
updateMainZone();
|
||||
updateSecondaryZones();
|
||||
updateDisplayInfo();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private <T> T getDocument(String uri, Class<T> response) throws IOException {
|
||||
try {
|
||||
String result = HttpUtil.executeUrl("GET", uri, REQUEST_TIMEOUT_MS);
|
||||
logger.trace("result of getDocument for uri '{}':\r\n{}", uri, result);
|
||||
|
||||
if (StringUtils.isNotBlank(result)) {
|
||||
JAXBContext jc = JAXBContext.newInstance(response);
|
||||
XMLInputFactory xif = XMLInputFactory.newInstance();
|
||||
XMLStreamReader xsr = xif.createXMLStreamReader(IOUtils.toInputStream(result));
|
||||
xsr = new PropertyRenamerDelegate(xsr);
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
T obj = (T) jc.createUnmarshaller().unmarshal(xsr);
|
||||
|
||||
return obj;
|
||||
}
|
||||
} catch (UnmarshalException e) {
|
||||
logger.debug("Failed to unmarshal xml document: {}", e.getMessage());
|
||||
} catch (JAXBException e) {
|
||||
logger.debug("Unexpected error occurred during unmarshalling of document: {}", e.getMessage());
|
||||
} catch (XMLStreamException e) {
|
||||
logger.debug("Communication error: {}", e.getMessage());
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private <T, S> T postDocument(String uri, Class<T> response, S request) throws IOException {
|
||||
try {
|
||||
JAXBContext jaxbContext = JAXBContext.newInstance(request.getClass());
|
||||
Marshaller jaxbMarshaller = jaxbContext.createMarshaller();
|
||||
StringWriter sw = new StringWriter();
|
||||
jaxbMarshaller.marshal(request, sw);
|
||||
|
||||
ByteArrayInputStream inputStream = new ByteArrayInputStream(sw.toString().getBytes(StandardCharsets.UTF_8));
|
||||
String result = HttpUtil.executeUrl("POST", uri, inputStream, CONTENT_TYPE_XML, REQUEST_TIMEOUT_MS);
|
||||
|
||||
if (StringUtils.isNotBlank(result)) {
|
||||
JAXBContext jcResponse = JAXBContext.newInstance(response);
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
T obj = (T) jcResponse.createUnmarshaller().unmarshal(IOUtils.toInputStream(result));
|
||||
|
||||
return obj;
|
||||
}
|
||||
} catch (JAXBException e) {
|
||||
logger.debug("Encoding error in post", e);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static class PropertyRenamerDelegate extends StreamReaderDelegate {
|
||||
|
||||
public PropertyRenamerDelegate(XMLStreamReader xsr) {
|
||||
super(xsr);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAttributeLocalName(int index) {
|
||||
return Introspector.decapitalize(super.getAttributeLocalName(index));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLocalName() {
|
||||
return Introspector.decapitalize(super.getLocalName());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
/**
|
||||
* 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.denonmarantz.internal.connector.telnet;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketTimeoutException;
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.openhab.binding.denonmarantz.internal.config.DenonMarantzConfiguration;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Manage telnet connection to the Denon/Marantz Receiver
|
||||
*
|
||||
* @author Jeroen Idserda - Initial contribution (1.x Binding)
|
||||
* @author Jan-Willem Veldhuis - Refactored for 2.x
|
||||
*/
|
||||
public class DenonMarantzTelnetClient implements Runnable {
|
||||
|
||||
private Logger logger = LoggerFactory.getLogger(DenonMarantzTelnetClient.class);
|
||||
|
||||
private static final Integer RECONNECT_DELAY = 60000; // 1 minute
|
||||
|
||||
private static final Integer TIMEOUT = 60000; // 1 minute
|
||||
|
||||
private DenonMarantzConfiguration config;
|
||||
|
||||
private DenonMarantzTelnetListener listener;
|
||||
|
||||
private boolean running = true;
|
||||
|
||||
private boolean connected = false;
|
||||
|
||||
private Socket socket;
|
||||
|
||||
private OutputStreamWriter out;
|
||||
|
||||
private BufferedReader in;
|
||||
|
||||
public DenonMarantzTelnetClient(DenonMarantzConfiguration config, DenonMarantzTelnetListener listener) {
|
||||
logger.debug("Denon listener created");
|
||||
this.config = config;
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
while (running) {
|
||||
if (!connected) {
|
||||
connectTelnetSocket();
|
||||
}
|
||||
|
||||
do {
|
||||
try {
|
||||
String line = in.readLine();
|
||||
if (line == null) {
|
||||
logger.debug("No more data read from client. Disconnecting..");
|
||||
listener.telnetClientConnected(false);
|
||||
disconnect();
|
||||
break;
|
||||
}
|
||||
logger.trace("Received from {}: {}", config.getHost(), line);
|
||||
if (!StringUtils.isBlank(line)) {
|
||||
listener.receivedLine(line);
|
||||
}
|
||||
} catch (SocketTimeoutException e) {
|
||||
logger.trace("Socket timeout");
|
||||
// Disconnects are not always detected unless you write to the socket.
|
||||
try {
|
||||
out.write('\r');
|
||||
out.flush();
|
||||
} catch (IOException e2) {
|
||||
logger.debug("Error writing to socket");
|
||||
connected = false;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
if (!Thread.currentThread().isInterrupted()) {
|
||||
// only log if we don't stop this on purpose causing a SocketClosed
|
||||
logger.debug("Error in telnet connection ", e);
|
||||
}
|
||||
connected = false;
|
||||
listener.telnetClientConnected(false);
|
||||
}
|
||||
} while (running && connected);
|
||||
}
|
||||
}
|
||||
|
||||
public void sendCommand(String command) {
|
||||
if (out != null) {
|
||||
logger.debug("Sending command {}", command);
|
||||
try {
|
||||
out.write(command + '\r');
|
||||
out.flush();
|
||||
} catch (IOException e) {
|
||||
logger.debug("Error sending command", e);
|
||||
}
|
||||
} else {
|
||||
logger.debug("Cannot send command, no telnet connection");
|
||||
}
|
||||
}
|
||||
|
||||
public void shutdown() {
|
||||
this.running = false;
|
||||
disconnect();
|
||||
}
|
||||
|
||||
private void connectTelnetSocket() {
|
||||
disconnect();
|
||||
int delay = 0;
|
||||
|
||||
while (this.running && (socket == null || !socket.isConnected())) {
|
||||
try {
|
||||
Thread.sleep(delay);
|
||||
logger.debug("Connecting to {}", config.getHost());
|
||||
|
||||
// Use raw socket instead of TelnetClient here because TelnetClient sends an extra newline char
|
||||
// after each write which causes the connection to become unresponsive.
|
||||
socket = new Socket();
|
||||
socket.connect(new InetSocketAddress(config.getHost(), config.getTelnetPort()), TIMEOUT);
|
||||
socket.setKeepAlive(true);
|
||||
socket.setSoTimeout(TIMEOUT);
|
||||
|
||||
in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
|
||||
out = new OutputStreamWriter(socket.getOutputStream(), "UTF-8");
|
||||
|
||||
connected = true;
|
||||
listener.telnetClientConnected(true);
|
||||
} catch (IOException e) {
|
||||
logger.debug("Cannot connect to {}", config.getHost(), e);
|
||||
listener.telnetClientConnected(false);
|
||||
} catch (InterruptedException e) {
|
||||
logger.debug("Interrupted while connecting to {}", config.getHost(), e);
|
||||
}
|
||||
delay = RECONNECT_DELAY;
|
||||
}
|
||||
|
||||
logger.debug("Denon telnet client connected to {}", config.getHost());
|
||||
}
|
||||
|
||||
public boolean isConnected() {
|
||||
return connected;
|
||||
}
|
||||
|
||||
private void disconnect() {
|
||||
if (socket != null) {
|
||||
logger.debug("Disconnecting socket");
|
||||
try {
|
||||
socket.close();
|
||||
} catch (IOException e) {
|
||||
logger.debug("Error while disconnecting telnet client", e);
|
||||
} finally {
|
||||
socket = null;
|
||||
out = null;
|
||||
in = null;
|
||||
listener.telnetClientConnected(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,275 @@
|
||||
/**
|
||||
* 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.denonmarantz.internal.connector.telnet;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.openhab.binding.denonmarantz.internal.DenonMarantzState;
|
||||
import org.openhab.binding.denonmarantz.internal.config.DenonMarantzConfiguration;
|
||||
import org.openhab.binding.denonmarantz.internal.connector.DenonMarantzConnector;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* This class makes the connection to the receiver and manages it.
|
||||
* It is also responsible for sending commands to the receiver.
|
||||
*
|
||||
* @author Jeroen Idserda - Initial Contribution (1.x Binding)
|
||||
* @author Jan-Willem Veldhuis - Refactored for 2.x
|
||||
*/
|
||||
public class DenonMarantzTelnetConnector extends DenonMarantzConnector implements DenonMarantzTelnetListener {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(DenonMarantzTelnetConnector.class);
|
||||
|
||||
// All regular commands. Example: PW, SICD, SITV, Z2MU
|
||||
private static final Pattern COMMAND_PATTERN = Pattern.compile("^([A-Z0-9]{2})(.+)$");
|
||||
|
||||
// Example: E2Counting Crows
|
||||
private static final Pattern DISPLAY_PATTERN = Pattern.compile("^(E|A)([0-9]{1})(.+)$");
|
||||
|
||||
private static final BigDecimal NINETYNINE = new BigDecimal("99");
|
||||
|
||||
private DenonMarantzTelnetClient telnetClient;
|
||||
|
||||
private boolean displayNowplaying = false;
|
||||
|
||||
protected boolean disposing = false;
|
||||
|
||||
private Future<?> telnetStateRequest;
|
||||
|
||||
private Future<?> telnetRunnable;
|
||||
|
||||
public DenonMarantzTelnetConnector(DenonMarantzConfiguration config, DenonMarantzState state,
|
||||
ScheduledExecutorService scheduler) {
|
||||
this.config = config;
|
||||
this.scheduler = scheduler;
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up the connection to the receiver. Either using Telnet or by polling the HTTP API.
|
||||
*/
|
||||
@Override
|
||||
public void connect() {
|
||||
telnetClient = new DenonMarantzTelnetClient(config, this);
|
||||
telnetRunnable = scheduler.submit(telnetClient);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void telnetClientConnected(boolean connected) {
|
||||
if (!connected) {
|
||||
if (config.isTelnet() && !disposing) {
|
||||
logger.debug("Telnet client disconnected.");
|
||||
state.connectionError(
|
||||
"Error connecting to the telnet port. Consider disabling telnet in this Thing's configuration to use HTTP polling instead.");
|
||||
}
|
||||
} else {
|
||||
refreshState();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Shutdown the telnet client (if initialized) and the http client
|
||||
*/
|
||||
@Override
|
||||
public void dispose() {
|
||||
logger.debug("disposing connector");
|
||||
disposing = true;
|
||||
|
||||
if (telnetStateRequest != null) {
|
||||
telnetStateRequest.cancel(true);
|
||||
telnetStateRequest = null;
|
||||
}
|
||||
|
||||
if (telnetClient != null && !telnetRunnable.isDone()) {
|
||||
telnetRunnable.cancel(true);
|
||||
telnetClient.shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
private void refreshState() {
|
||||
// Sends a series of state query commands over the telnet connection
|
||||
telnetStateRequest = scheduler.submit(() -> {
|
||||
List<String> cmds = new ArrayList<>(Arrays.asList("PW?", "MS?", "MV?", "ZM?", "MU?", "SI?"));
|
||||
if (config.getZoneCount() > 1) {
|
||||
cmds.add("Z2?");
|
||||
cmds.add("Z2MU?");
|
||||
}
|
||||
if (config.getZoneCount() > 2) {
|
||||
cmds.add("Z3?");
|
||||
cmds.add("Z3MU?");
|
||||
}
|
||||
for (String cmd : cmds) {
|
||||
internalSendCommand(cmd);
|
||||
try {
|
||||
Thread.sleep(300);
|
||||
} catch (InterruptedException e) {
|
||||
logger.trace("requestStateOverTelnet() - Interrupted while requesting state.");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* This method tries to parse information received over the telnet connection.
|
||||
* It can be quite unreliable. Some chars go missing or turn into other chars. That's
|
||||
* why each command is validated using a regex.
|
||||
*
|
||||
* @param line The received command (one line)
|
||||
*/
|
||||
@Override
|
||||
public void receivedLine(String line) {
|
||||
if (COMMAND_PATTERN.matcher(line).matches()) {
|
||||
/*
|
||||
* This splits the commandString into the command and the parameter. SICD
|
||||
* for example has SI as the command and CD as the parameter.
|
||||
*/
|
||||
String command = line.substring(0, 2);
|
||||
String value = line.substring(2, line.length()).trim();
|
||||
|
||||
logger.debug("Received Command: {}, value: {}", command, value);
|
||||
|
||||
// use received command (event) from telnet to update state
|
||||
switch (command) {
|
||||
case "SI": // Switch Input
|
||||
state.setInput(value);
|
||||
break;
|
||||
case "PW": // Power
|
||||
if (value.equals("ON") || value.equals("STANDBY")) {
|
||||
state.setPower(value.equals("ON"));
|
||||
}
|
||||
break;
|
||||
case "MS": // Main zone surround program
|
||||
state.setSurroundProgram(value);
|
||||
break;
|
||||
case "MV": // Main zone volume
|
||||
if (StringUtils.isNumeric(value)) {
|
||||
state.setMainVolume(fromDenonValue(value));
|
||||
}
|
||||
break;
|
||||
case "MU": // Main zone mute
|
||||
if (value.equals("ON") || value.equals("OFF")) {
|
||||
state.setMute(value.equals("ON"));
|
||||
}
|
||||
break;
|
||||
case "NS": // Now playing information
|
||||
processTitleCommand(value);
|
||||
break;
|
||||
case "Z2": // Zone 2
|
||||
if (value.equals("ON") || value.equals("OFF")) {
|
||||
state.setZone2Power(value.equals("ON"));
|
||||
} else if (value.equals("MUON") || value.equals("MUOFF")) {
|
||||
state.setZone2Mute(value.equals("MUON"));
|
||||
} else if (StringUtils.isNumeric(value)) {
|
||||
state.setZone2Volume(fromDenonValue(value));
|
||||
} else {
|
||||
state.setZone2Input(value);
|
||||
}
|
||||
break;
|
||||
case "Z3": // Zone 3
|
||||
if (value.equals("ON") || value.equals("OFF")) {
|
||||
state.setZone3Power(value.equals("ON"));
|
||||
} else if (value.equals("MUON") || value.equals("MUOFF")) {
|
||||
state.setZone3Mute(value.equals("MUON"));
|
||||
} else if (StringUtils.isNumeric(value)) {
|
||||
state.setZone3Volume(fromDenonValue(value));
|
||||
} else {
|
||||
state.setZone3Input(value);
|
||||
}
|
||||
break;
|
||||
case "Z4": // Zone 4
|
||||
if (value.equals("ON") || value.equals("OFF")) {
|
||||
state.setZone4Power(value.equals("ON"));
|
||||
} else if (value.equals("MUON") || value.equals("MUOFF")) {
|
||||
state.setZone4Mute(value.equals("MUON"));
|
||||
} else if (StringUtils.isNumeric(value)) {
|
||||
state.setZone4Volume(fromDenonValue(value));
|
||||
} else {
|
||||
state.setZone4Input(value);
|
||||
}
|
||||
break;
|
||||
case "ZM": // Main zone
|
||||
if (value.equals("ON") || value.equals("OFF")) {
|
||||
state.setMainZonePower(value.equals("ON"));
|
||||
}
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
logger.trace("Ignoring received line: '{}'", line);
|
||||
}
|
||||
}
|
||||
|
||||
private BigDecimal fromDenonValue(String string) {
|
||||
/*
|
||||
* 455 = 45,5
|
||||
* 45 = 45
|
||||
* 045 = 4,5
|
||||
* 04 = 4
|
||||
*/
|
||||
BigDecimal value = new BigDecimal(string);
|
||||
if (value.compareTo(NINETYNINE) == 1 || (string.startsWith("0") && string.length() > 2)) {
|
||||
value = value.divide(BigDecimal.TEN);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
private void processTitleCommand(String value) {
|
||||
if (DISPLAY_PATTERN.matcher(value).matches()) {
|
||||
Integer commandNo = Integer.valueOf(value.substring(1, 2));
|
||||
String titleValue = value.substring(2);
|
||||
|
||||
if (commandNo == 0) {
|
||||
displayNowplaying = titleValue.contains("Now Playing");
|
||||
}
|
||||
|
||||
String nowPlaying = displayNowplaying ? cleanupDisplayInfo(titleValue) : "";
|
||||
|
||||
switch (commandNo) {
|
||||
case 1:
|
||||
state.setNowPlayingTrack(nowPlaying);
|
||||
break;
|
||||
case 2:
|
||||
state.setNowPlayingArtist(nowPlaying);
|
||||
break;
|
||||
case 4:
|
||||
state.setNowPlayingAlbum(nowPlaying);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void internalSendCommand(String command) {
|
||||
logger.debug("Sending command '{}'", command);
|
||||
if (StringUtils.isBlank(command)) {
|
||||
logger.warn("Trying to send empty command");
|
||||
return;
|
||||
}
|
||||
telnetClient.sendCommand(command);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display info could contain some garbled text, attempt to clean it up.
|
||||
*/
|
||||
private String cleanupDisplayInfo(String titleValue) {
|
||||
byte firstByteRemoved[] = Arrays.copyOfRange(titleValue.getBytes(), 1, titleValue.getBytes().length);
|
||||
return new String(firstByteRemoved).replaceAll("[\u0000-\u001f]", "");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/**
|
||||
* 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.denonmarantz.internal.connector.telnet;
|
||||
|
||||
import org.openhab.binding.denonmarantz.internal.connector.DenonMarantzConnector;
|
||||
|
||||
/**
|
||||
* Listener interface used to notify the {@link DenonMarantzConnector} about received messages over Telnet
|
||||
*
|
||||
* @author Jan-Willem Veldhuis - Initial contribution
|
||||
*
|
||||
*/
|
||||
public interface DenonMarantzTelnetListener {
|
||||
/**
|
||||
* The telnet client has received a line.
|
||||
*
|
||||
* @param line the received line
|
||||
*/
|
||||
void receivedLine(String line);
|
||||
|
||||
/**
|
||||
* The telnet client has successfully connected to the receiver.
|
||||
*
|
||||
* @param connected whether or not the connection was successful
|
||||
*/
|
||||
void telnetClientConnected(boolean connected);
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
/**
|
||||
* 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.denonmarantz.internal.discovery;
|
||||
|
||||
import static org.openhab.binding.denonmarantz.internal.DenonMarantzBindingConstants.*;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import javax.jmdns.ServiceInfo;
|
||||
|
||||
import org.openhab.core.config.discovery.DiscoveryResult;
|
||||
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
|
||||
import org.openhab.core.config.discovery.mdns.MDNSDiscoveryParticipant;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* @author Jan-Willem Veldhuis - Initial contribution
|
||||
*
|
||||
*/
|
||||
@Component(immediate = true)
|
||||
public class DenonMarantzDiscoveryParticipant implements MDNSDiscoveryParticipant {
|
||||
|
||||
private Logger logger = LoggerFactory.getLogger(DenonMarantzDiscoveryParticipant.class);
|
||||
|
||||
// Service type for 'Airplay enabled' receivers
|
||||
private static final String RAOP_SERVICE_TYPE = "_raop._tcp.local.";
|
||||
|
||||
/**
|
||||
* Match the serial number, vendor and model of the discovered AVR.
|
||||
* Input is like "0006781D58B1@Marantz SR5008._raop._tcp.local."
|
||||
* A Denon AVR serial (MAC address) starts with 0005CD
|
||||
* A Marantz AVR serial (MAC address) starts with 000678
|
||||
*/
|
||||
private static final Pattern DENON_MARANTZ_PATTERN = Pattern
|
||||
.compile("^((?:0005CD|000678)[A-Z0-9]+)@(.+)\\._raop\\._tcp\\.local\\.$");
|
||||
|
||||
/**
|
||||
* Denon AVRs have a MAC address / serial number starting with 0005CD
|
||||
*/
|
||||
private static final String DENON_MAC_PREFIX = "0005CD";
|
||||
|
||||
/**
|
||||
* Marantz AVRs have a MAC address / serial number starting with 000678
|
||||
*/
|
||||
private static final String MARANTZ_MAC_PREFIX = "000678";
|
||||
|
||||
@Override
|
||||
public Set<ThingTypeUID> getSupportedThingTypeUIDs() {
|
||||
return Collections.singleton(THING_TYPE_AVR);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getServiceType() {
|
||||
return RAOP_SERVICE_TYPE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DiscoveryResult createResult(ServiceInfo serviceInfo) {
|
||||
String qualifiedName = serviceInfo.getQualifiedName();
|
||||
logger.debug("AVR found: {}", qualifiedName);
|
||||
ThingUID thingUID = getThingUID(serviceInfo);
|
||||
if (thingUID != null) {
|
||||
Matcher matcher = DENON_MARANTZ_PATTERN.matcher(qualifiedName);
|
||||
matcher.matches(); // we already know it matches, it was matched in getThingUID
|
||||
String serial = matcher.group(1).toLowerCase();
|
||||
|
||||
/**
|
||||
* The Vendor is not available from the mDNS result.
|
||||
* We assign the Vendor based on our assumptions of the MAC address prefix.
|
||||
*/
|
||||
String vendor = "";
|
||||
if (serial.startsWith(MARANTZ_MAC_PREFIX)) {
|
||||
vendor = "Marantz";
|
||||
} else if (serial.startsWith(DENON_MAC_PREFIX)) {
|
||||
vendor = "Denon";
|
||||
}
|
||||
|
||||
// 'am=...' property describes the model name
|
||||
String model = serviceInfo.getPropertyString("am");
|
||||
String friendlyName = matcher.group(2).trim();
|
||||
|
||||
Map<String, Object> properties = new HashMap<>(2);
|
||||
|
||||
if (serviceInfo.getHostAddresses().length == 0) {
|
||||
logger.debug("Could not determine IP address for the Denon/Marantz AVR");
|
||||
return null;
|
||||
}
|
||||
String host = serviceInfo.getHostAddresses()[0];
|
||||
|
||||
logger.debug("IP Address: {}", host);
|
||||
|
||||
properties.put(PARAMETER_HOST, host);
|
||||
properties.put(Thing.PROPERTY_SERIAL_NUMBER, serial);
|
||||
properties.put(Thing.PROPERTY_VENDOR, vendor);
|
||||
properties.put(Thing.PROPERTY_MODEL_ID, model);
|
||||
|
||||
String label = friendlyName + " (" + vendor + ' ' + model + ")";
|
||||
DiscoveryResult result = DiscoveryResultBuilder.create(thingUID).withProperties(properties).withLabel(label)
|
||||
.withRepresentationProperty(Thing.PROPERTY_SERIAL_NUMBER).build();
|
||||
return result;
|
||||
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ThingUID getThingUID(ServiceInfo service) {
|
||||
Matcher matcher = DENON_MARANTZ_PATTERN.matcher(service.getQualifiedName());
|
||||
if (matcher.matches()) {
|
||||
logger.debug("This seems like a supported Denon/Marantz AVR!");
|
||||
String serial = matcher.group(1).toLowerCase();
|
||||
return new ThingUID(THING_TYPE_AVR, serial);
|
||||
} else {
|
||||
logger.trace("This discovered device is not supported by the DenonMarantz binding, ignoring..");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,444 @@
|
||||
/**
|
||||
* 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.denonmarantz.internal.handler;
|
||||
|
||||
import static org.openhab.binding.denonmarantz.internal.DenonMarantzBindingConstants.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.StringReader;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import javax.xml.parsers.DocumentBuilder;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
import javax.xml.xpath.XPath;
|
||||
import javax.xml.xpath.XPathConstants;
|
||||
import javax.xml.xpath.XPathExpressionException;
|
||||
import javax.xml.xpath.XPathFactory;
|
||||
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.eclipse.jetty.client.api.ContentResponse;
|
||||
import org.openhab.binding.denonmarantz.internal.DenonMarantzState;
|
||||
import org.openhab.binding.denonmarantz.internal.DenonMarantzStateChangedListener;
|
||||
import org.openhab.binding.denonmarantz.internal.UnsupportedCommandTypeException;
|
||||
import org.openhab.binding.denonmarantz.internal.config.DenonMarantzConfiguration;
|
||||
import org.openhab.binding.denonmarantz.internal.connector.DenonMarantzConnector;
|
||||
import org.openhab.binding.denonmarantz.internal.connector.DenonMarantzConnectorFactory;
|
||||
import org.openhab.binding.denonmarantz.internal.connector.http.DenonMarantzHttpConnector;
|
||||
import org.openhab.core.config.core.Configuration;
|
||||
import org.openhab.core.thing.Channel;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
import org.openhab.core.thing.ThingStatusDetail;
|
||||
import org.openhab.core.thing.binding.BaseThingHandler;
|
||||
import org.openhab.core.thing.binding.builder.ChannelBuilder;
|
||||
import org.openhab.core.thing.type.ChannelTypeUID;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.RefreshType;
|
||||
import org.openhab.core.types.State;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Node;
|
||||
import org.xml.sax.InputSource;
|
||||
import org.xml.sax.SAXException;
|
||||
|
||||
/**
|
||||
* The {@link DenonMarantzHandler} is responsible for handling commands, which are
|
||||
* sent to one of the channels.
|
||||
*
|
||||
* @author Jan-Willem Veldhuis - Initial contribution
|
||||
*/
|
||||
public class DenonMarantzHandler extends BaseThingHandler implements DenonMarantzStateChangedListener {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(DenonMarantzHandler.class);
|
||||
private static final int RETRY_TIME_SECONDS = 30;
|
||||
private HttpClient httpClient;
|
||||
private DenonMarantzConnector connector;
|
||||
private DenonMarantzConfiguration config;
|
||||
private DenonMarantzConnectorFactory connectorFactory = new DenonMarantzConnectorFactory();
|
||||
private DenonMarantzState denonMarantzState;
|
||||
private ScheduledFuture<?> retryJob;
|
||||
|
||||
public DenonMarantzHandler(Thing thing, HttpClient httpClient) {
|
||||
super(thing);
|
||||
this.httpClient = httpClient;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
if (connector == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (connector instanceof DenonMarantzHttpConnector && command instanceof RefreshType) {
|
||||
// Refreshing individual channels isn't supported by the Http connector.
|
||||
// The connector refreshes all channels together at the configured polling interval.
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
switch (channelUID.getId()) {
|
||||
case CHANNEL_POWER:
|
||||
connector.sendPowerCommand(command, 0);
|
||||
break;
|
||||
case CHANNEL_MAIN_ZONE_POWER:
|
||||
connector.sendPowerCommand(command, 1);
|
||||
break;
|
||||
case CHANNEL_MUTE:
|
||||
connector.sendMuteCommand(command, 1);
|
||||
break;
|
||||
case CHANNEL_MAIN_VOLUME:
|
||||
connector.sendVolumeCommand(command, 1);
|
||||
break;
|
||||
case CHANNEL_MAIN_VOLUME_DB:
|
||||
connector.sendVolumeDbCommand(command, 1);
|
||||
break;
|
||||
case CHANNEL_INPUT:
|
||||
connector.sendInputCommand(command, 1);
|
||||
break;
|
||||
case CHANNEL_SURROUND_PROGRAM:
|
||||
connector.sendSurroundProgramCommand(command);
|
||||
break;
|
||||
case CHANNEL_COMMAND:
|
||||
connector.sendCustomCommand(command);
|
||||
break;
|
||||
|
||||
case CHANNEL_ZONE2_POWER:
|
||||
connector.sendPowerCommand(command, 2);
|
||||
break;
|
||||
case CHANNEL_ZONE2_MUTE:
|
||||
connector.sendMuteCommand(command, 2);
|
||||
break;
|
||||
case CHANNEL_ZONE2_VOLUME:
|
||||
connector.sendVolumeCommand(command, 2);
|
||||
break;
|
||||
case CHANNEL_ZONE2_VOLUME_DB:
|
||||
connector.sendVolumeDbCommand(command, 2);
|
||||
break;
|
||||
case CHANNEL_ZONE2_INPUT:
|
||||
connector.sendInputCommand(command, 2);
|
||||
break;
|
||||
|
||||
case CHANNEL_ZONE3_POWER:
|
||||
connector.sendPowerCommand(command, 3);
|
||||
break;
|
||||
case CHANNEL_ZONE3_MUTE:
|
||||
connector.sendMuteCommand(command, 3);
|
||||
break;
|
||||
case CHANNEL_ZONE3_VOLUME:
|
||||
connector.sendVolumeCommand(command, 3);
|
||||
break;
|
||||
case CHANNEL_ZONE3_VOLUME_DB:
|
||||
connector.sendVolumeDbCommand(command, 3);
|
||||
break;
|
||||
case CHANNEL_ZONE3_INPUT:
|
||||
connector.sendInputCommand(command, 3);
|
||||
break;
|
||||
|
||||
case CHANNEL_ZONE4_POWER:
|
||||
connector.sendPowerCommand(command, 4);
|
||||
break;
|
||||
case CHANNEL_ZONE4_MUTE:
|
||||
connector.sendMuteCommand(command, 4);
|
||||
break;
|
||||
case CHANNEL_ZONE4_VOLUME:
|
||||
connector.sendVolumeCommand(command, 4);
|
||||
break;
|
||||
case CHANNEL_ZONE4_VOLUME_DB:
|
||||
connector.sendVolumeDbCommand(command, 4);
|
||||
break;
|
||||
case CHANNEL_ZONE4_INPUT:
|
||||
connector.sendInputCommand(command, 4);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new UnsupportedCommandTypeException();
|
||||
}
|
||||
} catch (UnsupportedCommandTypeException e) {
|
||||
logger.debug("Unsupported command {} for channel {}", command, channelUID.getId());
|
||||
}
|
||||
}
|
||||
|
||||
public boolean checkConfiguration() {
|
||||
// prevent too low values for polling interval
|
||||
if (config.httpPollingInterval < 5) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
||||
"The polling interval should be at least 5 seconds!");
|
||||
return false;
|
||||
}
|
||||
// Check zone count is within supported range
|
||||
if (config.getZoneCount() < 1 || config.getZoneCount() > 4) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
||||
"This binding supports 1 to 4 zones. Please update the zone count.");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to auto configure the connection type (Telnet or HTTP)
|
||||
* for Things not added through Paper UI.
|
||||
*/
|
||||
private void autoConfigure() {
|
||||
/*
|
||||
* The isTelnet parameter has no default.
|
||||
* When not set we will try to auto-detect the correct values
|
||||
* for isTelnet and zoneCount and update the Thing accordingly.
|
||||
*/
|
||||
if (config.isTelnet() == null) {
|
||||
logger.debug("Trying to auto-detect the connection.");
|
||||
ContentResponse response;
|
||||
boolean telnetEnable = true;
|
||||
int httpPort = 80;
|
||||
boolean httpApiUsable = false;
|
||||
|
||||
// try to reach the HTTP API at port 80 (most models, except Denon ...H should respond.
|
||||
String host = config.getHost();
|
||||
try {
|
||||
response = httpClient.newRequest("http://" + host + "/goform/Deviceinfo.xml")
|
||||
.timeout(3, TimeUnit.SECONDS).send();
|
||||
if (response.getStatus() == HttpURLConnection.HTTP_OK) {
|
||||
logger.debug("We can access the HTTP API, disabling the Telnet mode by default.");
|
||||
telnetEnable = false;
|
||||
httpApiUsable = true;
|
||||
}
|
||||
} catch (InterruptedException | TimeoutException | ExecutionException e) {
|
||||
logger.debug("Error when trying to access AVR using HTTP on port 80, reverting to Telnet mode.", e);
|
||||
}
|
||||
|
||||
if (telnetEnable) {
|
||||
// the above attempt failed. Let's try on port 8080, as for some models a subset of the HTTP API is
|
||||
// available
|
||||
try {
|
||||
response = httpClient.newRequest("http://" + host + ":8080/goform/Deviceinfo.xml")
|
||||
.timeout(3, TimeUnit.SECONDS).send();
|
||||
if (response.getStatus() == HttpURLConnection.HTTP_OK) {
|
||||
logger.debug(
|
||||
"This model responds to HTTP port 8080, we use this port to retrieve the number of zones.");
|
||||
httpPort = 8080;
|
||||
httpApiUsable = true;
|
||||
}
|
||||
} catch (InterruptedException | TimeoutException | ExecutionException e) {
|
||||
logger.debug("Additionally tried to connect to port 8080, this also failed", e);
|
||||
}
|
||||
}
|
||||
|
||||
// default zone count
|
||||
int zoneCount = 2;
|
||||
|
||||
// try to determine the zone count by checking the Deviceinfo.xml file
|
||||
if (httpApiUsable) {
|
||||
int status = 0;
|
||||
response = null;
|
||||
try {
|
||||
response = httpClient.newRequest("http://" + host + ":" + httpPort + "/goform/Deviceinfo.xml")
|
||||
.timeout(3, TimeUnit.SECONDS).send();
|
||||
status = response.getStatus();
|
||||
} catch (InterruptedException | TimeoutException | ExecutionException e) {
|
||||
logger.debug("Failed in fetching the Deviceinfo.xml to determine zone count", e);
|
||||
}
|
||||
|
||||
if (status == HttpURLConnection.HTTP_OK && response != null) {
|
||||
DocumentBuilderFactory domFactory = DocumentBuilderFactory.newInstance();
|
||||
DocumentBuilder builder;
|
||||
try {
|
||||
builder = domFactory.newDocumentBuilder();
|
||||
Document dDoc = builder.parse(new InputSource(new StringReader(response.getContentAsString())));
|
||||
XPath xPath = XPathFactory.newInstance().newXPath();
|
||||
Node node = (Node) xPath.evaluate("/Device_Info/DeviceZones/text()", dDoc, XPathConstants.NODE);
|
||||
if (node != null) {
|
||||
String nodeValue = node.getNodeValue();
|
||||
logger.trace("/Device_Info/DeviceZones/text() = {}", nodeValue);
|
||||
zoneCount = Integer.parseInt(nodeValue);
|
||||
logger.debug("Discovered number of zones: {}", zoneCount);
|
||||
}
|
||||
} catch (ParserConfigurationException | SAXException | IOException | XPathExpressionException
|
||||
| NumberFormatException e) {
|
||||
logger.debug("Something went wrong with looking up the zone count in Deviceinfo.xml: {}",
|
||||
e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
config.setTelnet(telnetEnable);
|
||||
config.setZoneCount(zoneCount);
|
||||
Configuration configuration = editConfiguration();
|
||||
configuration.put(PARAMETER_TELNET_ENABLED, telnetEnable);
|
||||
configuration.put(PARAMETER_ZONE_COUNT, zoneCount);
|
||||
updateConfiguration(configuration);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
cancelRetry();
|
||||
config = getConfigAs(DenonMarantzConfiguration.class);
|
||||
|
||||
// Configure Connection type (Telnet/HTTP) and number of zones
|
||||
// Note: this only happens for discovered Things
|
||||
autoConfigure();
|
||||
|
||||
if (!checkConfiguration()) {
|
||||
return;
|
||||
}
|
||||
|
||||
denonMarantzState = new DenonMarantzState(this);
|
||||
configureZoneChannels();
|
||||
updateStatus(ThingStatus.UNKNOWN);
|
||||
// create connection (either Telnet or HTTP)
|
||||
// ThingStatus ONLINE/OFFLINE is set when AVR status is known.
|
||||
createConnection();
|
||||
}
|
||||
|
||||
private void createConnection() {
|
||||
if (connector != null) {
|
||||
connector.dispose();
|
||||
}
|
||||
connector = connectorFactory.getConnector(config, denonMarantzState, scheduler, httpClient);
|
||||
connector.connect();
|
||||
}
|
||||
|
||||
private void cancelRetry() {
|
||||
ScheduledFuture<?> localRetryJob = retryJob;
|
||||
if (localRetryJob != null && !localRetryJob.isDone()) {
|
||||
localRetryJob.cancel(false);
|
||||
}
|
||||
}
|
||||
|
||||
private void configureZoneChannels() {
|
||||
logger.debug("Configuring zone channels");
|
||||
Integer zoneCount = config.getZoneCount();
|
||||
List<Channel> channels = new ArrayList<>(this.getThing().getChannels());
|
||||
boolean channelsUpdated = false;
|
||||
|
||||
// construct a set with the existing channel type UIDs, to quickly check
|
||||
Set<String> currentChannels = new HashSet<>();
|
||||
channels.forEach(channel -> currentChannels.add(channel.getUID().getId()));
|
||||
|
||||
Set<Entry<String, ChannelTypeUID>> channelsToRemove = new HashSet<>();
|
||||
|
||||
if (zoneCount > 1) {
|
||||
List<Entry<String, ChannelTypeUID>> channelsToAdd = new ArrayList<>(ZONE2_CHANNEL_TYPES.entrySet());
|
||||
|
||||
if (zoneCount > 2) {
|
||||
// add channels for zone 3
|
||||
channelsToAdd.addAll(ZONE3_CHANNEL_TYPES.entrySet());
|
||||
if (zoneCount > 3) {
|
||||
// add channels for zone 4 (more zones currently not supported)
|
||||
channelsToAdd.addAll(ZONE4_CHANNEL_TYPES.entrySet());
|
||||
} else {
|
||||
channelsToRemove.addAll(ZONE4_CHANNEL_TYPES.entrySet());
|
||||
}
|
||||
} else {
|
||||
channelsToRemove.addAll(ZONE3_CHANNEL_TYPES.entrySet());
|
||||
channelsToRemove.addAll(ZONE4_CHANNEL_TYPES.entrySet());
|
||||
}
|
||||
|
||||
// filter out the already existing channels
|
||||
channelsToAdd.removeIf(c -> currentChannels.contains(c.getKey()));
|
||||
|
||||
// add the channels that were not yet added
|
||||
if (!channelsToAdd.isEmpty()) {
|
||||
for (Entry<String, ChannelTypeUID> entry : channelsToAdd) {
|
||||
String itemType = CHANNEL_ITEM_TYPES.get(entry.getKey());
|
||||
Channel channel = ChannelBuilder
|
||||
.create(new ChannelUID(this.getThing().getUID(), entry.getKey()), itemType)
|
||||
.withType(entry.getValue()).build();
|
||||
channels.add(channel);
|
||||
}
|
||||
channelsUpdated = true;
|
||||
} else {
|
||||
logger.debug("No zone channels have been added");
|
||||
}
|
||||
} else {
|
||||
channelsToRemove.addAll(ZONE2_CHANNEL_TYPES.entrySet());
|
||||
channelsToRemove.addAll(ZONE3_CHANNEL_TYPES.entrySet());
|
||||
channelsToRemove.addAll(ZONE4_CHANNEL_TYPES.entrySet());
|
||||
}
|
||||
|
||||
// filter out the non-existing channels
|
||||
channelsToRemove.removeIf(c -> !currentChannels.contains(c.getKey()));
|
||||
|
||||
// remove the channels that were not yet added
|
||||
if (!channelsToRemove.isEmpty()) {
|
||||
for (Entry<String, ChannelTypeUID> entry : channelsToRemove) {
|
||||
if (channels.removeIf(c -> (entry.getKey()).equals(c.getUID().getId()))) {
|
||||
logger.trace("Removed channel {}", entry.getKey());
|
||||
} else {
|
||||
logger.trace("Could NOT remove channel {}", entry.getKey());
|
||||
}
|
||||
}
|
||||
channelsUpdated = true;
|
||||
} else {
|
||||
logger.debug("No zone channels have been removed");
|
||||
}
|
||||
|
||||
// update Thing if channels changed
|
||||
if (channelsUpdated) {
|
||||
updateThing(editThing().withChannels(channels).build());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
if (connector != null) {
|
||||
connector.dispose();
|
||||
connector = null;
|
||||
}
|
||||
cancelRetry();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelLinked(ChannelUID channelUID) {
|
||||
super.channelLinked(channelUID);
|
||||
String channelID = channelUID.getId();
|
||||
if (isLinked(channelID)) {
|
||||
State state = denonMarantzState.getStateForChannelID(channelID);
|
||||
if (state != null) {
|
||||
updateState(channelID, state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stateChanged(String channelID, State state) {
|
||||
logger.debug("Received state {} for channelID {}", state, channelID);
|
||||
|
||||
// Don't flood the log with thing 'updated: ONLINE' each time a single channel changed
|
||||
if (this.getThing().getStatus() != ThingStatus.ONLINE) {
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
}
|
||||
updateState(channelID, state);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void connectionError(String errorMessage) {
|
||||
if (this.getThing().getStatus() != ThingStatus.OFFLINE) {
|
||||
// Don't flood the log with thing 'updated: OFFLINE' when already offline
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, errorMessage);
|
||||
}
|
||||
connector.dispose();
|
||||
retryJob = scheduler.schedule(this::createConnection, RETRY_TIME_SECONDS, TimeUnit.SECONDS);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/**
|
||||
* 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.denonmarantz.internal.xml.adapters;
|
||||
|
||||
import javax.xml.bind.annotation.adapters.XmlAdapter;
|
||||
|
||||
/**
|
||||
* Maps 'On' and 'Off' string values to a boolean
|
||||
*
|
||||
* @author Jeroen Idserda - Initial contribution
|
||||
*/
|
||||
public class OnOffAdapter extends XmlAdapter<String, Boolean> {
|
||||
|
||||
@Override
|
||||
public Boolean unmarshal(String v) throws Exception {
|
||||
if (v != null) {
|
||||
return Boolean.valueOf(v.toLowerCase().equals("on"));
|
||||
}
|
||||
|
||||
return Boolean.FALSE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String marshal(Boolean v) throws Exception {
|
||||
return v ? "On" : "Off";
|
||||
}
|
||||
}
|
||||
@@ -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.denonmarantz.internal.xml.adapters;
|
||||
|
||||
import javax.xml.bind.annotation.adapters.XmlAdapter;
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
|
||||
/**
|
||||
* Adapter to clean up string values
|
||||
*
|
||||
* @author Jeroen Idserda - Initial contribution
|
||||
*/
|
||||
public class StringAdapter extends XmlAdapter<String, String> {
|
||||
|
||||
@Override
|
||||
public String unmarshal(String v) throws Exception {
|
||||
String val = v;
|
||||
if (val != null) {
|
||||
val = StringUtils.trimToEmpty(val);
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String marshal(String v) throws Exception {
|
||||
return v;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/**
|
||||
* 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.denonmarantz.internal.xml.adapters;
|
||||
|
||||
import static org.openhab.binding.denonmarantz.internal.DenonMarantzBindingConstants.DB_OFFSET;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
import javax.xml.bind.annotation.adapters.XmlAdapter;
|
||||
|
||||
/**
|
||||
* Maps Denon volume values in db to percentage
|
||||
*
|
||||
* @author Jeroen Idserda - Initial contribution
|
||||
*/
|
||||
public class VolumeAdapter extends XmlAdapter<String, BigDecimal> {
|
||||
|
||||
@Override
|
||||
public BigDecimal unmarshal(String v) throws Exception {
|
||||
if (v != null && !v.trim().equals("--")) {
|
||||
return new BigDecimal(v.trim()).add(DB_OFFSET);
|
||||
}
|
||||
|
||||
return BigDecimal.ZERO;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String marshal(BigDecimal v) throws Exception {
|
||||
if (v.equals(BigDecimal.ZERO)) {
|
||||
return "--";
|
||||
}
|
||||
|
||||
return v.subtract(DB_OFFSET).toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
/**
|
||||
* 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.denonmarantz.internal.xml.entities;
|
||||
|
||||
import javax.xml.bind.annotation.XmlAccessType;
|
||||
import javax.xml.bind.annotation.XmlAccessorType;
|
||||
import javax.xml.bind.annotation.XmlRootElement;
|
||||
|
||||
/**
|
||||
* Contains information about a Denon/Marantz receiver.
|
||||
*
|
||||
* @author Jeroen Idserda - Initial contribution
|
||||
*/
|
||||
@XmlRootElement(name = "device_Info")
|
||||
@XmlAccessorType(XmlAccessType.FIELD)
|
||||
public class Deviceinfo {
|
||||
|
||||
private Integer deviceZones;
|
||||
|
||||
private String modelName;
|
||||
|
||||
public Integer getDeviceZones() {
|
||||
return deviceZones;
|
||||
}
|
||||
|
||||
public void setDeviceZones(Integer deviceZones) {
|
||||
this.deviceZones = deviceZones;
|
||||
}
|
||||
|
||||
public String getModelName() {
|
||||
return modelName;
|
||||
}
|
||||
|
||||
public void setModelName(String modelName) {
|
||||
this.modelName = modelName;
|
||||
}
|
||||
}
|
||||
@@ -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.denonmarantz.internal.xml.entities;
|
||||
|
||||
import javax.xml.bind.annotation.XmlAccessType;
|
||||
import javax.xml.bind.annotation.XmlAccessorType;
|
||||
import javax.xml.bind.annotation.XmlRootElement;
|
||||
|
||||
import org.openhab.binding.denonmarantz.internal.xml.entities.types.OnOffType;
|
||||
|
||||
/**
|
||||
* Holds information about the Main zone of the receiver
|
||||
*
|
||||
* @author Jeroen Idserda - Initial contribution
|
||||
*/
|
||||
@XmlRootElement(name = "item")
|
||||
@XmlAccessorType(XmlAccessType.FIELD)
|
||||
public class Main {
|
||||
|
||||
private OnOffType power;
|
||||
|
||||
public OnOffType getPower() {
|
||||
return power;
|
||||
}
|
||||
|
||||
public void setPower(OnOffType power) {
|
||||
this.power = power;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
/**
|
||||
* 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.denonmarantz.internal.xml.entities;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import javax.xml.bind.annotation.XmlAccessType;
|
||||
import javax.xml.bind.annotation.XmlAccessorType;
|
||||
import javax.xml.bind.annotation.XmlElement;
|
||||
import javax.xml.bind.annotation.XmlElementWrapper;
|
||||
import javax.xml.bind.annotation.XmlRootElement;
|
||||
|
||||
import org.openhab.binding.denonmarantz.internal.xml.entities.types.OnOffType;
|
||||
import org.openhab.binding.denonmarantz.internal.xml.entities.types.StringType;
|
||||
import org.openhab.binding.denonmarantz.internal.xml.entities.types.VolumeType;
|
||||
|
||||
/**
|
||||
* Holds information about the secondary zones of the receiver
|
||||
*
|
||||
* @author Jeroen Idserda - Initial contribution
|
||||
*/
|
||||
@XmlRootElement(name = "item")
|
||||
@XmlAccessorType(XmlAccessType.FIELD)
|
||||
public class ZoneStatus {
|
||||
|
||||
private OnOffType power;
|
||||
|
||||
@XmlElementWrapper(name = "inputFuncList")
|
||||
@XmlElement(name = "value")
|
||||
private List<String> inputFunctions;
|
||||
|
||||
private StringType inputFuncSelect;
|
||||
|
||||
private StringType volumeDisplay;
|
||||
|
||||
private StringType surrMode;
|
||||
|
||||
private VolumeType masterVolume;
|
||||
|
||||
private OnOffType mute;
|
||||
|
||||
public OnOffType getPower() {
|
||||
return power;
|
||||
}
|
||||
|
||||
public void setPower(OnOffType power) {
|
||||
this.power = power;
|
||||
}
|
||||
|
||||
public StringType getInputFuncSelect() {
|
||||
return inputFuncSelect;
|
||||
}
|
||||
|
||||
public void setInputFuncSelect(StringType inputFuncSelect) {
|
||||
this.inputFuncSelect = inputFuncSelect;
|
||||
}
|
||||
|
||||
public StringType getVolumeDisplay() {
|
||||
return volumeDisplay;
|
||||
}
|
||||
|
||||
public void setVolumeDisplay(StringType volumeDisplay) {
|
||||
this.volumeDisplay = volumeDisplay;
|
||||
}
|
||||
|
||||
public StringType getSurrMode() {
|
||||
return surrMode;
|
||||
}
|
||||
|
||||
public void setSurrMode(StringType surrMode) {
|
||||
this.surrMode = surrMode;
|
||||
}
|
||||
|
||||
public VolumeType getMasterVolume() {
|
||||
return masterVolume;
|
||||
}
|
||||
|
||||
public void setMasterVolume(VolumeType masterVolume) {
|
||||
this.masterVolume = masterVolume;
|
||||
}
|
||||
|
||||
public OnOffType getMute() {
|
||||
return mute;
|
||||
}
|
||||
|
||||
public void setMute(OnOffType mute) {
|
||||
this.mute = mute;
|
||||
}
|
||||
|
||||
public List<String> getInputFuncList() {
|
||||
return this.inputFunctions;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
/**
|
||||
* 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.denonmarantz.internal.xml.entities;
|
||||
|
||||
import javax.xml.bind.annotation.XmlAccessType;
|
||||
import javax.xml.bind.annotation.XmlAccessorType;
|
||||
import javax.xml.bind.annotation.XmlRootElement;
|
||||
|
||||
import org.openhab.binding.denonmarantz.internal.xml.entities.types.OnOffType;
|
||||
import org.openhab.binding.denonmarantz.internal.xml.entities.types.StringType;
|
||||
import org.openhab.binding.denonmarantz.internal.xml.entities.types.VolumeType;
|
||||
|
||||
/**
|
||||
* Holds limited information about the secondary zones of the receiver
|
||||
*
|
||||
* @author Jeroen Idserda - Initial contribution
|
||||
*/
|
||||
@XmlRootElement(name = "item")
|
||||
@XmlAccessorType(XmlAccessType.FIELD)
|
||||
public class ZoneStatusLite {
|
||||
|
||||
private OnOffType power;
|
||||
|
||||
private StringType inputFuncSelect;
|
||||
|
||||
private StringType volumeDisplay;
|
||||
|
||||
private VolumeType masterVolume;
|
||||
|
||||
private OnOffType mute;
|
||||
|
||||
public OnOffType getPower() {
|
||||
return power;
|
||||
}
|
||||
|
||||
public void setPower(OnOffType power) {
|
||||
this.power = power;
|
||||
}
|
||||
|
||||
public StringType getInputFuncSelect() {
|
||||
return inputFuncSelect;
|
||||
}
|
||||
|
||||
public void setInputFuncSelect(StringType inputFuncSelect) {
|
||||
this.inputFuncSelect = inputFuncSelect;
|
||||
}
|
||||
|
||||
public StringType getVolumeDisplay() {
|
||||
return volumeDisplay;
|
||||
}
|
||||
|
||||
public void setVolumeDisplay(StringType volumeDisplay) {
|
||||
this.volumeDisplay = volumeDisplay;
|
||||
}
|
||||
|
||||
public VolumeType getMasterVolume() {
|
||||
return masterVolume;
|
||||
}
|
||||
|
||||
public void setMasterVolume(VolumeType masterVolume) {
|
||||
this.masterVolume = masterVolume;
|
||||
}
|
||||
|
||||
public OnOffType getMute() {
|
||||
return mute;
|
||||
}
|
||||
|
||||
public void setMute(OnOffType mute) {
|
||||
this.mute = mute;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
/**
|
||||
* 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.denonmarantz.internal.xml.entities.commands;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.xml.bind.annotation.XmlAccessType;
|
||||
import javax.xml.bind.annotation.XmlAccessorType;
|
||||
import javax.xml.bind.annotation.XmlElement;
|
||||
import javax.xml.bind.annotation.XmlRootElement;
|
||||
|
||||
/**
|
||||
* Wrapper for a list of {@link CommandTx}
|
||||
*
|
||||
* @author Jeroen Idserda - Initial contribution
|
||||
*/
|
||||
@XmlRootElement(name = "tx")
|
||||
@XmlAccessorType(XmlAccessType.FIELD)
|
||||
public class AppCommandRequest {
|
||||
|
||||
@XmlElement(name = "cmd")
|
||||
private List<CommandTx> commands = new ArrayList<>();
|
||||
|
||||
public AppCommandRequest() {
|
||||
}
|
||||
|
||||
public List<CommandTx> getCommands() {
|
||||
return commands;
|
||||
}
|
||||
|
||||
public void setCommands(List<CommandTx> commands) {
|
||||
this.commands = commands;
|
||||
}
|
||||
|
||||
public AppCommandRequest add(CommandTx command) {
|
||||
commands.add(command);
|
||||
return this;
|
||||
}
|
||||
|
||||
public static AppCommandRequest of(CommandTx command) {
|
||||
AppCommandRequest tx = new AppCommandRequest();
|
||||
return tx.add(command);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/**
|
||||
* 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.denonmarantz.internal.xml.entities.commands;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.xml.bind.annotation.XmlAccessType;
|
||||
import javax.xml.bind.annotation.XmlAccessorType;
|
||||
import javax.xml.bind.annotation.XmlElement;
|
||||
import javax.xml.bind.annotation.XmlRootElement;
|
||||
|
||||
/**
|
||||
* Response to an {@link AppCommandRequest}, wraps a list of {@link CommandRx}
|
||||
*
|
||||
* @author Jeroen Idserda - Initial contribution
|
||||
*/
|
||||
@XmlRootElement(name = "rx")
|
||||
@XmlAccessorType(XmlAccessType.FIELD)
|
||||
public class AppCommandResponse {
|
||||
|
||||
@XmlElement(name = "cmd")
|
||||
private List<CommandRx> commands = new ArrayList<>();
|
||||
|
||||
public AppCommandResponse() {
|
||||
}
|
||||
|
||||
public List<CommandRx> getCommands() {
|
||||
return commands;
|
||||
}
|
||||
|
||||
public void setCommands(List<CommandRx> commands) {
|
||||
this.commands = commands;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,203 @@
|
||||
/**
|
||||
* 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.denonmarantz.internal.xml.entities.commands;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.xml.bind.annotation.XmlAccessType;
|
||||
import javax.xml.bind.annotation.XmlAccessorType;
|
||||
import javax.xml.bind.annotation.XmlElement;
|
||||
import javax.xml.bind.annotation.XmlElementWrapper;
|
||||
import javax.xml.bind.annotation.XmlRootElement;
|
||||
|
||||
/**
|
||||
* Response to a {@link CommandTx}
|
||||
*
|
||||
* @author Jeroen Idserda - Initial contribution
|
||||
*/
|
||||
@XmlRootElement(name = "cmd")
|
||||
@XmlAccessorType(XmlAccessType.FIELD)
|
||||
public class CommandRx {
|
||||
|
||||
private String zone1;
|
||||
|
||||
private String zone2;
|
||||
|
||||
private String zone3;
|
||||
|
||||
private String zone4;
|
||||
|
||||
private String volume;
|
||||
|
||||
private String disptype;
|
||||
|
||||
private String dispvalue;
|
||||
|
||||
private String mute;
|
||||
|
||||
private String type;
|
||||
|
||||
@XmlElement(name = "text")
|
||||
private List<Text> texts = new ArrayList<>();
|
||||
|
||||
@XmlElementWrapper(name = "functionrename")
|
||||
@XmlElement(name = "list")
|
||||
private List<RenameSourceList> renameSourceLists;
|
||||
|
||||
@XmlElementWrapper(name = "functiondelete")
|
||||
@XmlElement(name = "list")
|
||||
private List<DeletedSourceList> deletedSourceLists;
|
||||
|
||||
private String playstatus;
|
||||
|
||||
private String playcontents;
|
||||
|
||||
private String repeat;
|
||||
|
||||
private String shuffle;
|
||||
|
||||
private String source;
|
||||
|
||||
public CommandRx() {
|
||||
}
|
||||
|
||||
public String getZone1() {
|
||||
return zone1;
|
||||
}
|
||||
|
||||
public void setZone1(String zone1) {
|
||||
this.zone1 = zone1;
|
||||
}
|
||||
|
||||
public String getZone2() {
|
||||
return zone2;
|
||||
}
|
||||
|
||||
public void setZone2(String zone2) {
|
||||
this.zone2 = zone2;
|
||||
}
|
||||
|
||||
public String getZone3() {
|
||||
return zone3;
|
||||
}
|
||||
|
||||
public void setZone3(String zone3) {
|
||||
this.zone3 = zone3;
|
||||
}
|
||||
|
||||
public String getZone4() {
|
||||
return zone4;
|
||||
}
|
||||
|
||||
public void setZone4(String zone4) {
|
||||
this.zone4 = zone4;
|
||||
}
|
||||
|
||||
public String getVolume() {
|
||||
return volume;
|
||||
}
|
||||
|
||||
public void setVolume(String volume) {
|
||||
this.volume = volume;
|
||||
}
|
||||
|
||||
public String getDisptype() {
|
||||
return disptype;
|
||||
}
|
||||
|
||||
public void setDisptype(String disptype) {
|
||||
this.disptype = disptype;
|
||||
}
|
||||
|
||||
public String getDispvalue() {
|
||||
return dispvalue;
|
||||
}
|
||||
|
||||
public void setDispvalue(String dispvalue) {
|
||||
this.dispvalue = dispvalue;
|
||||
}
|
||||
|
||||
public String getMute() {
|
||||
return mute;
|
||||
}
|
||||
|
||||
public void setMute(String mute) {
|
||||
this.mute = mute;
|
||||
}
|
||||
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public void setType(String type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public String getPlaystatus() {
|
||||
return playstatus;
|
||||
}
|
||||
|
||||
public void setPlaystatus(String playstatus) {
|
||||
this.playstatus = playstatus;
|
||||
}
|
||||
|
||||
public String getPlaycontents() {
|
||||
return playcontents;
|
||||
}
|
||||
|
||||
public void setPlaycontents(String playcontents) {
|
||||
this.playcontents = playcontents;
|
||||
}
|
||||
|
||||
public String getRepeat() {
|
||||
return repeat;
|
||||
}
|
||||
|
||||
public void setRepeat(String repeat) {
|
||||
this.repeat = repeat;
|
||||
}
|
||||
|
||||
public String getShuffle() {
|
||||
return shuffle;
|
||||
}
|
||||
|
||||
public void setShuffle(String shuffle) {
|
||||
this.shuffle = shuffle;
|
||||
}
|
||||
|
||||
public String getSource() {
|
||||
return source;
|
||||
}
|
||||
|
||||
public void setSource(String source) {
|
||||
this.source = source;
|
||||
}
|
||||
|
||||
public String getText(String key) {
|
||||
for (Text text : texts) {
|
||||
if (text.getId().equals(key)) {
|
||||
return text.getValue();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public List<RenameSourceList> getRenameSourceLists() {
|
||||
return renameSourceLists;
|
||||
}
|
||||
|
||||
public List<DeletedSourceList> getDeletedSourceLists() {
|
||||
return deletedSourceLists;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
/**
|
||||
* 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.denonmarantz.internal.xml.entities.commands;
|
||||
|
||||
import javax.xml.bind.annotation.XmlAccessType;
|
||||
import javax.xml.bind.annotation.XmlAccessorType;
|
||||
import javax.xml.bind.annotation.XmlAttribute;
|
||||
import javax.xml.bind.annotation.XmlRootElement;
|
||||
import javax.xml.bind.annotation.XmlValue;
|
||||
|
||||
/**
|
||||
* Individual commands that can be sent to a Denon/Marantz receiver to request specific information.
|
||||
*
|
||||
* @author Jeroen Idserda - Initial contribution
|
||||
*/
|
||||
@XmlRootElement(name = "cmd")
|
||||
@XmlAccessorType(XmlAccessType.FIELD)
|
||||
public class CommandTx {
|
||||
|
||||
private static final String DEFAULT_ID = "1";
|
||||
|
||||
public static final CommandTx CMD_ALL_POWER = of("GetAllZonePowerStatus");
|
||||
|
||||
public static final CommandTx CMD_VOLUME_LEVEL = of("GetVolumeLevel");
|
||||
|
||||
public static final CommandTx CMD_MUTE_STATUS = of("GetMuteStatus");
|
||||
|
||||
public static final CommandTx CMD_SOURCE_STATUS = of("GetSourceStatus");
|
||||
|
||||
public static final CommandTx CMD_SURROUND_STATUS = of("GetSurroundModeStatus");
|
||||
|
||||
public static final CommandTx CMD_ZONE_NAME = of("GetZoneName");
|
||||
|
||||
public static final CommandTx CMD_NET_STATUS = of("GetNetAudioStatus");
|
||||
|
||||
public static final CommandTx CMD_RENAME_SOURCE = of("GetRenameSource");
|
||||
|
||||
public static final CommandTx CMD_DELETED_SOURCE = of("GetDeletedSource");
|
||||
|
||||
@XmlAttribute(name = "id")
|
||||
private String id;
|
||||
|
||||
@XmlValue
|
||||
private String value;
|
||||
|
||||
public CommandTx() {
|
||||
}
|
||||
|
||||
public CommandTx(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public static CommandTx of(String command) {
|
||||
CommandTx cmdTx = new CommandTx(command);
|
||||
cmdTx.setId(DEFAULT_ID);
|
||||
return cmdTx;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/**
|
||||
* 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.denonmarantz.internal.xml.entities.commands;
|
||||
|
||||
import javax.xml.bind.annotation.XmlAccessType;
|
||||
import javax.xml.bind.annotation.XmlAccessorType;
|
||||
import javax.xml.bind.annotation.XmlRootElement;
|
||||
|
||||
/**
|
||||
* Used to unmarshall <list> items of the <functiondelete> CommandRX.
|
||||
*
|
||||
* @author Jan-Willem Veldhuis - Initial contribution
|
||||
*/
|
||||
@XmlRootElement(name = "list")
|
||||
@XmlAccessorType(XmlAccessType.FIELD)
|
||||
public class DeletedSourceList {
|
||||
|
||||
private String name;
|
||||
|
||||
private String funcName;
|
||||
|
||||
private Integer use;
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public String getFuncName() {
|
||||
return funcName;
|
||||
}
|
||||
|
||||
public Integer getUse() {
|
||||
return use;
|
||||
}
|
||||
}
|
||||
@@ -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.denonmarantz.internal.xml.entities.commands;
|
||||
|
||||
import javax.xml.bind.annotation.XmlAccessType;
|
||||
import javax.xml.bind.annotation.XmlAccessorType;
|
||||
import javax.xml.bind.annotation.XmlRootElement;
|
||||
|
||||
/**
|
||||
* Used to unmarshall <list> items of the <functionrename> CommandRX.
|
||||
*
|
||||
* @author Jan-Willem Veldhuis - Initial contribution
|
||||
*/
|
||||
@XmlRootElement(name = "list")
|
||||
@XmlAccessorType(XmlAccessType.FIELD)
|
||||
public class RenameSourceList {
|
||||
|
||||
private String name;
|
||||
|
||||
private String rename;
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public String getRename() {
|
||||
return rename;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/**
|
||||
* 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.denonmarantz.internal.xml.entities.commands;
|
||||
|
||||
import javax.xml.bind.annotation.XmlAccessType;
|
||||
import javax.xml.bind.annotation.XmlAccessorType;
|
||||
import javax.xml.bind.annotation.XmlAttribute;
|
||||
import javax.xml.bind.annotation.XmlRootElement;
|
||||
import javax.xml.bind.annotation.XmlValue;
|
||||
|
||||
/**
|
||||
* Holds text values with a certain id
|
||||
*
|
||||
* @author Jeroen Idserda - Initial contribution
|
||||
*/
|
||||
@XmlRootElement(name = "text")
|
||||
@XmlAccessorType(XmlAccessType.FIELD)
|
||||
public class Text {
|
||||
|
||||
@XmlAttribute(name = "id")
|
||||
private String id;
|
||||
|
||||
@XmlValue
|
||||
private String value;
|
||||
|
||||
public Text() {
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public void setValue(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
@@ -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.denonmarantz.internal.xml.entities.types;
|
||||
|
||||
import javax.xml.bind.annotation.XmlAccessType;
|
||||
import javax.xml.bind.annotation.XmlAccessorType;
|
||||
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
|
||||
|
||||
import org.openhab.binding.denonmarantz.internal.xml.adapters.OnOffAdapter;
|
||||
|
||||
/**
|
||||
* Contains an On/Off value in the form of a boolean
|
||||
*
|
||||
* @author Jeroen Idserda - Initial contribution
|
||||
*/
|
||||
@XmlAccessorType(XmlAccessType.FIELD)
|
||||
public class OnOffType {
|
||||
|
||||
@XmlJavaTypeAdapter(OnOffAdapter.class)
|
||||
private Boolean value;
|
||||
|
||||
public Boolean getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public void setValue(Boolean value) {
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
@@ -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.denonmarantz.internal.xml.entities.types;
|
||||
|
||||
import javax.xml.bind.annotation.XmlAccessType;
|
||||
import javax.xml.bind.annotation.XmlAccessorType;
|
||||
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
|
||||
|
||||
import org.openhab.binding.denonmarantz.internal.xml.adapters.StringAdapter;
|
||||
|
||||
/**
|
||||
* Contains a string value
|
||||
*
|
||||
* @author Jeroen Idserda - Initial contribution
|
||||
*/
|
||||
@XmlAccessorType(XmlAccessType.FIELD)
|
||||
public class StringType {
|
||||
|
||||
@XmlJavaTypeAdapter(value = StringAdapter.class)
|
||||
private String value;
|
||||
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public void setValue(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
@@ -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.denonmarantz.internal.xml.entities.types;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
import javax.xml.bind.annotation.XmlAccessType;
|
||||
import javax.xml.bind.annotation.XmlAccessorType;
|
||||
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
|
||||
|
||||
import org.openhab.binding.denonmarantz.internal.xml.adapters.VolumeAdapter;
|
||||
|
||||
/**
|
||||
* Contains a volume value (percentage)
|
||||
*
|
||||
* @author Jeroen Idserda - Initial contribution
|
||||
*/
|
||||
@XmlAccessorType(XmlAccessType.FIELD)
|
||||
public class VolumeType {
|
||||
|
||||
@XmlJavaTypeAdapter(value = VolumeAdapter.class)
|
||||
private BigDecimal value;
|
||||
|
||||
public BigDecimal getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public void setValue(BigDecimal value) {
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<binding:binding id="denonmarantz" 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>DenonMarantz Binding</name>
|
||||
<description>Binding for controlling network enabled Denon and Marantz receivers.</description>
|
||||
<author>Jan-Willem Veldhuis, Jeroen Idserda</author>
|
||||
|
||||
</binding:binding>
|
||||
@@ -0,0 +1,236 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="denonmarantz"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<!-- AVR control using Telnet -->
|
||||
<thing-type id="avr">
|
||||
<label>Denon/Marantz AVR</label>
|
||||
<description>Control a Denon/Marantz AVR.</description>
|
||||
|
||||
<channel-groups>
|
||||
<channel-group id="general" typeId="general"/>
|
||||
<channel-group id="mainZone" typeId="zone">
|
||||
<label>Main Zone Control</label>
|
||||
<description>Channels for the main zone of this AVR.</description>
|
||||
</channel-group>
|
||||
<channel-group id="zone2" typeId="zone">
|
||||
<label>Zone 2 Control</label>
|
||||
<description>Channels for zone2 of this AVR.</description>
|
||||
</channel-group>
|
||||
<channel-group id="zone3" typeId="zone">
|
||||
<label>Zone 3 Control</label>
|
||||
<description>Channels for zone3 of this AVR.</description>
|
||||
</channel-group>
|
||||
<channel-group id="zone4" typeId="zone">
|
||||
<label>Zone 4 Control</label>
|
||||
<description>Channels for zone4 of this AVR.</description>
|
||||
</channel-group>
|
||||
</channel-groups>
|
||||
|
||||
<config-description>
|
||||
<parameter-group name="receiverProperties">
|
||||
<label>Receiver Properties</label>
|
||||
</parameter-group>
|
||||
<parameter-group name="connectionSettings">
|
||||
<label>General Connection Settings</label>
|
||||
</parameter-group>
|
||||
|
||||
<parameter-group name="telnetSettings">
|
||||
<label>Telnet Settings</label>
|
||||
<description>Settings for the Telnet port of the AVR</description>
|
||||
<advanced>true</advanced>
|
||||
</parameter-group>
|
||||
|
||||
<parameter-group name="httpSettings">
|
||||
<label>HTTP Settings</label>
|
||||
<description>Settings for the HTTP port of the AVR</description>
|
||||
<advanced>true</advanced>
|
||||
</parameter-group>
|
||||
|
||||
<parameter name="zoneCount" type="integer" groupName="receiverProperties">
|
||||
<label>Zone Count of the Receiver</label>
|
||||
<description>Number of zones (including main zone), values 1-4 are supported.</description>
|
||||
<default>2</default>
|
||||
</parameter>
|
||||
|
||||
<parameter name="host" type="text" required="true" groupName="connectionSettings">
|
||||
<context>network-address</context>
|
||||
<label>AVR Host or IP Address</label>
|
||||
<description>Hostname or IP address of the AVR to control.</description>
|
||||
</parameter>
|
||||
|
||||
<parameter name="telnetEnabled" type="boolean" groupName="connectionSettings">
|
||||
<label>Use Telnet Port</label>
|
||||
<description>By using telnet the AVR updates are received immediately. Also, some devices only support telnet.
|
||||
However, the AVR only allows 1 simultaneous connection. Uncheck if you are using dedicated apps to control the AVR.
|
||||
Then HTTP polling is used instead.</description>
|
||||
</parameter>
|
||||
|
||||
<parameter name="telnetPort" type="integer" groupName="telnetSettings">
|
||||
<label>Telnet Port</label>
|
||||
<description>Telnet port used for AVR communication. Normally shouldn't be changed.</description>
|
||||
<default>23</default>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
|
||||
<parameter name="httpPort" type="integer" groupName="httpSettings">
|
||||
<label>HTTP Port</label>
|
||||
<description>HTTP Port used for AVR communication. Normally shouldn't be changed.</description>
|
||||
<default>80</default>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
|
||||
<parameter name="httpPollingInterval" type="integer" groupName="httpSettings">
|
||||
<label>Polling Interval</label>
|
||||
<description>Refresh interval of the HTTP API in seconds (minimal 5)</description>
|
||||
<default>5</default>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</thing-type>
|
||||
|
||||
<channel-group-type id="general">
|
||||
<label>General Control</label>
|
||||
<description>General channels for this AVR.</description>
|
||||
<channels>
|
||||
<channel id="power" typeId="mainPower"/>
|
||||
<channel id="surroundProgram" typeId="surroundProgram"/>
|
||||
<channel id="artist" typeId="artist"/>
|
||||
<channel id="album" typeId="album"/>
|
||||
<channel id="track" typeId="track"/>
|
||||
<channel id="command" typeId="command"/>
|
||||
</channels>
|
||||
</channel-group-type>
|
||||
|
||||
<channel-group-type id="zone">
|
||||
<label>Zone Control</label>
|
||||
<description>Channels for a zone of this AVR.</description>
|
||||
<channels>
|
||||
<channel id="power" typeId="zonePower"/>
|
||||
<channel id="volume" typeId="volume"/>
|
||||
<channel id="volumeDB" typeId="volumeDB"/>
|
||||
<channel id="mute" typeId="mute"/>
|
||||
<channel id="input" typeId="input"/>
|
||||
</channels>
|
||||
</channel-group-type>
|
||||
|
||||
<channel-type id="mainPower">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Power</label>
|
||||
<description>Power ON/OFF the AVR</description>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="zonePower">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Power (zone)</label>
|
||||
<description>Power ON/OFF this zone of the AVR</description>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="volume">
|
||||
<item-type>Dimmer</item-type>
|
||||
<label>Volume</label>
|
||||
<description>Set the volume level of this zone</description>
|
||||
<category>SoundVolume</category>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="volumeDB" advanced="true">
|
||||
<item-type>Number</item-type>
|
||||
<label>Volume (dB)</label>
|
||||
<description>Set the volume level (dB). Same as [mainVolume - 80].</description>
|
||||
<category>SoundVolume</category>
|
||||
<state min="-80" max="18" step="0.5" pattern="%.1f dB"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="mute">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Mute</label>
|
||||
<description>Enable/Disable Mute on this zone of the AVR</description>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="input">
|
||||
<item-type>String</item-type>
|
||||
<label>Input Source</label>
|
||||
<description>Select the input source for this zone of the AVR</description>
|
||||
<state>
|
||||
<options>
|
||||
<option value="DVD">DVD</option>
|
||||
<option value="BD">BD</option>
|
||||
<option value="TV">TV</option>
|
||||
<option value="SAT/CBL">SAT/CBL</option>
|
||||
<option value="SAT">SAT</option>
|
||||
<option value="MPLAY">MPLAY</option>
|
||||
<option value="VCR">VCR</option>
|
||||
<option value="GAME">GAME</option>
|
||||
<option value="V.AUX">V.AUX</option>
|
||||
<option value="TUNER">TUNER</option>
|
||||
<option value="HDRADIO">HDRADIO</option>
|
||||
<option value="SIRIUS">SIRIUS</option>
|
||||
<option value="SPOTIFY">SPOTIFY</option>
|
||||
<option value="SIRIUSXM">SIRIUSXM</option>
|
||||
<option value="RHAPSODY">RHAPSODY</option>
|
||||
<option value="PANDORA">PANDORA</option>
|
||||
<option value="NAPSTER">NAPSTER</option>
|
||||
<option value="LASTFM">LASTFM</option>
|
||||
<option value="FLICKR">FLICKR</option>
|
||||
<option value="IRADIO">IRADIO</option>
|
||||
<option value="SERVER">SERVER</option>
|
||||
<option value="FAVORITES">FAVORITES</option>
|
||||
<option value="CDR">CDR</option>
|
||||
<option value="AUX1">AUX1</option>
|
||||
<option value="AUX2">AUX2</option>
|
||||
<option value="AUX3">AUX3</option>
|
||||
<option value="AUX4">AUX4</option>
|
||||
<option value="AUX5">AUX5</option>
|
||||
<option value="AUX6">AUX6</option>
|
||||
<option value="AUX7">AUX7</option>
|
||||
<option value="NET">NET</option>
|
||||
<option value="NET/USB">NET/USB</option>
|
||||
<option value="BT">BT</option>
|
||||
<option value="M-XPORT">M-XPORT</option>
|
||||
<option value="USB/IPOD">USB/IPOD</option>
|
||||
<option value="USB">USB</option>
|
||||
<option value="IPD">IPD</option>
|
||||
<option value="IRP">IRP</option>
|
||||
<option value="FVP">FVP</option>
|
||||
<option value="OTP">OTP</option>
|
||||
</options>
|
||||
</state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="surroundProgram">
|
||||
<item-type>String</item-type>
|
||||
<label>Surround Program</label>
|
||||
<description>Select the surround program of the AVR</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="artist">
|
||||
<item-type>String</item-type>
|
||||
<label>Now Playing (artist)</label>
|
||||
<description>Displays the artist of the now playing song.</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="album">
|
||||
<item-type>String</item-type>
|
||||
<label>Now Playing (album)</label>
|
||||
<description>Displays the album of the now playing song.</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="track">
|
||||
<item-type>String</item-type>
|
||||
<label>Now Playing (track)</label>
|
||||
<description>Displays the title of the now playing track.</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="command" advanced="true">
|
||||
<item-type>String</item-type>
|
||||
<label>Send a Custom Command</label>
|
||||
<description>Use this channel to send any custom command, e.g. SITV or MSSTANDARD (check the protocol documentation)</description>
|
||||
</channel-type>
|
||||
|
||||
</thing:thing-descriptions>
|
||||
Reference in New Issue
Block a user