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.handler;
15 import static org.openhab.binding.yamahareceiver.internal.YamahaReceiverBindingConstants.*;
16 import static org.openhab.binding.yamahareceiver.internal.YamahaReceiverBindingConstants.Inputs.*;
18 import java.io.IOException;
19 import java.util.Arrays;
20 import java.util.Collection;
21 import java.util.List;
23 import java.util.Map.Entry;
25 import org.eclipse.jdt.annotation.NonNullByDefault;
26 import org.openhab.binding.yamahareceiver.internal.ChannelsTypeProviderAvailableInputs;
27 import org.openhab.binding.yamahareceiver.internal.ChannelsTypeProviderPreset;
28 import org.openhab.binding.yamahareceiver.internal.YamahaReceiverBindingConstants.Feature;
29 import org.openhab.binding.yamahareceiver.internal.YamahaReceiverBindingConstants.Zone;
30 import org.openhab.binding.yamahareceiver.internal.config.YamahaZoneConfig;
31 import org.openhab.binding.yamahareceiver.internal.protocol.AbstractConnection;
32 import org.openhab.binding.yamahareceiver.internal.protocol.IStateUpdatable;
33 import org.openhab.binding.yamahareceiver.internal.protocol.InputWithNavigationControl;
34 import org.openhab.binding.yamahareceiver.internal.protocol.InputWithPlayControl;
35 import org.openhab.binding.yamahareceiver.internal.protocol.InputWithPresetControl;
36 import org.openhab.binding.yamahareceiver.internal.protocol.InputWithTunerBandControl;
37 import org.openhab.binding.yamahareceiver.internal.protocol.ProtocolFactory;
38 import org.openhab.binding.yamahareceiver.internal.protocol.ReceivedMessageParseException;
39 import org.openhab.binding.yamahareceiver.internal.protocol.ZoneAvailableInputs;
40 import org.openhab.binding.yamahareceiver.internal.protocol.ZoneControl;
41 import org.openhab.binding.yamahareceiver.internal.protocol.xml.InputWithNavigationControlXML;
42 import org.openhab.binding.yamahareceiver.internal.protocol.xml.InputWithPlayControlXML;
43 import org.openhab.binding.yamahareceiver.internal.protocol.xml.ZoneControlXML;
44 import org.openhab.binding.yamahareceiver.internal.state.AvailableInputState;
45 import org.openhab.binding.yamahareceiver.internal.state.AvailableInputStateListener;
46 import org.openhab.binding.yamahareceiver.internal.state.DabBandState;
47 import org.openhab.binding.yamahareceiver.internal.state.DabBandStateListener;
48 import org.openhab.binding.yamahareceiver.internal.state.DeviceInformationState;
49 import org.openhab.binding.yamahareceiver.internal.state.NavigationControlState;
50 import org.openhab.binding.yamahareceiver.internal.state.NavigationControlStateListener;
51 import org.openhab.binding.yamahareceiver.internal.state.PlayInfoState;
52 import org.openhab.binding.yamahareceiver.internal.state.PlayInfoStateListener;
53 import org.openhab.binding.yamahareceiver.internal.state.PresetInfoState;
54 import org.openhab.binding.yamahareceiver.internal.state.PresetInfoStateListener;
55 import org.openhab.binding.yamahareceiver.internal.state.ZoneControlState;
56 import org.openhab.binding.yamahareceiver.internal.state.ZoneControlStateListener;
57 import org.openhab.core.config.core.Configuration;
58 import org.openhab.core.library.types.DecimalType;
59 import org.openhab.core.library.types.IncreaseDecreaseType;
60 import org.openhab.core.library.types.NextPreviousType;
61 import org.openhab.core.library.types.OnOffType;
62 import org.openhab.core.library.types.PercentType;
63 import org.openhab.core.library.types.PlayPauseType;
64 import org.openhab.core.library.types.StringType;
65 import org.openhab.core.library.types.UpDownType;
66 import org.openhab.core.thing.Bridge;
67 import org.openhab.core.thing.Channel;
68 import org.openhab.core.thing.ChannelUID;
69 import org.openhab.core.thing.Thing;
70 import org.openhab.core.thing.ThingStatus;
71 import org.openhab.core.thing.ThingStatusDetail;
72 import org.openhab.core.thing.ThingStatusInfo;
73 import org.openhab.core.thing.binding.BaseThingHandler;
74 import org.openhab.core.thing.binding.ThingHandlerService;
75 import org.openhab.core.thing.binding.builder.ChannelBuilder;
76 import org.openhab.core.types.Command;
77 import org.openhab.core.types.RefreshType;
78 import org.slf4j.Logger;
79 import org.slf4j.LoggerFactory;
82 * The {@link YamahaZoneThingHandler} is managing one zone of a Yamaha AVR.
83 * It has a state consisting of the zone, the current input ID, {@link ZoneControlState}
84 * and some more state objects and uses the zone control protocol
85 * class {@link ZoneControlXML}, {@link InputWithPlayControlXML} and {@link InputWithNavigationControlXML}
88 * @author David Graeff - Initial contribution
89 * @author Tomasz Maruszak - [yamaha] Tuner band selection and preset feature for dual band models (RX-S601D), added
92 public class YamahaZoneThingHandler extends BaseThingHandler
93 implements ZoneControlStateListener, NavigationControlStateListener, PlayInfoStateListener,
94 AvailableInputStateListener, PresetInfoStateListener, DabBandStateListener {
96 private final Logger logger = LoggerFactory.getLogger(YamahaZoneThingHandler.class);
98 private YamahaZoneConfig zoneConfig;
100 /// ChannelType providers
101 public @NonNullByDefault({}) ChannelsTypeProviderPreset channelsTypeProviderPreset;
102 public @NonNullByDefault({}) ChannelsTypeProviderAvailableInputs channelsTypeProviderAvailableInputs;
105 protected ZoneControlState zoneState = new ZoneControlState();
106 protected PresetInfoState presetInfoState = new PresetInfoState();
107 protected DabBandState dabBandState = new DabBandState();
108 protected PlayInfoState playInfoState = new PlayInfoState();
109 protected NavigationControlState navigationInfoState = new NavigationControlState();
112 protected ZoneControl zoneControl;
113 protected InputWithPlayControl inputWithPlayControl;
114 protected InputWithNavigationControl inputWithNavigationControl;
115 protected ZoneAvailableInputs zoneAvailableInputs;
116 protected InputWithPresetControl inputWithPresetControl;
117 protected InputWithTunerBandControl inputWithDabBandControl;
119 public YamahaZoneThingHandler(Thing thing) {
124 public Collection<Class<? extends ThingHandlerService>> getServices() {
125 return List.of(ChannelsTypeProviderAvailableInputs.class, ChannelsTypeProviderPreset.class);
129 * Sets the {@link DeviceInformationState} for the handler.
131 public DeviceInformationState getDeviceInformationState() {
132 return getBridgeHandler().getDeviceInformationState();
136 public void handleConfigurationUpdate(Map<String, Object> configurationParameters) {
137 validateConfigurationParameters(configurationParameters);
139 Configuration configuration = editConfiguration();
140 for (Entry<String, Object> configurationParameter : configurationParameters.entrySet()) {
141 configuration.put(configurationParameter.getKey(), configurationParameter.getValue());
144 updateConfiguration(configuration);
146 zoneConfig = configuration.as(YamahaZoneConfig.class);
147 logger.trace("Updating configuration of {} with zone '{}'", getThing().getLabel(), zoneConfig.getZoneValue());
151 * We handle updates of this thing ourself.
154 public void thingUpdated(Thing thing) {
159 * Calls createCommunicationObject if the host name is configured correctly.
162 public void initialize() {
163 // Determine the zone of this thing
165 zoneConfig = getConfigAs(YamahaZoneConfig.class);
166 logger.trace("Initialize {} with zone '{}'", getThing().getLabel(), zoneConfig.getZoneValue());
168 Bridge bridge = getBridge();
169 initializeThing(bridge != null ? bridge.getStatus() : null);
172 protected YamahaBridgeHandler getBridgeHandler() {
173 Bridge bridge = getBridge();
174 if (bridge == null) {
177 return (YamahaBridgeHandler) bridge.getHandler();
180 protected ProtocolFactory getProtocolFactory() {
181 return getBridgeHandler().getProtocolFactory();
184 protected AbstractConnection getConnection() {
185 return getBridgeHandler().getConnection();
189 public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
190 initializeThing(bridgeStatusInfo.getStatus());
193 private void initializeThing(ThingStatus bridgeStatus) {
194 YamahaBridgeHandler bridgeHandler = getBridgeHandler();
195 if (bridgeHandler != null && bridgeStatus != null) {
196 if (bridgeStatus == ThingStatus.ONLINE) {
197 if (zoneConfig == null || zoneConfig.getZone() == null) {
198 String msg = String.format(
199 "Zone not set or invalid zone name used: '%s'. It needs to be on of: '%s'",
200 zoneConfig.getZoneValue(), Arrays.toString(Zone.values()));
201 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, msg);
202 logger.info("{}", msg);
204 if (zoneControl == null) {
205 YamahaBridgeHandler brHandler = getBridgeHandler();
207 zoneControl = getProtocolFactory().ZoneControl(getConnection(), zoneConfig, this,
208 brHandler::getInputConverter, getDeviceInformationState());
209 zoneAvailableInputs = getProtocolFactory().ZoneAvailableInputs(getConnection(), zoneConfig,
210 this, brHandler::getInputConverter, getDeviceInformationState());
212 updateZoneInformation();
215 updateStatus(ThingStatus.ONLINE);
218 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
220 zoneAvailableInputs = null;
223 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_UNINITIALIZED);
228 * Return true if the zone is set, and zoneControl and zoneAvailableInputs objects have been created.
230 boolean isCorrectlyInitialized() {
231 return zoneConfig != null && zoneConfig.getZone() != null && zoneAvailableInputs != null && zoneControl != null;
235 * Request new zone and available input information
237 void updateZoneInformation() {
238 updateAsyncMakeOfflineIfFail(zoneAvailableInputs);
239 updateAsyncMakeOfflineIfFail(zoneControl);
241 if (inputWithPlayControl != null) {
242 updateAsyncMakeOfflineIfFail(inputWithPlayControl);
245 if (inputWithNavigationControl != null) {
246 updateAsyncMakeOfflineIfFail(inputWithNavigationControl);
249 if (inputWithPresetControl != null) {
250 updateAsyncMakeOfflineIfFail(inputWithPresetControl);
253 if (inputWithDabBandControl != null) {
254 updateAsyncMakeOfflineIfFail(inputWithDabBandControl);
259 public void handleCommand(ChannelUID channelUID, Command command) {
260 if (zoneControl == null) {
264 String id = channelUID.getIdWithoutGroup();
267 if (command instanceof RefreshType) {
268 refreshFromState(channelUID);
274 zoneControl.setPower(((OnOffType) command) == OnOffType.ON);
277 zoneControl.setInput(((StringType) command).toString());
279 case CHANNEL_SURROUND:
280 zoneControl.setSurroundProgram(((StringType) command).toString());
282 case CHANNEL_VOLUME_DB:
283 zoneControl.setVolumeDB(((DecimalType) command).floatValue());
286 if (command instanceof DecimalType decimalCommand) {
287 zoneControl.setVolume(decimalCommand.floatValue());
288 } else if (command instanceof IncreaseDecreaseType increaseDecreaseCommand) {
289 zoneControl.setVolumeRelative(zoneState,
290 (increaseDecreaseCommand == IncreaseDecreaseType.INCREASE ? 1 : -1)
291 * zoneConfig.getVolumeRelativeChangeFactor());
295 zoneControl.setMute(((OnOffType) command) == OnOffType.ON);
298 zoneControl.setScene(((StringType) command).toString());
300 case CHANNEL_DIALOGUE_LEVEL:
301 zoneControl.setDialogueLevel(((DecimalType) command).intValue());
304 case CHANNEL_HDMI1OUT:
305 zoneControl.setHDMI1Out(((OnOffType) command) == OnOffType.ON);
308 case CHANNEL_HDMI2OUT:
309 zoneControl.setHDMI2Out(((OnOffType) command) == OnOffType.ON);
312 case CHANNEL_NAVIGATION_MENU:
313 if (inputWithNavigationControl == null) {
314 logger.warn("Channel {} not working with {} input!", id, zoneState.inputID);
318 String path = ((StringType) command).toFullString();
319 inputWithNavigationControl.selectItemFullPath(path);
322 case CHANNEL_NAVIGATION_UPDOWN:
323 if (inputWithNavigationControl == null) {
324 logger.warn("Channel {} not working with {} input!", id, zoneState.inputID);
327 if (((UpDownType) command) == UpDownType.UP) {
328 inputWithNavigationControl.goUp();
330 inputWithNavigationControl.goDown();
334 case CHANNEL_NAVIGATION_LEFTRIGHT:
335 if (inputWithNavigationControl == null) {
336 logger.warn("Channel {} not working with {} input!", id, zoneState.inputID);
339 if (((UpDownType) command) == UpDownType.UP) {
340 inputWithNavigationControl.goLeft();
342 inputWithNavigationControl.goRight();
346 case CHANNEL_NAVIGATION_SELECT:
347 if (inputWithNavigationControl == null) {
348 logger.warn("Channel {} not working with {} input!", id, zoneState.inputID);
351 inputWithNavigationControl.selectCurrentItem();
354 case CHANNEL_NAVIGATION_BACK:
355 if (inputWithNavigationControl == null) {
356 logger.warn("Channel {} not working with {} input!", id, zoneState.inputID);
359 inputWithNavigationControl.goBack();
362 case CHANNEL_NAVIGATION_BACKTOROOT:
363 if (inputWithNavigationControl == null) {
364 logger.warn("Channel {} not working with {} input!", id, zoneState.inputID);
367 inputWithNavigationControl.goToRoot();
370 case CHANNEL_PLAYBACK_PRESET:
371 if (inputWithPresetControl == null) {
372 logger.warn("Channel {} not working with {} input!", id, zoneState.inputID);
376 if (command instanceof DecimalType decimalCommand) {
377 inputWithPresetControl.selectItemByPresetNumber(decimalCommand.intValue());
378 } else if (command instanceof StringType stringCommand) {
380 int v = Integer.valueOf(stringCommand.toString());
381 inputWithPresetControl.selectItemByPresetNumber(v);
382 } catch (NumberFormatException e) {
383 logger.warn("Provide a number for {}", id);
388 case CHANNEL_TUNER_BAND:
389 if (inputWithDabBandControl == null) {
390 logger.warn("Channel {} not working with {} input!", id, zoneState.inputID);
394 if (command instanceof StringType) {
395 inputWithDabBandControl.selectBandByName(command.toString());
397 logger.warn("Provide a string for {}", id);
401 case CHANNEL_PLAYBACK:
402 if (inputWithPlayControl == null) {
403 logger.warn("Channel {} not working with {} input!", id, zoneState.inputID);
407 if (command instanceof PlayPauseType t) {
410 inputWithPlayControl.pause();
413 inputWithPlayControl.play();
416 } else if (command instanceof NextPreviousType t) {
419 inputWithPlayControl.nextTrack();
422 inputWithPlayControl.previousTrack();
425 } else if (command instanceof DecimalType decimalCommand) {
426 int v = decimalCommand.intValue();
428 inputWithPlayControl.skipREV();
430 inputWithPlayControl.skipFF();
432 } else if (command instanceof StringType stringCommand) {
433 String v = stringCommand.toFullString();
436 inputWithPlayControl.play();
439 inputWithPlayControl.pause();
442 inputWithPlayControl.stop();
445 inputWithPlayControl.skipREV();
448 inputWithPlayControl.skipFF();
451 inputWithPlayControl.nextTrack();
454 inputWithPlayControl.previousTrack();
460 logger.warn("Channel {} not supported!", id);
462 } catch (IOException e) {
463 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
464 } catch (ReceivedMessageParseException e) {
465 // Some AVRs send unexpected responses. We log parser exceptions therefore.
466 logger.debug("Parse error!", e);
471 * Called by handleCommand() if a RefreshType command was received. It will update
472 * the given channel with the correct state.
474 * @param channelUID The channel
476 private void refreshFromState(ChannelUID channelUID) {
477 String id = channelUID.getId();
479 if (id.equals(grpZone(CHANNEL_POWER))) {
480 updateState(channelUID, OnOffType.from(zoneState.power));
482 } else if (id.equals(grpZone(CHANNEL_VOLUME_DB))) {
483 updateState(channelUID, new DecimalType(zoneState.volumeDB));
484 } else if (id.equals(grpZone(CHANNEL_VOLUME))) {
485 updateState(channelUID, new PercentType((int) zoneConfig.getVolumePercentage(zoneState.volumeDB)));
486 } else if (id.equals(grpZone(CHANNEL_MUTE))) {
487 updateState(channelUID, OnOffType.from(zoneState.mute));
488 } else if (id.equals(grpZone(CHANNEL_INPUT))) {
489 updateState(channelUID, new StringType(zoneState.inputID));
490 } else if (id.equals(grpZone(CHANNEL_SURROUND))) {
491 updateState(channelUID, new StringType(zoneState.surroundProgram));
492 } else if (id.equals(grpZone(CHANNEL_SCENE))) {
493 logger.debug("No state updates available");
494 } else if (id.equals(grpZone(CHANNEL_DIALOGUE_LEVEL))) {
495 updateState(channelUID, new DecimalType(zoneState.dialogueLevel));
496 } else if (id.equals(grpZone(CHANNEL_HDMI1OUT))) {
497 updateState(channelUID, OnOffType.from(zoneState.hdmi1Out));
498 } else if (id.equals(grpZone(CHANNEL_HDMI2OUT))) {
499 updateState(channelUID, OnOffType.from(zoneState.hdmi2Out));
501 } else if (id.equals(grpPlayback(CHANNEL_PLAYBACK))) {
502 updateState(channelUID, new StringType(playInfoState.playbackMode));
503 } else if (id.equals(grpPlayback(CHANNEL_PLAYBACK_STATION))) {
504 updateState(channelUID, new StringType(playInfoState.station));
505 } else if (id.equals(grpPlayback(CHANNEL_PLAYBACK_ARTIST))) {
506 updateState(channelUID, new StringType(playInfoState.artist));
507 } else if (id.equals(grpPlayback(CHANNEL_PLAYBACK_ALBUM))) {
508 updateState(channelUID, new StringType(playInfoState.album));
509 } else if (id.equals(grpPlayback(CHANNEL_PLAYBACK_SONG))) {
510 updateState(channelUID, new StringType(playInfoState.song));
511 } else if (id.equals(grpPlayback(CHANNEL_PLAYBACK_SONG_IMAGE_URL))) {
512 updateState(channelUID, new StringType(playInfoState.songImageUrl));
513 } else if (id.equals(grpPlayback(CHANNEL_PLAYBACK_PRESET))) {
514 updateState(channelUID, new DecimalType(presetInfoState.presetChannel));
515 } else if (id.equals(grpPlayback(CHANNEL_TUNER_BAND))) {
516 updateState(channelUID, new StringType(dabBandState.band));
518 } else if (id.equals(grpNav(CHANNEL_NAVIGATION_MENU))) {
519 updateState(channelUID, new StringType(navigationInfoState.getCurrentItemName()));
520 } else if (id.equals(grpNav(CHANNEL_NAVIGATION_LEVEL))) {
521 updateState(channelUID, new DecimalType(navigationInfoState.menuLayer));
522 } else if (id.equals(grpNav(CHANNEL_NAVIGATION_CURRENT_ITEM))) {
523 updateState(channelUID, new DecimalType(navigationInfoState.currentLine));
524 } else if (id.equals(grpNav(CHANNEL_NAVIGATION_TOTAL_ITEMS))) {
525 updateState(channelUID, new DecimalType(navigationInfoState.maxLine));
527 logger.warn("Channel {} not implemented!", id);
532 public void zoneStateChanged(ZoneControlState msg) {
533 boolean inputChanged = !msg.inputID.equals(zoneState.inputID);
536 updateStatus(ThingStatus.ONLINE);
538 updateState(grpZone(CHANNEL_POWER), OnOffType.from(zoneState.power));
539 updateState(grpZone(CHANNEL_INPUT), new StringType(zoneState.inputID));
540 updateState(grpZone(CHANNEL_SURROUND), new StringType(zoneState.surroundProgram));
541 updateState(grpZone(CHANNEL_VOLUME_DB), new DecimalType(zoneState.volumeDB));
542 updateState(grpZone(CHANNEL_VOLUME), new PercentType((int) zoneConfig.getVolumePercentage(zoneState.volumeDB)));
543 updateState(grpZone(CHANNEL_MUTE), OnOffType.from(zoneState.mute));
544 updateState(grpZone(CHANNEL_DIALOGUE_LEVEL), new DecimalType(zoneState.dialogueLevel));
545 updateState(grpZone(CHANNEL_HDMI1OUT), OnOffType.from(zoneState.hdmi1Out));
546 updateState(grpZone(CHANNEL_HDMI2OUT), OnOffType.from(zoneState.hdmi2Out));
548 // If the input changed
555 * Called by {@link #zoneStateChanged(ZoneControlState)} if the input has changed.
556 * Will request updates from {@see InputWithNavigationControl} and {@see InputWithPlayControl}.
558 private void inputChanged() {
559 logger.debug("Input changed to {}", zoneState.inputID);
561 if (!isInputSupported(zoneState.inputID)) {
562 // for now just emit a warning in logs
563 logger.warn("Input {} is not supported on your AVR model", zoneState.inputID);
566 inputChangedCheckForNavigationControl();
567 // Note: the DAB band needs to be initialized before preset and playback
568 inputChangedCheckForDabBand();
569 inputChangedCheckForPlaybackControl();
570 inputChangedCheckForPresetControl();
574 * Checks if the specified input is supported given the detected device feature information.
576 * @param inputID - the input name
577 * @return true when input is supported
579 private boolean isInputSupported(String inputID) {
582 return getDeviceInformationState().features.contains(Feature.SPOTIFY);
585 return getDeviceInformationState().features.contains(Feature.TUNER)
586 || getDeviceInformationState().features.contains(Feature.DAB);
588 // Note: add more inputs here in the future
593 private void inputChangedCheckForNavigationControl() {
594 boolean includeInputWithNavigationControl = false;
596 for (String channelName : CHANNELS_NAVIGATION) {
597 if (isLinked(grpNav(channelName))) {
598 includeInputWithNavigationControl = true;
603 if (includeInputWithNavigationControl) {
604 includeInputWithNavigationControl = InputWithNavigationControl.SUPPORTED_INPUTS.contains(zoneState.inputID);
605 if (!includeInputWithNavigationControl) {
606 logger.debug("Navigation control not supported by {}", zoneState.inputID);
610 logger.trace("Navigation control requested by channel");
612 if (!includeInputWithNavigationControl) {
613 inputWithNavigationControl = null;
614 navigationInfoState.invalidate();
615 navigationUpdated(navigationInfoState);
619 inputWithNavigationControl = getProtocolFactory().InputWithNavigationControl(getConnection(),
620 navigationInfoState, zoneState.inputID, this, getDeviceInformationState());
622 updateAsyncMakeOfflineIfFail(inputWithNavigationControl);
625 private void inputChangedCheckForPlaybackControl() {
626 boolean includeInputWithPlaybackControl = false;
628 for (String channelName : CHANNELS_PLAYBACK) {
629 if (isLinked(grpPlayback(channelName))) {
630 includeInputWithPlaybackControl = true;
635 logger.trace("Playback control requested by channel");
637 if (includeInputWithPlaybackControl) {
638 includeInputWithPlaybackControl = InputWithPlayControl.SUPPORTED_INPUTS.contains(zoneState.inputID);
639 if (!includeInputWithPlaybackControl) {
640 logger.debug("Playback control not supported by {}", zoneState.inputID);
644 if (!includeInputWithPlaybackControl) {
645 inputWithPlayControl = null;
646 playInfoState.invalidate();
647 playInfoUpdated(playInfoState);
652 * The {@link inputChangedCheckForDabBand} needs to be called first before this method, in case the AVR Supports
655 if (inputWithDabBandControl != null) {
656 // When input is Tuner DAB there is no playback control
657 inputWithPlayControl = null;
659 inputWithPlayControl = getProtocolFactory().InputWithPlayControl(getConnection(), zoneState.inputID, this,
660 getBridgeHandler().getConfiguration(), getDeviceInformationState());
662 updateAsyncMakeOfflineIfFail(inputWithPlayControl);
666 private void inputChangedCheckForPresetControl() {
667 boolean includeInput = isLinked(grpPlayback(CHANNEL_PLAYBACK_PRESET));
669 logger.trace("Preset control requested by channel");
672 includeInput = InputWithPresetControl.SUPPORTED_INPUTS.contains(zoneState.inputID);
674 logger.debug("Preset control not supported by {}", zoneState.inputID);
679 inputWithPresetControl = null;
680 presetInfoState.invalidate();
681 presetInfoUpdated(presetInfoState);
686 * The {@link inputChangedCheckForDabBand} needs to be called first before this method, in case the AVR Supports
689 if (inputWithDabBandControl != null) {
690 // When the input is Tuner DAB the control also provides preset functionality
691 inputWithPresetControl = (InputWithPresetControl) inputWithDabBandControl;
692 // Note: No need to update state - it will be already called for DabBand control (see
693 // inputChangedCheckForDabBand)
695 inputWithPresetControl = getProtocolFactory().InputWithPresetControl(getConnection(), zoneState.inputID,
696 this, getDeviceInformationState());
698 updateAsyncMakeOfflineIfFail(inputWithPresetControl);
702 private void inputChangedCheckForDabBand() {
703 boolean includeInput = isLinked(grpPlayback(CHANNEL_TUNER_BAND));
705 logger.trace("Band control requested by channel");
708 // Check if TUNER input is DAB - dual bands radio tuner
709 includeInput = InputWithTunerBandControl.SUPPORTED_INPUTS.contains(zoneState.inputID)
710 && getDeviceInformationState().features.contains(Feature.DAB);
712 logger.debug("Band control not supported by {}", zoneState.inputID);
717 inputWithDabBandControl = null;
718 dabBandState.invalidate();
719 dabBandUpdated(dabBandState);
723 logger.debug("InputWithTunerBandControl created for {}", zoneState.inputID);
724 inputWithDabBandControl = getProtocolFactory().InputWithDabBandControl(zoneState.inputID, getConnection(), this,
725 this, this, getDeviceInformationState());
727 updateAsyncMakeOfflineIfFail(inputWithDabBandControl);
730 protected void updateAsyncMakeOfflineIfFail(IStateUpdatable stateUpdatable) {
731 scheduler.submit(() -> {
733 stateUpdatable.update();
734 } catch (IOException e) {
735 logger.debug("State update error. Changing thing to offline", e);
736 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
737 } catch (ReceivedMessageParseException e) {
738 String message = e.getMessage();
739 updateProperty(PROPERTY_LAST_PARSE_ERROR, message != null ? message : "");
740 // Some AVRs send unexpected responses. We log parser exceptions therefore.
741 logger.debug("Parse error!", e);
747 * Once this thing is set up and the AVR is connected, the available inputs for this zone are requested.
748 * The thing is updated with a new CHANNEL_AVAILABLE_INPUT which lists the available inputs for the current zone..
751 public void availableInputsChanged(AvailableInputState msg) {
752 // Update channel type provider with a list of available inputs
753 channelsTypeProviderAvailableInputs.changeAvailableInputs(msg.availableInputs);
755 // Remove the old channel and add the new channel. The channel will be requested from the
756 // yamahaChannelTypeProvider.
757 ChannelUID inputChannelUID = new ChannelUID(thing.getUID(), CHANNEL_GROUP_ZONE, CHANNEL_INPUT);
758 Channel channel = ChannelBuilder.create(inputChannelUID, "String")
759 .withType(channelsTypeProviderAvailableInputs.getChannelTypeUID()).build();
760 updateThing(editThing().withoutChannel(inputChannelUID).withChannel(channel).build());
763 private String grpPlayback(String channelIDWithoutGroup) {
764 return new ChannelUID(thing.getUID(), CHANNEL_GROUP_PLAYBACK, channelIDWithoutGroup).getId();
767 private String grpNav(String channelIDWithoutGroup) {
768 return new ChannelUID(thing.getUID(), CHANNEL_GROUP_NAVIGATION, channelIDWithoutGroup).getId();
771 private String grpZone(String channelIDWithoutGroup) {
772 return new ChannelUID(thing.getUID(), CHANNEL_GROUP_ZONE, channelIDWithoutGroup).getId();
776 public void playInfoUpdated(PlayInfoState msg) {
779 updateState(grpPlayback(CHANNEL_PLAYBACK), new StringType(msg.playbackMode));
780 updateState(grpPlayback(CHANNEL_PLAYBACK_STATION), new StringType(msg.station));
781 updateState(grpPlayback(CHANNEL_PLAYBACK_ARTIST), new StringType(msg.artist));
782 updateState(grpPlayback(CHANNEL_PLAYBACK_ALBUM), new StringType(msg.album));
783 updateState(grpPlayback(CHANNEL_PLAYBACK_SONG), new StringType(msg.song));
784 updateState(grpPlayback(CHANNEL_PLAYBACK_SONG_IMAGE_URL), new StringType(msg.songImageUrl));
788 public void presetInfoUpdated(PresetInfoState msg) {
789 presetInfoState = msg;
791 if (msg.presetChannelNamesChanged) {
792 msg.presetChannelNamesChanged = false;
794 channelsTypeProviderPreset.changePresetNames(msg.presetChannelNames);
796 // Remove the old channel and add the new channel. The channel will be requested from the
797 // channelsTypeProviderPreset.
798 ChannelUID inputChannelUID = new ChannelUID(thing.getUID(), CHANNEL_GROUP_PLAYBACK,
799 CHANNEL_PLAYBACK_PRESET);
800 Channel channel = ChannelBuilder.create(inputChannelUID, "Number")
801 .withType(channelsTypeProviderPreset.getChannelTypeUID()).build();
802 updateThing(editThing().withoutChannel(inputChannelUID).withChannel(channel).build());
805 updateState(grpPlayback(CHANNEL_PLAYBACK_PRESET), new DecimalType(msg.presetChannel));
809 public void dabBandUpdated(DabBandState msg) {
811 updateState(grpPlayback(CHANNEL_TUNER_BAND), new StringType(msg.band));
815 public void navigationUpdated(NavigationControlState msg) {
816 navigationInfoState = msg;
817 updateState(grpNav(CHANNEL_NAVIGATION_MENU), new StringType(msg.menuName));
818 updateState(grpNav(CHANNEL_NAVIGATION_LEVEL), new DecimalType(msg.menuLayer));
819 updateState(grpNav(CHANNEL_NAVIGATION_CURRENT_ITEM), new DecimalType(msg.currentLine));
820 updateState(grpNav(CHANNEL_NAVIGATION_TOTAL_ITEMS), new DecimalType(msg.maxLine));
824 public void navigationError(String msg) {
825 updateProperty(PROPERTY_MENU_ERROR, msg);
826 logger.warn("Navigation error: {}", msg);