2 * Copyright (c) 2010-2024 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.openhab.binding.yamahareceiver.internal.YamahaReceiverBindingConstants.Feature;
25 import org.openhab.binding.yamahareceiver.internal.YamahaReceiverBindingConstants.Zone;
26 import org.openhab.binding.yamahareceiver.internal.protocol.AbstractConnection;
27 import org.openhab.binding.yamahareceiver.internal.protocol.DeviceInformation;
28 import org.openhab.binding.yamahareceiver.internal.protocol.ReceivedMessageParseException;
29 import org.openhab.binding.yamahareceiver.internal.state.DeviceInformationState;
30 import org.openhab.binding.yamahareceiver.internal.state.SystemControlState;
31 import org.slf4j.Logger;
32 import org.slf4j.LoggerFactory;
33 import org.w3c.dom.Node;
36 * The system control protocol class is used to control basic non-zone functionality
37 * of a Yamaha receiver with HTTP/xml.
38 * No state will be saved in here, but in {@link SystemControlState} instead.
40 * @author David Graeff - Initial contribution
41 * @author Tomasz Maruszak - DAB support, Spotify support, better feature detection
43 public class DeviceInformationXML implements DeviceInformation {
44 private final Logger logger = LoggerFactory.getLogger(DeviceInformationXML.class);
46 private final WeakReference<AbstractConnection> comReference;
47 protected DeviceInformationState state;
49 public DeviceInformationXML(AbstractConnection com, DeviceInformationState state) {
50 this.comReference = new WeakReference<>(com);
55 * We need that called only once. Will give us name, id, version and zone information.
62 * <Main_Zone>1</Main_Zone>
68 * <HD_Radio>0</HD_Radio>
69 * <Rhapsody>0</Rhapsody>
70 * <Napster>0</Napster>
71 * <SiriusXM>0</SiriusXM>
72 * <Spotify>1</Spotify>
73 * <Pandora>0</Pandora>
75 * <MusicCast_Link>1</MusicCast_Link>
77 * <NET_RADIO>1</NET_RADIO>
78 * <Bluetooth>1</Bluetooth>
80 * <iPod_USB>1</iPod_USB>
81 * <AirPlay>1</AirPlay>
82 * </Feature_Existence>
89 public void update() throws IOException, ReceivedMessageParseException {
90 XMLConnection con = (XMLConnection) comReference.get();
92 Node systemConfigNode = getResponse(con, SYSTEM_STATUS_CONFIG_CMD, SYSTEM_STATUS_CONFIG_PATH);
94 state.host = con.getHost();
95 state.name = getNodeContentOrEmpty(systemConfigNode, "Model_Name");
96 state.id = getNodeContentOrEmpty(systemConfigNode, "System_ID");
97 state.version = getNodeContentOrEmpty(systemConfigNode, "Version");
100 state.features.clear();
101 state.properties.clear();
103 // Get and store the Yamaha Description XML. This will be used to detect proper command naming in other areas.
104 DeviceDescriptorXML descriptor = new DeviceDescriptorXML();
105 descriptor.load(con);
106 descriptor.attach(state);
108 Node featureNode = getNode(systemConfigNode, "Feature_Existence");
109 if (featureNode != null) {
110 for (Zone zone : Zone.values()) {
111 checkFeature(featureNode, zone.toString(), zone, state.zones);
114 XMLConstants.FEATURE_BY_YNC_TAG
115 .forEach((name, feature) -> checkFeature(featureNode, name, feature, state.features));
118 // on older models (RX-V3900) the Feature_Existence element does not exist
120 descriptor.zones.forEach((zone, x) -> state.zones.add(zone));
121 descriptor.features.forEach((feature, x) -> state.features.add(feature));
124 detectZoneBSupport(con);
126 logger.debug("Found zones: {}, features: {}", state.zones, state.features);
130 * Detect if Zone_B is supported (HTR-4069). This will allow Zone_2 to be emulated by the Zone_B feature.
133 * @throws IOException
134 * @throws ReceivedMessageParseException
136 private void detectZoneBSupport(XMLConnection con) throws IOException, ReceivedMessageParseException {
137 if (state.zones.contains(Main_Zone) && !state.zones.contains(Zone_2)) {
138 // Detect if Zone_B is supported (HTR-4069). This will allow Zone_2 to be emulated.
140 // Retrieve Main_Zone basic status, from which we will know this AVR supports Zone_B feature.
141 Node basicStatusNode = getZoneResponse(con, Main_Zone, ZONE_BASIC_STATUS_CMD, ZONE_BASIC_STATUS_PATH);
142 String power = getNodeContentOrEmpty(basicStatusNode, "Power_Control/Zone_B_Power_Info");
143 if (!power.isEmpty()) {
144 logger.debug("Zone_2 emulation enabled via Zone_B");
145 state.zones.add(Zone_2);
146 state.features.add(Feature.ZONE_B);
151 private boolean isFeatureSupported(Node node, String name) {
152 String value = getNodeContentOrEmpty(node, name);
153 return "1".equals(value) || "Available".equals(value);
156 private <T> void checkFeature(Node node, String name, T value, Set<T> set) {
157 if (isFeatureSupported(node, name)) {