Skip to content

Commit

Permalink
Add HTTP protocol support for newer receivers
Browse files Browse the repository at this point in the history
Resolves openhab#16747

Signed-off-by: Jacob Laursen <jacob-github@vindvejr.dk>
  • Loading branch information
jlaur committed May 23, 2024
1 parent b5375fe commit 2419356
Show file tree
Hide file tree
Showing 4 changed files with 93 additions and 30 deletions.
7 changes: 3 additions & 4 deletions bundles/org.openhab.binding.denonmarantz/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,15 @@ This binding integrates Denon & Marantz AV receivers by using either Telnet or a
This binding supports Denon and Marantz receivers having a Telnet interface or a web based controller at `http://<AVR IP address>/`.
The thing type for all of them is `avr`.

Tested models: Marantz SR5008, Denon AVR-X2000 / X3000 / X1200W / X2100W / X2200W / X3100W / X3300W / X4400H

Denon models with HEOS support (`AVR-X..00H`) do not support the HTTP API. They do support Telnet.
During Discovery this is auto-detected and configured.
Tested models: Marantz SR5008, Denon AVR-3808 / AVR-X2000 / X3000 / X1200W / X2100W / X2200W / X3100W / X3300W / X4400H / X4800H

## Discovery

This binding can discover Denon and Marantz receivers using mDNS.
The serial number (which is the MAC address of the network interface) is used as unique identifier.

The protocol (including port for HTTP) will be auto-detected.

It tries to detect the number of zones (when the AVR responds to HTTP).
It defaults to 2 zones.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.StringWriter;
import java.math.BigDecimal;
import java.net.URLEncoder;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
Expand Down Expand Up @@ -48,6 +49,7 @@
import org.openhab.binding.denonmarantz.internal.xml.dto.commands.AppCommandResponse;
import org.openhab.binding.denonmarantz.internal.xml.dto.commands.CommandRx;
import org.openhab.binding.denonmarantz.internal.xml.dto.commands.CommandTx;
import org.openhab.binding.denonmarantz.internal.xml.dto.types.StringType;
import org.openhab.core.io.net.http.HttpUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand Down Expand Up @@ -196,6 +198,40 @@ private void updateMain() throws IOException {
}
}

private void updateMainZoneByAppCommand() throws IOException {
String url = statusUrl + URL_APP_COMMAND;
logger.trace("Refreshing URL: {}", url);

AppCommandRequest request = AppCommandRequest.of(CommandTx.CMD_ALL_POWER).add(CommandTx.CMD_VOLUME_LEVEL)
.add(CommandTx.CMD_MUTE_STATUS).add(CommandTx.CMD_SOURCE_STATUS).add(CommandTx.CMD_SURROUND_STATUS);
AppCommandResponse response = postDocument(url, AppCommandResponse.class, request);

if (response != null) {
for (CommandRx rx : response.getCommands()) {
String inputSource = rx.getSource();
if (inputSource != null) {
state.setInput(inputSource);
}
Boolean power = rx.getZone1();
if (power != null) {
state.setMainZonePower(power.booleanValue());
}
BigDecimal volume = rx.getVolume();
if (volume != null) {
state.setMainVolume(volume);
}
Boolean mute = rx.getMute();
if (mute != null) {
state.setMute(mute.booleanValue());
}
String surroundMode = rx.getSurround();
if (surroundMode != null) {
state.setSurroundProgram(surroundMode);
}
}
}
}

