2 * Copyright (c) 2010-2021 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
7 * This program and the accompanying materials are made available under the
8 * terms of the Eclipse Public License 2.0 which is available at
9 * http://www.eclipse.org/legal/epl-2.0
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.yamahareceiver.internal.protocol.xml;
15 import static org.openhab.binding.yamahareceiver.internal.YamahaReceiverBindingConstants.Zone.*;
16 import static org.openhab.binding.yamahareceiver.internal.protocol.xml.XMLConstants.Commands.*;
17 import static org.openhab.binding.yamahareceiver.internal.protocol.xml.XMLProtocolService.*;
18 import static org.openhab.binding.yamahareceiver.internal.protocol.xml.XMLUtils.*;
20 import java.io.IOException;
21 import java.lang.ref.WeakReference;
24 import org.apache.commons.lang.StringUtils;
25 import org.openhab.binding.yamahareceiver.internal.YamahaReceiverBindingConstants.Feature;
26 import org.openhab.binding.yamahareceiver.internal.YamahaReceiverBindingConstants.Zone;
27 import org.openhab.binding.yamahareceiver.internal.protocol.AbstractConnection;
28 import org.openhab.binding.yamahareceiver.internal.protocol.DeviceInformation;
29 import org.openhab.binding.yamahareceiver.internal.protocol.ReceivedMessageParseException;
30 import org.openhab.binding.yamahareceiver.internal.state.DeviceInformationState;
31 import org.openhab.binding.yamahareceiver.internal.state.SystemControlState;
32 import org.slf4j.Logger;
33 import org.slf4j.LoggerFactory;
34 import org.w3c.dom.Node;
37 * The system control protocol class is used to control basic non-zone functionality
38 * of a Yamaha receiver with HTTP/xml.
39 * No state will be saved in here, but in {@link SystemControlState} instead.
41 * @author David Graeff - Initial contribution
42 * @author Tomasz Maruszak - DAB support, Spotify support, better feature detection
44 public class DeviceInformationXML implements DeviceInformation {
45 private final Logger logger = LoggerFactory.getLogger(DeviceInformationXML.class);
47 private final WeakReference<AbstractConnection> comReference;
48 protected DeviceInformationState state;
50 public DeviceInformationXML(AbstractConnection com, DeviceInformationState state) {
51 this.comReference = new WeakReference<>(com);
56 * We need that called only once. Will give us name, id, version and zone information.
60 * <Main_Zone>1</Main_Zone>
66 * <HD_Radio>0</HD_Radio>
67 * <Rhapsody>0</Rhapsody>
68 * <Napster>0</Napster>
69 * <SiriusXM>0</SiriusXM>
70 * <Spotify>1</Spotify>
71 * <Pandora>0</Pandora>
73 * <MusicCast_Link>1</MusicCast_Link>
75 * <NET_RADIO>1</NET_RADIO>
76 * <Bluetooth>1</Bluetooth>
78 * <iPod_USB>1</iPod_USB>
79 * <AirPlay>1</AirPlay>
80 * </Feature_Existence>
85 public void update() throws IOException, ReceivedMessageParseException {
86 XMLConnection con = (XMLConnection) comReference.get();
88 Node systemConfigNode = getResponse(con, SYSTEM_STATUS_CONFIG_CMD, SYSTEM_STATUS_CONFIG_PATH);
90 state.host = con.getHost();
91 state.name = getNodeContentOrEmpty(systemConfigNode, "Model_Name");
92 state.id = getNodeContentOrEmpty(systemConfigNode, "System_ID");
93 state.version = getNodeContentOrEmpty(systemConfigNode, "Version");
96 state.features.clear();
97 state.properties.clear();
99 // Get and store the Yamaha Description XML. This will be used to detect proper command naming in other areas.
100 DeviceDescriptorXML descriptor = new DeviceDescriptorXML();
101 descriptor.load(con);
102 descriptor.attach(state);
104 Node featureNode = getNode(systemConfigNode, "Feature_Existence");
105 if (featureNode != null) {
106 for (Zone zone : Zone.values()) {
107 checkFeature(featureNode, zone.toString(), zone, state.zones);
110 XMLConstants.FEATURE_BY_YNC_TAG
111 .forEach((name, feature) -> checkFeature(featureNode, name, feature, state.features));
114 // on older models (RX-V3900) the Feature_Existence element does not exist
116 descriptor.zones.forEach((zone, x) -> state.zones.add(zone));
117 descriptor.features.forEach((feature, x) -> state.features.add(feature));
120 detectZoneBSupport(con);
122 logger.debug("Found zones: {}, features: {}", state.zones, state.features);
126 * Detect if Zone_B is supported (HTR-4069). This will allow Zone_2 to be emulated by the Zone_B feature.
129 * @throws IOException
130 * @throws ReceivedMessageParseException
132 private void detectZoneBSupport(XMLConnection con) throws IOException, ReceivedMessageParseException {
133 if (state.zones.contains(Main_Zone) && !state.zones.contains(Zone_2)) {
134 // Detect if Zone_B is supported (HTR-4069). This will allow Zone_2 to be emulated.
136 // Retrieve Main_Zone basic status, from which we will know this AVR supports Zone_B feature.
137 Node basicStatusNode = getZoneResponse(con, Main_Zone, ZONE_BASIC_STATUS_CMD, ZONE_BASIC_STATUS_PATH);
138 String power = getNodeContentOrEmpty(basicStatusNode, "Power_Control/Zone_B_Power_Info");
139 if (StringUtils.isNotEmpty(power)) {
140 logger.debug("Zone_2 emulation enabled via Zone_B");
141 state.zones.add(Zone_2);
142 state.features.add(Feature.ZONE_B);
147 private boolean isFeatureSupported(Node node, String name) {
148 String value = getNodeContentOrEmpty(node, name);
149 boolean supported = value.equals("1") || value.equals("Available");
153 private <T> void checkFeature(Node node, String name, T value, Set<T> set) {
154 if (isFeatureSupported(node, name)) {