]> git.basschouten.com Git - openhab-addons.git/blob
5a6868f0bf4770c4e663fb62260d709e8b2ebb21
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2022 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.apache.commons.lang3.ArrayUtils;
22 import org.apache.commons.lang3.StringUtils;
23 import org.openhab.binding.yamahareceiver.internal.protocol.AbstractConnection;
24 import org.openhab.binding.yamahareceiver.internal.protocol.InputWithPresetControl;
25 import org.openhab.binding.yamahareceiver.internal.protocol.ReceivedMessageParseException;
26 import org.openhab.binding.yamahareceiver.internal.state.DeviceInformationState;
27 import org.openhab.binding.yamahareceiver.internal.state.PlayInfoState;
28 import org.openhab.binding.yamahareceiver.internal.state.PresetInfoState;
29 import org.openhab.binding.yamahareceiver.internal.state.PresetInfoStateListener;
30 import org.slf4j.LoggerFactory;
31 import org.w3c.dom.Node;
32
33 /**
34  * This class implements the Yamaha Receiver protocol related to navigation functionally. USB, NET_RADIO, IPOD and
35  * other inputs are using the same way of playback control.
36  *
37  * The XML nodes <Play_Info> and <Play_Control> are used.
38  *
39  * Example:
40  *
41  * InputWithPlayControl menu = new InputWithPlayControl("NET_RADIO", comObject);
42  * menu.goToPath(menuDir);
43  * menu.selectItem(stationName);
44  *
45  * No state will be saved in here, but in {@link PlayInfoState} and
46  * {@link PresetInfoState} instead.
47  *
48  * @author David Graeff
49  * @author Tomasz Maruszak - Compatibility fixes
50  */
51 public class InputWithPresetControlXML extends AbstractInputControlXML implements InputWithPresetControl {
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 a 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 (StringUtils.isNumeric(presetValue)) {
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 (ArrayUtils.indexOf(LETTERS, 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 = LETTERS[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
191     private static final Character[] LETTERS = new Character[] { 'A', 'B', 'C', 'D' };
192 }