private void updateMainZone() throws IOException {
String url = statusUrl + URL_ZONE_MAIN;
logger.trace("Refreshing URL: {}", url);
Expand All @@ -211,10 +247,11 @@ private void updateMainZone() throws IOException {
config.inputOptions = mainZone.getInputFuncList();
}

if (mainZone.getSurrMode() == null) {
StringType surroundMode = mainZone.getSurrMode();
if (surroundMode == null) {
logger.debug("Unable to get the SURROUND_MODE. MainZone update may not be correct.");
} else {
state.setSurroundProgram(mainZone.getSurrMode().getValue());
state.setSurroundProgram(surroundMode.getValue());
}
}
}
Expand Down Expand Up @@ -298,8 +335,12 @@ private boolean setConfigProperties() throws IOException {
private void refreshHttpProperties() throws IOException {
logger.trace("Refreshing Denon status");

updateMain();
updateMainZone();
if (config.getHttpPort() == 8080) {
updateMainZoneByAppCommand();
} else {
updateMain();
updateMainZone();
}
updateSecondaryZones();
updateDisplayInfo();
}
Expand Down Expand Up @@ -345,6 +386,7 @@ private <T, S> T postDocument(String uri, Class<T> response, S request) throws I

ByteArrayInputStream inputStream = new ByteArrayInputStream(sw.toString().getBytes(StandardCharsets.UTF_8));
String result = HttpUtil.executeUrl("POST", uri, inputStream, CONTENT_TYPE_XML, REQUEST_TIMEOUT_MS);
logger.trace("result of postDocument for uri '{}':\r\n{}", uri, result);

if (result != null && !result.isBlank()) {
JAXBContext jcResponse = JAXBContext.newInstance(response);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,7 @@ private void autoConfigure() throws InterruptedException {
httpApiUsable = true;
}
} catch (TimeoutException | ExecutionException e) {
logger.debug("Error when trying to access AVR using HTTP on port 80, reverting to Telnet mode.", e);
logger.debug("Error when trying to access AVR using HTTP on port 80.", e);
}

if (telnetEnable) {
Expand All @@ -239,13 +239,15 @@ private void autoConfigure() throws InterruptedException {
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.");
logger.debug("This model responds to HTTP port 8080, disabling the Telnet mode by default.");
telnetEnable = false;
httpPort = 8080;
httpApiUsable = true;
}
} catch (TimeoutException | ExecutionException e) {
logger.debug("Additionally tried to connect to port 8080, this also failed", e);
logger.debug(
"Additionally tried to connect to port 8080, this also failed. Reverting to Telnet mode.",
e);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
*/
package org.openhab.binding.denonmarantz.internal.xml.dto.commands;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;

Expand All @@ -20,9 +21,12 @@
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlElementWrapper;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;

import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.denonmarantz.internal.xml.adapters.OnOffAdapter;
import org.openhab.binding.denonmarantz.internal.xml.adapters.VolumeAdapter;

/**
* Response to a {@link CommandTx}
Expand All @@ -33,21 +37,27 @@
@XmlAccessorType(XmlAccessType.FIELD)
public class CommandRx {

private String zone1;
@XmlJavaTypeAdapter(OnOffAdapter.class)
private Boolean zone1;

private String zone2;
@XmlJavaTypeAdapter(OnOffAdapter.class)
private Boolean zone2;

private String zone3;
@XmlJavaTypeAdapter(OnOffAdapter.class)
private Boolean zone3;

private String zone4;
@XmlJavaTypeAdapter(OnOffAdapter.class)
private Boolean zone4;

private String volume;
@XmlJavaTypeAdapter(value = VolumeAdapter.class)
private BigDecimal volume;

private String disptype;

private String dispvalue;

private String mute;
@XmlJavaTypeAdapter(OnOffAdapter.class)
private Boolean mute;

private String type;

Expand All @@ -72,46 +82,48 @@ public class CommandRx {

private String source;

private String surround;

public CommandRx() {
}

public String getZone1() {
public Boolean getZone1() {
return zone1;
}

public void setZone1(String zone1) {
public void setZone1(Boolean zone1) {
this.zone1 = zone1;
}

public String getZone2() {
public Boolean getZone2() {
return zone2;
}

public void setZone2(String zone2) {
public void setZone2(Boolean zone2) {
this.zone2 = zone2;
}

public String getZone3() {
public Boolean getZone3() {
return zone3;
}

public void setZone3(String zone3) {
public void setZone3(Boolean zone3) {
this.zone3 = zone3;
}

public String getZone4() {
public Boolean getZone4() {
return zone4;
}

public void setZone4(String zone4) {
public void setZone4(Boolean zone4) {
this.zone4 = zone4;
}

public String getVolume() {
public BigDecimal getVolume() {
return volume;
}

public void setVolume(String volume) {
public void setVolume(BigDecimal volume) {
this.volume = volume;
}

Expand All @@ -131,11 +143,11 @@ public void setDispvalue(String dispvalue) {
this.dispvalue = dispvalue;
}

public String getMute() {
public Boolean getMute() {
return mute;
}

public void setMute(String mute) {
public void setMute(Boolean mute) {
this.mute = mute;
}

Expand Down Expand Up @@ -187,6 +199,14 @@ public void setSource(String source) {
this.source = source;
}

public String getSurround() {
return surround;
}

public void setSurround(String surround) {
this.surround = surround;
}

public @Nullable String getText(@NonNull String key) {
for (Text text : texts) {
if (key.equals(text.getId())) {
Expand Down

0 comments on commit 2419356

Please sign in to comment.