]> git.basschouten.com Git - openhab-addons.git/blob
9251cca84602f3159640974533656853a12a20bf
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
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
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.yamahareceiver.internal.protocol.xml;
14
15 import static org.openhab.binding.yamahareceiver.internal.protocol.xml.XMLConstants.GET_PARAM;
16 import static org.openhab.binding.yamahareceiver.internal.protocol.xml.XMLProtocolService.getResponse;
17 import static org.openhab.binding.yamahareceiver.internal.protocol.xml.XMLUtils.*;
18
19 import java.io.IOException;
20
21 import org.openhab.binding.yamahareceiver.internal.protocol.AbstractConnection;
22 import org.openhab.binding.yamahareceiver.internal.protocol.InputWithPresetControl;
23 import org.openhab.binding.yamahareceiver.internal.protocol.ReceivedMessageParseException;
24 import org.openhab.binding.yamahareceiver.internal.state.DeviceInformationState;
25 import org.openhab.binding.yamahareceiver.internal.state.PlayInfoState;
26 import org.openhab.binding.yamahareceiver.internal.state.PresetInfoState;
27 import org.openhab.binding.yamahareceiver.internal.state.PresetInfoStateListener;
28 import org.slf4j.LoggerFactory;
29 import org.w3c.dom.Node;
30
31 /**
32  * This class implements the Yamaha Receiver protocol related to navigation functionally. USB, NET_RADIO, IPOD and
33  * other inputs are using the same way of playback control.
34  *
35  * The XML nodes <Play_Info> and <Play_Control> are used.
36  *
37  * Example:
38  *
39  * InputWithPlayControl menu = new InputWithPlayControl("NET_RADIO", comObject);
40  * menu.goToPath(menuDir);
41  * menu.selectItem(stationName);
42  *
43  * No state will be saved in here, but in {@link PlayInfoState} and
44  * {@link PresetInfoState} instead.
45  *
46  * @author David Graeff
47  * @author Tomasz Maruszak - Compatibility fixes
48  */
49 public class InputWithPresetControlXML extends AbstractInputControlXML implements InputWithPresetControl {
50
51     private static final String PRESET_LETTERS = "ABCD";
52
53     protected CommandTemplate preset = new CommandTemplate(
54             "<Play_Control><Preset><Preset_Sel>%s</Preset_Sel></Preset></Play_Control>",
55             "Play_Control/Preset/Preset_Sel");
56
57     private final PresetInfoStateListener observer;
58
59     /**
60      * Create an InputWithPlayControl object for altering menu positions and requesting current menu information as well
61      * as controlling the playback and choosing a preset item.
62      *
63      * @param inputID The input ID like USB or NET_RADIO.
64      * @param con The Yamaha communication object to send http requests.
65      */
66     public InputWithPresetControlXML(String inputID, AbstractConnection con, PresetInfoStateListener observer,
67             DeviceInformationState deviceInformationState) {
68         super(LoggerFactory.getLogger(InputWithPresetControlXML.class), inputID, con, deviceInformationState);
69
70         this.observer = observer;
71
72         this.applyModelVariations();
73     }
74
75     /**
76      * Apply command changes to ensure compatibility with all supported models
77      */
78     protected void applyModelVariations() {
79         if (deviceDescriptor == null) {
80             logger.trace("Descriptor not available");
81             return;
82         }
83
84         // add compatibility adjustments here (if any)
85     }
86
87     /**
88      * Updates the preset information
89      *
90      * @throws Exception
91      */
92     @Override
93     public void update() throws IOException, ReceivedMessageParseException {
94         if (observer == null) {
95             return;
96         }
97
98         AbstractConnection con = comReference.get();
99         Node response = getResponse(con,
100                 wrInput("<Play_Control><Preset><Preset_Sel_Item>GetParam</Preset_Sel_Item></Preset></Play_Control>"),
101                 inputElement);
102
103         PresetInfoState msg = new PresetInfoState();
104
105         // Set preset channel names, obtained from this xpath:
106         // NET_RADIO/Play_Control/Preset/Preset_Sel_Item/Item_1/Title
107         Node presetNode = getNode(response, "Play_Control/Preset/Preset_Sel_Item");
108         if (presetNode != null) {
109             for (int i = 1; i <= PRESET_CHANNELS; i++) {
110                 Node itemNode = getNode(presetNode, "Item_" + i);
111                 if (itemNode == null) {
112                     break;
113                 }
114
115                 String title = getNodeContentOrDefault(itemNode, "Title", "Item_" + i);
116                 String value = getNodeContentOrDefault(itemNode, "Param", String.valueOf(i));
117
118                 // For RX-V3900 when a preset slot is not used, this is how it looks
119                 if (title.isEmpty() && "Not Used".equalsIgnoreCase(value)) {
120                     continue;
121                 }
122
123                 int presetChannel = convertToPresetNumber(value);
124                 PresetInfoState.Preset preset = new PresetInfoState.Preset(title, presetChannel);
125                 msg.presetChannelNames.add(preset);
126             }
127         }
128         msg.presetChannelNamesChanged = true;
129
130         String presetValue = getNodeContentOrEmpty(response, preset.getPath());
131
132         // fall back to second method of obtaining current preset (works for Tuner on RX-V3900)
133         if (presetValue.isEmpty()) {
134             try {
135                 Node presetResponse = getResponse(con, wrInput(preset.apply(GET_PARAM)), inputElement);
136                 presetValue = getNodeContentOrEmpty(presetResponse, preset.getPath());
137             } catch (IOException | ReceivedMessageParseException e) {
138                 // this is on purpose, in case the AVR does not support this request and responds with error or nonsense
139             }
140         }
141
142         // For Tuner input on RX-V3900 this is not a number (e.g. "A1" or "B1").
143         msg.presetChannel = convertToPresetNumber(presetValue);
144
145         observer.presetInfoUpdated(msg);
146     }
147
148     private int convertToPresetNumber(String presetValue) {
149         if (!presetValue.isEmpty()) {
150             if (presetValue.chars().allMatch(Character::isDigit)) {
151                 return Integer.parseInt(presetValue);
152             } else {
153                 // special handling for RX-V3900, where 'A1' becomes 101 and 'B2' becomes 202 preset
154                 if (presetValue.length() >= 2) {
155                     Character presetAlpha = presetValue.charAt(0);
156                     if (Character.isLetter(presetAlpha) && Character.isUpperCase(presetAlpha)
157                             && Character.isDigit(presetValue.charAt(1))) {
158                         int presetNumber = Integer.parseInt(presetValue.substring(1));
159                         return (PRESET_LETTERS.indexOf(presetAlpha) + 1) * 100 + presetNumber;
160                     }
161                 }
162             }
163         }
164         return -1;
165     }
166
167     /**
168      * Select a preset channel.
169      *
170      * @param presetChannel The preset position [1,40]
171      * @throws Exception
172      */
173     @Override
174     public void selectItemByPresetNumber(int presetChannel) throws IOException, ReceivedMessageParseException {
175         String presetValue;
176
177         // special handling for RX-V3900, where 'A1' becomes 101 and 'B2' becomes 202 preset
178         if (presetChannel > 100) {
179             int presetNumber = presetChannel % 100;
180             char presetAlpha = PRESET_LETTERS.charAt(presetChannel / 100 - 1);
181             presetValue = Character.toString(presetAlpha) + presetNumber;
182         } else {
183             presetValue = Integer.toString(presetChannel);
184         }
185
186         String cmd = wrInput(preset.apply(presetValue));
187         comReference.get().send(cmd);
188         update();
189     }
190 }