From 546700eb367aba61d7c5fdc9bce5edb9eac9eb49 Mon Sep 17 00:00:00 2001 From: Jacob Laursen Date: Sun, 12 May 2024 00:05:56 +0200 Subject: [PATCH 1/2] Add HTTP protocol support for newer receivers Resolves #16747 Signed-off-by: Jacob Laursen --- .../README.md | 10 +- .../http/DenonMarantzHttpConnector.java | 257 +++++++++++++----- .../exception/HttpCommunicationException.java | 37 +++ .../internal/handler/DenonMarantzHandler.java | 10 +- .../internal/xml/dto/commands/CommandRx.java | 56 ++-- 5 files changed, 272 insertions(+), 98 deletions(-) create mode 100644 bundles/org.openhab.binding.denonmarantz/src/main/java/org/openhab/binding/denonmarantz/internal/exception/HttpCommunicationException.java diff --git a/bundles/org.openhab.binding.denonmarantz/README.md b/bundles/org.openhab.binding.denonmarantz/README.md index 7617ea5a1840..525f2e954576 100644 --- a/bundles/org.openhab.binding.denonmarantz/README.md +++ b/bundles/org.openhab.binding.denonmarantz/README.md @@ -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:///`. 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-4520 / 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 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 defaults to 2 zones. +It defaults to two zones. ## Thing Configuration diff --git a/bundles/org.openhab.binding.denonmarantz/src/main/java/org/openhab/binding/denonmarantz/internal/connector/http/DenonMarantzHttpConnector.java b/bundles/org.openhab.binding.denonmarantz/src/main/java/org/openhab/binding/denonmarantz/internal/connector/http/DenonMarantzHttpConnector.java index 37be5ec3073c..6f8a2b878f8f 100644 --- a/bundles/org.openhab.binding.denonmarantz/src/main/java/org/openhab/binding/denonmarantz/internal/connector/http/DenonMarantzHttpConnector.java +++ b/bundles/org.openhab.binding.denonmarantz/src/main/java/org/openhab/binding/denonmarantz/internal/connector/http/DenonMarantzHttpConnector.java @@ -14,14 +14,16 @@ import java.beans.Introspector; 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; +import java.util.concurrent.ExecutionException; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBException; @@ -35,11 +37,17 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; 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.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.config.DenonMarantzConfiguration; 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.Main; import org.openhab.binding.denonmarantz.internal.xml.dto.ZoneStatus; @@ -48,7 +56,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.core.io.net.http.HttpUtil; +import org.openhab.binding.denonmarantz.internal.xml.dto.types.StringType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -92,6 +100,8 @@ public class DenonMarantzHttpConnector extends DenonMarantzConnector { private @Nullable ScheduledFuture pollingJob; + private boolean legacyApiSupported = true; + public DenonMarantzHttpConnector(DenonMarantzConfiguration config, DenonMarantzState state, ScheduledExecutorService scheduler, HttpClient httpClient) { super(config, scheduler, state); @@ -114,16 +124,19 @@ public void connect() { logger.debug("HTTP polling started."); try { setConfigProperties(); - } catch (IOException e) { + } catch (TimeoutException | ExecutionException | HttpCommunicationException e) { logger.debug("IO error while retrieving document:", e); state.connectionError("IO error while connecting to AVR: " + e.getMessage()); return; + } catch (InterruptedException e) { + logger.debug("Interrupted while retrieving document: {}", e.getMessage()); + Thread.currentThread().interrupt(); } pollingJob = scheduler.scheduleWithFixedDelay(() -> { try { refreshHttpProperties(); - } catch (IOException e) { + } catch (TimeoutException | ExecutionException e) { logger.debug("IO error while retrieving document", e); state.connectionError("IO error while connecting to AVR: " + e.getMessage()); stopPolling(); @@ -137,6 +150,9 @@ public void connect() { sb.append(s.toString()).append("\n"); } 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); } @@ -186,96 +202,163 @@ public void onComplete(@Nullable Result result) { }); } - private void updateMain() throws IOException { + private void updateMain() throws TimeoutException, ExecutionException, InterruptedException { String url = statusUrl + URL_MAIN; logger.trace("Refreshing URL: {}", url); - Main statusMain = getDocument(url, Main.class); - if (statusMain != null) { - state.setPower(statusMain.getPower().getValue()); + try { + Main statusMain = getDocument(url, Main.class); + 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; 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()); + try { + 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 (config.inputOptions == null) { - 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()); + } } - - if (mainZone.getSurrMode() == null) { - logger.debug("Unable to get the SURROUND_MODE. MainZone update may not be correct."); + } catch (HttpCommunicationException e) { + if (e.getHttpStatus() == HttpStatus.FORBIDDEN_403) { + legacyApiSupported = false; + logger.debug("Legacy API not supported, will attempt app command method"); } else { - state.setSurroundProgram(mainZone.getSurrMode().getValue()); + logger.debug("Failed to update main zone by legacy API: {}", e.getMessage()); + } + } + } + + 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 IOException { + private void updateSecondaryZones() throws TimeoutException, ExecutionException, InterruptedException { 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; + try { + 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; + } } + } 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; logger.trace("Refreshing URL: {}", url); 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) { - return; - } - CommandRx titleInfo = response.getCommands().get(0); - String artist = titleInfo.getText("artist"); - if (artist != null) { - state.setNowPlayingArtist(artist); - } - String album = titleInfo.getText("album"); - if (album != null) { - state.setNowPlayingAlbum(album); - } - String track = titleInfo.getText("track"); - if (track != null) { - state.setNowPlayingTrack(track); + if (response == null) { + return; + } + CommandRx titleInfo = response.getCommands().get(0); + String artist = titleInfo.getText("artist"); + if (artist != null) { + state.setNowPlayingArtist(artist); + } + String album = titleInfo.getText("album"); + if (album != null) { + state.setNowPlayingAlbum(album); + } + String track = titleInfo.getText("track"); + if (track != null) { + 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; logger.debug("Refreshing URL: {}", url); @@ -295,20 +378,39 @@ private boolean setConfigProperties() throws IOException { return (deviceinfo != null); } - private void refreshHttpProperties() throws IOException { + private void refreshHttpProperties() throws TimeoutException, ExecutionException, InterruptedException { logger.trace("Refreshing Denon status"); - updateMain(); - updateMainZone(); + if (legacyApiSupported) { + updateMain(); + updateMainZone(); + } + + if (!legacyApiSupported) { + updateMainZoneByAppCommand(); + } + updateSecondaryZones(); updateDisplayInfo(); } @Nullable - private T getDocument(String uri, Class response) throws IOException { + private T getDocument(String uri, Class response) + throws TimeoutException, ExecutionException, InterruptedException, HttpCommunicationException { try { - String result = HttpUtil.executeUrl("GET", uri, REQUEST_TIMEOUT_MS); - logger.trace("result of getDocument for uri '{}':\r\n{}", uri, result); + Request request = httpClient.newRequest(uri).timeout(REQUEST_TIMEOUT_MS, TimeUnit.MILLISECONDS) + .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 '{}'", status); + } if (result != null && !result.isBlank()) { JAXBContext jc = JAXBContext.newInstance(response); @@ -336,15 +438,28 @@ private T getDocument(String uri, Class response) throws IOException { } @Nullable - private T postDocument(String uri, Class response, S request) throws IOException { + private T postDocument(String uri, Class response, S request) + throws TimeoutException, ExecutionException, InterruptedException, HttpCommunicationException { 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); + Request httpRequest = httpClient.newRequest(uri).timeout(REQUEST_TIMEOUT_MS, TimeUnit.MILLISECONDS) + .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 '{}'", status); + } if (result != null && !result.isBlank()) { JAXBContext jcResponse = JAXBContext.newInstance(response); diff --git a/bundles/org.openhab.binding.denonmarantz/src/main/java/org/openhab/binding/denonmarantz/internal/exception/HttpCommunicationException.java b/bundles/org.openhab.binding.denonmarantz/src/main/java/org/openhab/binding/denonmarantz/internal/exception/HttpCommunicationException.java new file mode 100644 index 000000000000..0e1e35440840 --- /dev/null +++ b/bundles/org.openhab.binding.denonmarantz/src/main/java/org/openhab/binding/denonmarantz/internal/exception/HttpCommunicationException.java @@ -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; + } +} diff --git a/bundles/org.openhab.binding.denonmarantz/src/main/java/org/openhab/binding/denonmarantz/internal/handler/DenonMarantzHandler.java b/bundles/org.openhab.binding.denonmarantz/src/main/java/org/openhab/binding/denonmarantz/internal/handler/DenonMarantzHandler.java index e37d55e2893f..b821905e869b 100644 --- a/bundles/org.openhab.binding.denonmarantz/src/main/java/org/openhab/binding/denonmarantz/internal/handler/DenonMarantzHandler.java +++ b/bundles/org.openhab.binding.denonmarantz/src/main/java/org/openhab/binding/denonmarantz/internal/handler/DenonMarantzHandler.java @@ -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) { @@ -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); } } diff --git a/bundles/org.openhab.binding.denonmarantz/src/main/java/org/openhab/binding/denonmarantz/internal/xml/dto/commands/CommandRx.java b/bundles/org.openhab.binding.denonmarantz/src/main/java/org/openhab/binding/denonmarantz/internal/xml/dto/commands/CommandRx.java index 55531ce69d35..07c6f038da08 100644 --- a/bundles/org.openhab.binding.denonmarantz/src/main/java/org/openhab/binding/denonmarantz/internal/xml/dto/commands/CommandRx.java +++ b/bundles/org.openhab.binding.denonmarantz/src/main/java/org/openhab/binding/denonmarantz/internal/xml/dto/commands/CommandRx.java @@ -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; @@ -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} @@ -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; @@ -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; } @@ -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; } @@ -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())) { From 5d981f1c78b941963c84659c18bcfb8c5df45b6d Mon Sep 17 00:00:00 2001 From: Jacob Laursen Date: Sat, 25 May 2024 13:36:50 +0200 Subject: [PATCH 2/2] Fix exception message Signed-off-by: Jacob Laursen --- .../internal/connector/http/DenonMarantzHttpConnector.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bundles/org.openhab.binding.denonmarantz/src/main/java/org/openhab/binding/denonmarantz/internal/connector/http/DenonMarantzHttpConnector.java b/bundles/org.openhab.binding.denonmarantz/src/main/java/org/openhab/binding/denonmarantz/internal/connector/http/DenonMarantzHttpConnector.java index 6f8a2b878f8f..73d643654e93 100644 --- a/bundles/org.openhab.binding.denonmarantz/src/main/java/org/openhab/binding/denonmarantz/internal/connector/http/DenonMarantzHttpConnector.java +++ b/bundles/org.openhab.binding.denonmarantz/src/main/java/org/openhab/binding/denonmarantz/internal/connector/http/DenonMarantzHttpConnector.java @@ -409,7 +409,7 @@ private T getDocument(String uri, Class response) 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 '{}'", status); + throw new HttpCommunicationException("Error retrieving document for uri '" + uri + "'", status); } if (result != null && !result.isBlank()) { @@ -458,7 +458,7 @@ private T postDocument(String uri, Class response, S request) 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 '{}'", status); + throw new HttpCommunicationException("Error retrieving document for uri '" + uri + "'", status); } if (result != null && !result.isBlank()) {