]> git.basschouten.com Git - openhab-addons.git/blob
5bb611cede2177b6bbf1f30a57b1d43e90be0812
[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 {@code <Play_Info>} and {@code <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 - Initial contribution
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 IOException
91      * @throws ReceivedMessageParseException
92      */
93     @Override
94     public void update() throws IOException, ReceivedMessageParseException {
95         if (observer == null) {
96             return;
97         }
98
99         AbstractConnection con = comReference.get();
100         Node response = getResponse(con,
101                 wrInput("<Play_Control><Preset><Preset_Sel_Item>GetParam</Preset_Sel_Item></Preset></Play_Control>"),
102                 inputElement);
103
104         PresetInfoState msg = new PresetInfoState();
105
106         // Set preset channel names, obtained from this xpath:
107         // NET_RADIO/Play_Control/Preset/Preset_Sel_Item/Item_1/Title
108         Node presetNode = getNode(response, "Play_Control/Preset/Preset_Sel_Item");
109         if (presetNode != null) {
110             for (int i = 1; i <= PRESET_CHANNELS; i++) {
111                 Node itemNode = getNode(presetNode, "Item_" + i);
112                 if (itemNode == null) {
113                     break;
114                 }
115
116                 String title = getNodeContentOrDefault(itemNode, "Title", "Item_" + i);
117                 String value = getNodeContentOrDefault(itemNode, "Param", String.valueOf(i));
118
119                 // For RX-V3900 when a preset slot is not used, this is how it looks
120                 if (title.isEmpty() && "Not Used".equalsIgnoreCase(value)) {
121                     continue;
122                 }
123
124                 int presetChannel = convertToPresetNumber(value);
125                 PresetInfoState.Preset preset = new PresetInfoState.Preset(title, presetChannel);
126                 msg.presetChannelNames.add(preset);
127             }
128         }
129         msg.presetChannelNamesChanged = true;
130
131         String presetValue = getNodeContentOrEmpty(response, preset.getPath());
132
133         // fall back to second method of obtaining current preset (works for Tuner on RX-V3900)
134         if (presetValue.isEmpty()) {
135             try {
136                 Node presetResponse = getResponse(con, wrInput(preset.apply(GET_PARAM)), inputElement);
137                 presetValue = getNodeContentOrEmpty(presetResponse, preset.getPath());
138             } catch (IOException | ReceivedMessageParseException e) {
139                 // this is on purpose, in case the AVR does not support this request and responds with error or nonsense
140             }
141         }
142
143         // For Tuner input on RX-V3900 this is not a number (e.g. "A1" or "B1").
144         msg.presetChannel = convertToPresetNumber(presetValue);
145
146         observer.presetInfoUpdated(msg);
147     }
148
149     private int convertToPresetNumber(String presetValue) {
150         if (!presetValue.isEmpty()) {
151             if (presetValue.chars().allMatch(Character::isDigit)) {
152                 return Integer.parseInt(presetValue);
153             } else {
154                 // special handling for RX-V3900, where 'A1' becomes 101 and 'B2' becomes 202 preset
155                 if (presetValue.length() >= 2) {
156                     Character presetAlpha = presetValue.charAt(0);
157                     if (Character.isLetter(presetAlpha) && Character.isUpperCase(presetAlpha)
158                             && Character.isDigit(presetValue.charAt(1))) {
159                         int presetNumber = Integer.parseInt(presetValue.substring(1));
160                         return (PRESET_LETTERS.indexOf(presetAlpha) + 1) * 100 + presetNumber;
161                     }
162                 }
163             }
164         }
165         return -1;
166     }
167
168     /**
169      * Select a preset channel.
170      *
171      * @param presetChannel The preset position [1,40]
172      * @throws IOException
173      * @throws ReceivedMessageParseException
174      */
175     @Override
176     public void selectItemByPresetNumber(int presetChannel) throws IOException, ReceivedMessageParseException {
177         String presetValue;
178
179         // special handling for RX-V3900, where 'A1' becomes 101 and 'B2' becomes 202 preset
180         if (presetChannel > 100) {
181             int presetNumber = presetChannel % 100;
182             char presetAlpha = PRESET_LETTERS.charAt(presetChannel / 100 - 1);
183             presetValue = Character.toString(presetAlpha) + presetNumber;
184         } else {
185             presetValue = Integer.toString(presetChannel);
186         }
187
188         String cmd = wrInput(preset.apply(presetValue));
189         comReference.get().send(cmd);
190         update();
191     }
192 }