mirror of
https://github.com/openhab/openhab-addons.git
synced 2025-01-10 15:11:59 +01:00
[denonmarantz] Add HTTP protocol support for newer receivers (#16748)
* Add HTTP protocol support for newer receivers Resolves #16747 Signed-off-by: Jacob Laursen <jacob-github@vindvejr.dk>
This commit is contained in:
parent
7dcaf86607
commit
f8700a9cdd
@ -7,18 +7,18 @@ 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>/`.
|
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`.
|
The thing type for all of them is `avr`.
|
||||||
|
|
||||||
Tested models: Marantz SR5008, Denon AVR-X2000 / X3000 / X1200W / X2100W / X2200W / X3100W / X3300W / X4400H
|
Tested models: Marantz SR5008, Denon AVR-3808 / AVR-4520 / AVR-X2000 / X3000 / X1200W / X2100W / X2200W / X3100W / X3300W / X4400H / X4800H
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
## Discovery
|
## Discovery
|
||||||
|
|
||||||
This binding can discover Denon and Marantz receivers using mDNS.
|
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 serial number (which is the MAC address of the network interface) is used as unique identifier.
|
||||||
|
|
||||||
|
The protocol will be auto-detected.
|
||||||
|
The HTTP port as well as slight variations in the API will be auto-detected as well.
|
||||||
|
|
||||||
It tries to detect the number of zones (when the AVR responds to HTTP).
|
It tries to detect the number of zones (when the AVR responds to HTTP).
|
||||||
It defaults to 2 zones.
|
It defaults to two zones.
|
||||||
|
|
||||||
## Thing Configuration
|
## Thing Configuration
|
||||||
|
|
||||||
|
@ -14,14 +14,16 @@ package org.openhab.binding.denonmarantz.internal.connector.http;
|
|||||||
|
|
||||||
import java.beans.Introspector;
|
import java.beans.Introspector;
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.StringWriter;
|
import java.io.StringWriter;
|
||||||
|
import java.math.BigDecimal;
|
||||||
import java.net.URLEncoder;
|
import java.net.URLEncoder;
|
||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
import java.util.concurrent.ScheduledExecutorService;
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
import java.util.concurrent.ScheduledFuture;
|
import java.util.concurrent.ScheduledFuture;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.TimeoutException;
|
||||||
|
|
||||||
import javax.xml.bind.JAXBContext;
|
import javax.xml.bind.JAXBContext;
|
||||||
import javax.xml.bind.JAXBException;
|
import javax.xml.bind.JAXBException;
|
||||||
@ -35,11 +37,17 @@ import javax.xml.stream.util.StreamReaderDelegate;
|
|||||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
import org.eclipse.jdt.annotation.Nullable;
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
import org.eclipse.jetty.client.HttpClient;
|
import org.eclipse.jetty.client.HttpClient;
|
||||||
|
import org.eclipse.jetty.client.api.ContentResponse;
|
||||||
|
import org.eclipse.jetty.client.api.Request;
|
||||||
import org.eclipse.jetty.client.api.Response;
|
import org.eclipse.jetty.client.api.Response;
|
||||||
import org.eclipse.jetty.client.api.Result;
|
import org.eclipse.jetty.client.api.Result;
|
||||||
|
import org.eclipse.jetty.client.util.StringContentProvider;
|
||||||
|
import org.eclipse.jetty.http.HttpMethod;
|
||||||
|
import org.eclipse.jetty.http.HttpStatus;
|
||||||
import org.openhab.binding.denonmarantz.internal.DenonMarantzState;
|
import org.openhab.binding.denonmarantz.internal.DenonMarantzState;
|
||||||
import org.openhab.binding.denonmarantz.internal.config.DenonMarantzConfiguration;
|
import org.openhab.binding.denonmarantz.internal.config.DenonMarantzConfiguration;
|
||||||
import org.openhab.binding.denonmarantz.internal.connector.DenonMarantzConnector;
|
import org.openhab.binding.denonmarantz.internal.connector.DenonMarantzConnector;
|
||||||
|
import org.openhab.binding.denonmarantz.internal.exception.HttpCommunicationException;
|
||||||
import org.openhab.binding.denonmarantz.internal.xml.dto.Deviceinfo;
|
import org.openhab.binding.denonmarantz.internal.xml.dto.Deviceinfo;
|
||||||
import org.openhab.binding.denonmarantz.internal.xml.dto.Main;
|
import org.openhab.binding.denonmarantz.internal.xml.dto.Main;
|
||||||
import org.openhab.binding.denonmarantz.internal.xml.dto.ZoneStatus;
|
import org.openhab.binding.denonmarantz.internal.xml.dto.ZoneStatus;
|
||||||
@ -48,7 +56,7 @@ import org.openhab.binding.denonmarantz.internal.xml.dto.commands.AppCommandRequ
|
|||||||
import org.openhab.binding.denonmarantz.internal.xml.dto.commands.AppCommandResponse;
|
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.CommandRx;
|
||||||
import org.openhab.binding.denonmarantz.internal.xml.dto.commands.CommandTx;
|
import org.openhab.binding.denonmarantz.internal.xml.dto.commands.CommandTx;
|
||||||
import org.openhab.core.io.net.http.HttpUtil;
|
import org.openhab.binding.denonmarantz.internal.xml.dto.types.StringType;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
@ -92,6 +100,8 @@ public class DenonMarantzHttpConnector extends DenonMarantzConnector {
|
|||||||
|
|
||||||
private @Nullable ScheduledFuture<?> pollingJob;
|
private @Nullable ScheduledFuture<?> pollingJob;
|
||||||
|
|
||||||
|
private boolean legacyApiSupported = true;
|
||||||
|
|
||||||
public DenonMarantzHttpConnector(DenonMarantzConfiguration config, DenonMarantzState state,
|
public DenonMarantzHttpConnector(DenonMarantzConfiguration config, DenonMarantzState state,
|
||||||
ScheduledExecutorService scheduler, HttpClient httpClient) {
|
ScheduledExecutorService scheduler, HttpClient httpClient) {
|
||||||
super(config, scheduler, state);
|
super(config, scheduler, state);
|
||||||
@ -114,16 +124,19 @@ public class DenonMarantzHttpConnector extends DenonMarantzConnector {
|
|||||||
logger.debug("HTTP polling started.");
|
logger.debug("HTTP polling started.");
|
||||||
try {
|
try {
|
||||||
setConfigProperties();
|
setConfigProperties();
|
||||||
} catch (IOException e) {
|
} catch (TimeoutException | ExecutionException | HttpCommunicationException e) {
|
||||||
logger.debug("IO error while retrieving document:", e);
|
logger.debug("IO error while retrieving document:", e);
|
||||||
state.connectionError("IO error while connecting to AVR: " + e.getMessage());
|
state.connectionError("IO error while connecting to AVR: " + e.getMessage());
|
||||||
return;
|
return;
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
logger.debug("Interrupted while retrieving document: {}", e.getMessage());
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
}
|
}
|
||||||
|
|
||||||
pollingJob = scheduler.scheduleWithFixedDelay(() -> {
|
pollingJob = scheduler.scheduleWithFixedDelay(() -> {
|
||||||
try {
|
try {
|
||||||
refreshHttpProperties();
|
refreshHttpProperties();
|
||||||
} catch (IOException e) {
|
} catch (TimeoutException | ExecutionException e) {
|
||||||
logger.debug("IO error while retrieving document", e);
|
logger.debug("IO error while retrieving document", e);
|
||||||
state.connectionError("IO error while connecting to AVR: " + e.getMessage());
|
state.connectionError("IO error while connecting to AVR: " + e.getMessage());
|
||||||
stopPolling();
|
stopPolling();
|
||||||
@ -137,6 +150,9 @@ public class DenonMarantzHttpConnector extends DenonMarantzConnector {
|
|||||||
sb.append(s.toString()).append("\n");
|
sb.append(s.toString()).append("\n");
|
||||||
}
|
}
|
||||||
logger.error("Error while polling Http: \"{}\". Stacktrace: \n{}", e.getMessage(), sb.toString());
|
logger.error("Error while polling Http: \"{}\". Stacktrace: \n{}", e.getMessage(), sb.toString());
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
logger.debug("Interrupted while polling: {}", e.getMessage());
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
}
|
}
|
||||||
}, 0, config.httpPollingInterval, TimeUnit.SECONDS);
|
}, 0, config.httpPollingInterval, TimeUnit.SECONDS);
|
||||||
}
|
}
|
||||||
@ -186,96 +202,163 @@ public class DenonMarantzHttpConnector extends DenonMarantzConnector {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateMain() throws IOException {
|
private void updateMain() throws TimeoutException, ExecutionException, InterruptedException {
|
||||||
String url = statusUrl + URL_MAIN;
|
String url = statusUrl + URL_MAIN;
|
||||||
logger.trace("Refreshing URL: {}", url);
|
logger.trace("Refreshing URL: {}", url);
|
||||||
|
|
||||||
Main statusMain = getDocument(url, Main.class);
|
try {
|
||||||
if (statusMain != null) {
|
Main statusMain = getDocument(url, Main.class);
|
||||||
state.setPower(statusMain.getPower().getValue());
|
if (statusMain != null) {
|
||||||
|
state.setPower(statusMain.getPower().getValue());
|
||||||
|
}
|
||||||
|
} catch (HttpCommunicationException e) {
|
||||||
|
if (e.getHttpStatus() == HttpStatus.FORBIDDEN_403) {
|
||||||
|
legacyApiSupported = false;
|
||||||
|
logger.debug("Legacy API not supported, will attempt app command method");
|
||||||
|
} else {
|
||||||
|
logger.debug("Failed to update main by legacy API: {}", e.getMessage());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateMainZone() throws IOException {
|
private void updateMainZone() throws TimeoutException, ExecutionException, InterruptedException {
|
||||||
String url = statusUrl + URL_ZONE_MAIN;
|
String url = statusUrl + URL_ZONE_MAIN;
|
||||||
logger.trace("Refreshing URL: {}", url);
|
logger.trace("Refreshing URL: {}", url);
|
||||||
|
|
||||||
ZoneStatus mainZone = getDocument(url, ZoneStatus.class);
|
try {
|
||||||
if (mainZone != null) {
|
ZoneStatus mainZone = getDocument(url, ZoneStatus.class);
|
||||||
state.setInput(mainZone.getInputFuncSelect().getValue());
|
if (mainZone != null) {
|
||||||
state.setMainVolume(mainZone.getMasterVolume().getValue());
|
state.setInput(mainZone.getInputFuncSelect().getValue());
|
||||||
state.setMainZonePower(mainZone.getPower().getValue());
|
state.setMainVolume(mainZone.getMasterVolume().getValue());
|
||||||
state.setMute(mainZone.getMute().getValue());
|
state.setMainZonePower(mainZone.getPower().getValue());
|
||||||
|
state.setMute(mainZone.getMute().getValue());
|
||||||
|
|
||||||
if (config.inputOptions == null) {
|
if (config.inputOptions == null) {
|
||||||
config.inputOptions = mainZone.getInputFuncList();
|
config.inputOptions = mainZone.getInputFuncList();
|
||||||
|
}
|
||||||
|
|
||||||
|
StringType surroundMode = mainZone.getSurrMode();
|
||||||
|
if (surroundMode == null) {
|
||||||
|
logger.debug("Unable to get the SURROUND_MODE. MainZone update may not be correct.");
|
||||||
|
} else {
|
||||||
|
state.setSurroundProgram(surroundMode.getValue());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
} catch (HttpCommunicationException e) {
|
||||||
if (mainZone.getSurrMode() == null) {
|
if (e.getHttpStatus() == HttpStatus.FORBIDDEN_403) {
|
||||||
logger.debug("Unable to get the SURROUND_MODE. MainZone update may not be correct.");
|
legacyApiSupported = false;
|
||||||
|
logger.debug("Legacy API not supported, will attempt app command method");
|
||||||
} else {
|
} else {
|
||||||
state.setSurroundProgram(mainZone.getSurrMode().getValue());
|
logger.debug("Failed to update main zone by legacy API: {}", e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateSecondaryZones() throws IOException {
|
private void updateMainZoneByAppCommand() throws TimeoutException, ExecutionException, InterruptedException {
|
||||||
|
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);
|
||||||
|
|
||||||
|
try {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (HttpCommunicationException e) {
|
||||||
|
logger.debug("Failed to update main zone by app command: {}", e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateSecondaryZones() throws TimeoutException, ExecutionException, InterruptedException {
|
||||||
for (int i = 2; i <= config.getZoneCount(); i++) {
|
for (int i = 2; i <= config.getZoneCount(); i++) {
|
||||||
String url = String.format("%s" + URL_ZONE_SECONDARY_LITE, statusUrl, i, i);
|
String url = String.format("%s" + URL_ZONE_SECONDARY_LITE, statusUrl, i, i);
|
||||||
logger.trace("Refreshing URL: {}", url);
|
logger.trace("Refreshing URL: {}", url);
|
||||||
ZoneStatusLite zoneSecondary = getDocument(url, ZoneStatusLite.class);
|
try {
|
||||||
if (zoneSecondary != null) {
|
ZoneStatusLite zoneSecondary = getDocument(url, ZoneStatusLite.class);
|
||||||
switch (i) {
|
if (zoneSecondary != null) {
|
||||||
// maximum 2 secondary zones are supported
|
switch (i) {
|
||||||
case 2:
|
// maximum 2 secondary zones are supported
|
||||||
state.setZone2Power(zoneSecondary.getPower().getValue());
|
case 2:
|
||||||
state.setZone2Volume(zoneSecondary.getMasterVolume().getValue());
|
state.setZone2Power(zoneSecondary.getPower().getValue());
|
||||||
state.setZone2Mute(zoneSecondary.getMute().getValue());
|
state.setZone2Volume(zoneSecondary.getMasterVolume().getValue());
|
||||||
state.setZone2Input(zoneSecondary.getInputFuncSelect().getValue());
|
state.setZone2Mute(zoneSecondary.getMute().getValue());
|
||||||
break;
|
state.setZone2Input(zoneSecondary.getInputFuncSelect().getValue());
|
||||||
case 3:
|
break;
|
||||||
state.setZone3Power(zoneSecondary.getPower().getValue());
|
case 3:
|
||||||
state.setZone3Volume(zoneSecondary.getMasterVolume().getValue());
|
state.setZone3Power(zoneSecondary.getPower().getValue());
|
||||||
state.setZone3Mute(zoneSecondary.getMute().getValue());
|
state.setZone3Volume(zoneSecondary.getMasterVolume().getValue());
|
||||||
state.setZone3Input(zoneSecondary.getInputFuncSelect().getValue());
|
state.setZone3Mute(zoneSecondary.getMute().getValue());
|
||||||
break;
|
state.setZone3Input(zoneSecondary.getInputFuncSelect().getValue());
|
||||||
case 4:
|
break;
|
||||||
state.setZone4Power(zoneSecondary.getPower().getValue());
|
case 4:
|
||||||
state.setZone4Volume(zoneSecondary.getMasterVolume().getValue());
|
state.setZone4Power(zoneSecondary.getPower().getValue());
|
||||||
state.setZone4Mute(zoneSecondary.getMute().getValue());
|
state.setZone4Volume(zoneSecondary.getMasterVolume().getValue());
|
||||||
state.setZone4Input(zoneSecondary.getInputFuncSelect().getValue());
|
state.setZone4Mute(zoneSecondary.getMute().getValue());
|
||||||
break;
|
state.setZone4Input(zoneSecondary.getInputFuncSelect().getValue());
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
} catch (HttpCommunicationException e) {
|
||||||
|
logger.debug("Failed to update zone {}: {}", i, e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateDisplayInfo() throws IOException {
|
private void updateDisplayInfo() throws TimeoutException, ExecutionException, InterruptedException {
|
||||||
String url = statusUrl + URL_APP_COMMAND;
|
String url = statusUrl + URL_APP_COMMAND;
|
||||||
logger.trace("Refreshing URL: {}", url);
|
logger.trace("Refreshing URL: {}", url);
|
||||||
|
|
||||||
AppCommandRequest request = AppCommandRequest.of(CommandTx.CMD_NET_STATUS);
|
AppCommandRequest request = AppCommandRequest.of(CommandTx.CMD_NET_STATUS);
|
||||||
AppCommandResponse response = postDocument(url, AppCommandResponse.class, request);
|
try {
|
||||||
|
AppCommandResponse response = postDocument(url, AppCommandResponse.class, request);
|
||||||
|
|
||||||
if (response == null) {
|
if (response == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
CommandRx titleInfo = response.getCommands().get(0);
|
CommandRx titleInfo = response.getCommands().get(0);
|
||||||
String artist = titleInfo.getText("artist");
|
String artist = titleInfo.getText("artist");
|
||||||
if (artist != null) {
|
if (artist != null) {
|
||||||
state.setNowPlayingArtist(artist);
|
state.setNowPlayingArtist(artist);
|
||||||
}
|
}
|
||||||
String album = titleInfo.getText("album");
|
String album = titleInfo.getText("album");
|
||||||
if (album != null) {
|
if (album != null) {
|
||||||
state.setNowPlayingAlbum(album);
|
state.setNowPlayingAlbum(album);
|
||||||
}
|
}
|
||||||
String track = titleInfo.getText("track");
|
String track = titleInfo.getText("track");
|
||||||
if (track != null) {
|
if (track != null) {
|
||||||
state.setNowPlayingTrack(track);
|
state.setNowPlayingTrack(track);
|
||||||
|
}
|
||||||
|
} catch (HttpCommunicationException e) {
|
||||||
|
logger.debug("Failed to update display info: {}", e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean setConfigProperties() throws IOException {
|
private boolean setConfigProperties()
|
||||||
|
throws TimeoutException, ExecutionException, InterruptedException, HttpCommunicationException {
|
||||||
String url = statusUrl + URL_DEVICE_INFO;
|
String url = statusUrl + URL_DEVICE_INFO;
|
||||||
logger.debug("Refreshing URL: {}", url);
|
logger.debug("Refreshing URL: {}", url);
|
||||||
|
|
||||||
@ -295,20 +378,39 @@ public class DenonMarantzHttpConnector extends DenonMarantzConnector {
|
|||||||
return (deviceinfo != null);
|
return (deviceinfo != null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void refreshHttpProperties() throws IOException {
|
private void refreshHttpProperties() throws TimeoutException, ExecutionException, InterruptedException {
|
||||||
logger.trace("Refreshing Denon status");
|
logger.trace("Refreshing Denon status");
|
||||||
|
|
||||||
updateMain();
|
if (legacyApiSupported) {
|
||||||
updateMainZone();
|
updateMain();
|
||||||
|
updateMainZone();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!legacyApiSupported) {
|
||||||
|
updateMainZoneByAppCommand();
|
||||||
|
}
|
||||||
|
|
||||||
updateSecondaryZones();
|
updateSecondaryZones();
|
||||||
updateDisplayInfo();
|
updateDisplayInfo();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private <T> T getDocument(String uri, Class<T> response) throws IOException {
|
private <T> T getDocument(String uri, Class<T> response)
|
||||||
|
throws TimeoutException, ExecutionException, InterruptedException, HttpCommunicationException {
|
||||||
try {
|
try {
|
||||||
String result = HttpUtil.executeUrl("GET", uri, REQUEST_TIMEOUT_MS);
|
Request request = httpClient.newRequest(uri).timeout(REQUEST_TIMEOUT_MS, TimeUnit.MILLISECONDS)
|
||||||
logger.trace("result of getDocument for uri '{}':\r\n{}", uri, result);
|
.method(HttpMethod.GET);
|
||||||
|
|
||||||
|
ContentResponse contentResponse = request.send();
|
||||||
|
|
||||||
|
String result = contentResponse.getContentAsString();
|
||||||
|
int status = contentResponse.getStatus();
|
||||||
|
|
||||||
|
logger.trace("result of getDocument for uri '{}' (status code {}):\r\n{}", uri, status, result);
|
||||||
|
|
||||||
|
if (!HttpStatus.isSuccess(status)) {
|
||||||
|
throw new HttpCommunicationException("Error retrieving document for uri '" + uri + "'", status);
|
||||||
|
}
|
||||||
|
|
||||||
if (result != null && !result.isBlank()) {
|
if (result != null && !result.isBlank()) {
|
||||||
JAXBContext jc = JAXBContext.newInstance(response);
|
JAXBContext jc = JAXBContext.newInstance(response);
|
||||||
@ -336,15 +438,28 @@ public class DenonMarantzHttpConnector extends DenonMarantzConnector {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private <T, S> T postDocument(String uri, Class<T> response, S request) throws IOException {
|
private <T, S> T postDocument(String uri, Class<T> response, S request)
|
||||||
|
throws TimeoutException, ExecutionException, InterruptedException, HttpCommunicationException {
|
||||||
try {
|
try {
|
||||||
JAXBContext jaxbContext = JAXBContext.newInstance(request.getClass());
|
JAXBContext jaxbContext = JAXBContext.newInstance(request.getClass());
|
||||||
Marshaller jaxbMarshaller = jaxbContext.createMarshaller();
|
Marshaller jaxbMarshaller = jaxbContext.createMarshaller();
|
||||||
StringWriter sw = new StringWriter();
|
StringWriter sw = new StringWriter();
|
||||||
jaxbMarshaller.marshal(request, sw);
|
jaxbMarshaller.marshal(request, sw);
|
||||||
|
|
||||||
ByteArrayInputStream inputStream = new ByteArrayInputStream(sw.toString().getBytes(StandardCharsets.UTF_8));
|
Request httpRequest = httpClient.newRequest(uri).timeout(REQUEST_TIMEOUT_MS, TimeUnit.MILLISECONDS)
|
||||||
String result = HttpUtil.executeUrl("POST", uri, inputStream, CONTENT_TYPE_XML, REQUEST_TIMEOUT_MS);
|
.content(new StringContentProvider(sw.toString(), StandardCharsets.UTF_8), CONTENT_TYPE_XML)
|
||||||
|
.method(HttpMethod.POST);
|
||||||
|
|
||||||
|
ContentResponse contentResponse = httpRequest.send();
|
||||||
|
|
||||||
|
String result = contentResponse.getContentAsString();
|
||||||
|
int status = contentResponse.getStatus();
|
||||||
|
|
||||||
|
logger.trace("result of postDocument for uri '{}' (status code {}):\r\n{}", uri, status, result);
|
||||||
|
|
||||||
|
if (!HttpStatus.isSuccess(status)) {
|
||||||
|
throw new HttpCommunicationException("Error retrieving document for uri '" + uri + "'", status);
|
||||||
|
}
|
||||||
|
|
||||||
if (result != null && !result.isBlank()) {
|
if (result != null && !result.isBlank()) {
|
||||||
JAXBContext jcResponse = JAXBContext.newInstance(response);
|
JAXBContext jcResponse = JAXBContext.newInstance(response);
|
||||||
|
@ -0,0 +1,37 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2024 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.binding.denonmarantz.internal.exception;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link HttpCommunicationException} is a generic exception thrown in case
|
||||||
|
* of communication failure or unexpected response.
|
||||||
|
*
|
||||||
|
* @author Jacob Laursen - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class HttpCommunicationException extends Exception {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
private int httpStatus = 0;
|
||||||
|
|
||||||
|
public HttpCommunicationException(String message, int httpStatus) {
|
||||||
|
super(message);
|
||||||
|
this.httpStatus = httpStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getHttpStatus() {
|
||||||
|
return httpStatus;
|
||||||
|
}
|
||||||
|
}
|
@ -229,7 +229,7 @@ public class DenonMarantzHandler extends BaseThingHandler implements DenonMarant
|
|||||||
httpApiUsable = true;
|
httpApiUsable = true;
|
||||||
}
|
}
|
||||||
} catch (TimeoutException | ExecutionException e) {
|
} 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) {
|
if (telnetEnable) {
|
||||||
@ -239,13 +239,15 @@ public class DenonMarantzHandler extends BaseThingHandler implements DenonMarant
|
|||||||
response = httpClient.newRequest("http://" + host + ":8080/goform/Deviceinfo.xml")
|
response = httpClient.newRequest("http://" + host + ":8080/goform/Deviceinfo.xml")
|
||||||
.timeout(3, TimeUnit.SECONDS).send();
|
.timeout(3, TimeUnit.SECONDS).send();
|
||||||
if (response.getStatus() == HttpURLConnection.HTTP_OK) {
|
if (response.getStatus() == HttpURLConnection.HTTP_OK) {
|
||||||
logger.debug(
|
logger.debug("This model responds to HTTP port 8080, disabling the Telnet mode by default.");
|
||||||
"This model responds to HTTP port 8080, we use this port to retrieve the number of zones.");
|
telnetEnable = false;
|
||||||
httpPort = 8080;
|
httpPort = 8080;
|
||||||
httpApiUsable = true;
|
httpApiUsable = true;
|
||||||
}
|
}
|
||||||
} catch (TimeoutException | ExecutionException e) {
|
} 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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
*/
|
*/
|
||||||
package org.openhab.binding.denonmarantz.internal.xml.dto.commands;
|
package org.openhab.binding.denonmarantz.internal.xml.dto.commands;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@ -20,9 +21,12 @@ import javax.xml.bind.annotation.XmlAccessorType;
|
|||||||
import javax.xml.bind.annotation.XmlElement;
|
import javax.xml.bind.annotation.XmlElement;
|
||||||
import javax.xml.bind.annotation.XmlElementWrapper;
|
import javax.xml.bind.annotation.XmlElementWrapper;
|
||||||
import javax.xml.bind.annotation.XmlRootElement;
|
import javax.xml.bind.annotation.XmlRootElement;
|
||||||
|
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
|
||||||
|
|
||||||
import org.eclipse.jdt.annotation.NonNull;
|
import org.eclipse.jdt.annotation.NonNull;
|
||||||
import org.eclipse.jdt.annotation.Nullable;
|
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}
|
* Response to a {@link CommandTx}
|
||||||
@ -33,21 +37,27 @@ import org.eclipse.jdt.annotation.Nullable;
|
|||||||
@XmlAccessorType(XmlAccessType.FIELD)
|
@XmlAccessorType(XmlAccessType.FIELD)
|
||||||
public class CommandRx {
|
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 disptype;
|
||||||
|
|
||||||
private String dispvalue;
|
private String dispvalue;
|
||||||
|
|
||||||
private String mute;
|
@XmlJavaTypeAdapter(OnOffAdapter.class)
|
||||||
|
private Boolean mute;
|
||||||
|
|
||||||
private String type;
|
private String type;
|
||||||
|
|
||||||
@ -72,46 +82,48 @@ public class CommandRx {
|
|||||||
|
|
||||||
private String source;
|
private String source;
|
||||||
|
|
||||||
|
private String surround;
|
||||||
|
|
||||||
public CommandRx() {
|
public CommandRx() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getZone1() {
|
public Boolean getZone1() {
|
||||||
return zone1;
|
return zone1;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setZone1(String zone1) {
|
public void setZone1(Boolean zone1) {
|
||||||
this.zone1 = zone1;
|
this.zone1 = zone1;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getZone2() {
|
public Boolean getZone2() {
|
||||||
return zone2;
|
return zone2;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setZone2(String zone2) {
|
public void setZone2(Boolean zone2) {
|
||||||
this.zone2 = zone2;
|
this.zone2 = zone2;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getZone3() {
|
public Boolean getZone3() {
|
||||||
return zone3;
|
return zone3;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setZone3(String zone3) {
|
public void setZone3(Boolean zone3) {
|
||||||
this.zone3 = zone3;
|
this.zone3 = zone3;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getZone4() {
|
public Boolean getZone4() {
|
||||||
return zone4;
|
return zone4;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setZone4(String zone4) {
|
public void setZone4(Boolean zone4) {
|
||||||
this.zone4 = zone4;
|
this.zone4 = zone4;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getVolume() {
|
public BigDecimal getVolume() {
|
||||||
return volume;
|
return volume;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setVolume(String volume) {
|
public void setVolume(BigDecimal volume) {
|
||||||
this.volume = volume;
|
this.volume = volume;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -131,11 +143,11 @@ public class CommandRx {
|
|||||||
this.dispvalue = dispvalue;
|
this.dispvalue = dispvalue;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getMute() {
|
public Boolean getMute() {
|
||||||
return mute;
|
return mute;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setMute(String mute) {
|
public void setMute(Boolean mute) {
|
||||||
this.mute = mute;
|
this.mute = mute;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -187,6 +199,14 @@ public class CommandRx {
|
|||||||
this.source = source;
|
this.source = source;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getSurround() {
|
||||||
|
return surround;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSurround(String surround) {
|
||||||
|
this.surround = surround;
|
||||||
|
}
|
||||||
|
|
||||||
public @Nullable String getText(@NonNull String key) {
|
public @Nullable String getText(@NonNull String key) {
|
||||||
for (Text text : texts) {
|
for (Text text : texts) {
|
||||||
if (key.equals(text.getId())) {
|
if (key.equals(text.getId())) {
|
||||||
|
Loading…
Reference in New Issue
Block a user