2 * Copyright (c) 2010-2023 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.Collections;
23 import java.util.Map.Entry;
24 import java.util.stream.Collectors;
25 import java.util.stream.Stream;
27 import org.eclipse.jdt.annotation.NonNullByDefault;
28 import org.openhab.binding.yamahareceiver.internal.ChannelsTypeProviderAvailableInputs;
29 import org.openhab.binding.yamahareceiver.internal.ChannelsTypeProviderPreset;
30 import org.openhab.binding.yamahareceiver.internal.YamahaReceiverBindingConstants.Feature;
31 import org.openhab.binding.yamahareceiver.internal.YamahaReceiverBindingConstants.Zone;
32 import org.openhab.binding.yamahareceiver.internal.config.YamahaZoneConfig;
33 import org.openhab.binding.yamahareceiver.internal.protocol.AbstractConnection;
34 import org.openhab.binding.yamahareceiver.internal.protocol.IStateUpdatable;
35 import org.openhab.binding.yamahareceiver.internal.protocol.InputWithNavigationControl;
36 import org.openhab.binding.yamahareceiver.internal.protocol.InputWithPlayControl;
37 import org.openhab.binding.yamahareceiver.internal.protocol.InputWithPresetControl;
38 import org.openhab.binding.yamahareceiver.internal.protocol.InputWithTunerBandControl;
39 import org.openhab.binding.yamahareceiver.internal.protocol.ProtocolFactory;
40 import org.openhab.binding.yamahareceiver.internal.protocol.ReceivedMessageParseException;
41 import org.openhab.binding.yamahareceiver.internal.protocol.ZoneAvailableInputs;
42 import org.openhab.binding.yamahareceiver.internal.protocol.ZoneControl;
43 import org.openhab.binding.yamahareceiver.internal.protocol.xml.InputWithNavigationControlXML;
44 import org.openhab.binding.yamahareceiver.internal.protocol.xml.InputWithPlayControlXML;
45 import org.openhab.binding.yamahareceiver.internal.protocol.xml.ZoneControlXML;
46 import org.openhab.binding.yamahareceiver.internal.state.AvailableInputState;
47 import org.openhab.binding.yamahareceiver.internal.state.AvailableInputStateListener;
48 import org.openhab.binding.yamahareceiver.internal.state.DabBandState;
49 import org.openhab.binding.yamahareceiver.internal.state.DabBandStateListener;
50 import org.openhab.binding.yamahareceiver.internal.state.DeviceInformationState;
51 import org.openhab.binding.yamahareceiver.internal.state.NavigationControlState;
52 import org.openhab.binding.yamahareceiver.internal.state.NavigationControlStateListener;
53 import org.openhab.binding.yamahareceiver.internal.state.PlayInfoState;
54 import org.openhab.binding.yamahareceiver.internal.state.PlayInfoStateListener;
55 import org.openhab.binding.yamahareceiver.internal.state.PresetInfoState;
56 import org.openhab.binding.yamahareceiver.internal.state.PresetInfoStateListener;
57 import org.openhab.binding.yamahareceiver.internal.state.ZoneControlState;
58 import org.openhab.binding.yamahareceiver.internal.state.ZoneControlStateListener;
59 import org.openhab.core.config.core.Configuration;
60 import org.openhab.core.library.types.DecimalType;
61 import org.openhab.core.library.types.IncreaseDecreaseType;
62 import org.openhab.core.library.types.NextPreviousType;
63 import org.openhab.core.library.types.OnOffType;
64 import org.openhab.core.library.types.PercentType;
65 import org.openhab.core.library.types.PlayPauseType;
66 import org.openhab.core.library.types.StringType;
67 import org.openhab.core.library.types.UpDownType;
68 import org.openhab.core.thing.Bridge;
69 import org.openhab.core.thing.Channel;
70 import org.openhab.core.thing.ChannelUID;
71 import org.openhab.core.thing.Thing;
72 import org.openhab.core.thing.ThingStatus;
73 import org.openhab.core.thing.ThingStatusDetail;
74 import org.openhab.core.thing.ThingStatusInfo;
75 import org.openhab.core.thing.binding.BaseThingHandler;
76 import org.openhab.core.thing.binding.ThingHandlerService;
77 import org.openhab.core.thing.binding.builder.ChannelBuilder;
78 import org.openhab.core.types.Command;
79 import org.openhab.core.types.RefreshType;
80 import org.slf4j.Logger;
81 import org.slf4j.LoggerFactory;
84 * The {@link YamahaZoneThingHandler} is managing one zone of a Yamaha AVR.
85 * It has a state consisting of the zone, the current input ID, {@link ZoneControlState}
86 * and some more state objects and uses the zone control protocol
87 * class {@link ZoneControlXML}, {@link InputWithPlayControlXML} and {@link InputWithNavigationControlXML}
90 * @author David Graeff <david.graeff@web.de>
91 * @author Tomasz Maruszak - [yamaha] Tuner band selection and preset feature for dual band models (RX-S601D), added
94 public class YamahaZoneThingHandler extends BaseThingHandler
95 implements ZoneControlStateListener, NavigationControlStateListener, PlayInfoStateListener,
96 AvailableInputStateListener, PresetInfoStateListener, DabBandStateListener {
98 private final Logger logger = LoggerFactory.getLogger(YamahaZoneThingHandler.class);
100 private YamahaZoneConfig zoneConfig;
102 /// ChannelType providers
103 public @NonNullByDefault({}) ChannelsTypeProviderPreset channelsTypeProviderPreset;
104 public @NonNullByDefault({}) ChannelsTypeProviderAvailableInputs channelsTypeProviderAvailableInputs;
107 protected ZoneControlState zoneState = new ZoneControlState();
108 protected PresetInfoState presetInfoState = new PresetInfoState();
109 protected DabBandState dabBandState = new DabBandState();
110 protected PlayInfoState playInfoState = new PlayInfoState();
111 protected NavigationControlState navigationInfoState = new NavigationControlState();
114 protected ZoneControl zoneControl;
115 protected InputWithPlayControl inputWithPlayControl;
116 protected InputWithNavigationControl inputWithNavigationControl;
117 protected ZoneAvailableInputs zoneAvailableInputs;
118 protected InputWithPresetControl inputWithPresetControl;
119 protected InputWithTunerBandControl inputWithDabBandControl;
121 public YamahaZoneThingHandler(Thing thing) {
126 public Collection<Class<? extends ThingHandlerService>> getServices() {
128 .unmodifiableList(Stream.of(ChannelsTypeProviderAvailableInputs.class, ChannelsTypeProviderPreset.class)
129 .collect(Collectors.toList()));
133 * Sets the {@link DeviceInformationState} for the handler.
135 public DeviceInformationState getDeviceInformationState() {
136 return getBridgeHandler().getDeviceInformationState();
140 public void handleConfigurationUpdate(Map<String, Object> configurationParameters) {
141 validateConfigurationParameters(configurationParameters);
143 Configuration configuration = editConfiguration();
144 for (Entry<String, Object> configurationParameter : configurationParameters.entrySet()) {
145 configuration.put(configurationParameter.getKey(), configurationParameter.getValue());
148 updateConfiguration(configuration);
150 zoneConfig = configuration.as(YamahaZoneConfig.class);
151 logger.trace("Updating configuration of {} with zone '{}'", getThing().getLabel(), zoneConfig.getZoneValue());
155 * We handle updates of this thing ourself.
158 public void thingUpdated(Thing thing) {
163 * Calls createCommunicationObject if the host name is configured correctly.
166 public void initialize() {
167 // Determine the zone of this thing
169 zoneConfig = getConfigAs(YamahaZoneConfig.class);
170 logger.trace("Initialize {} with zone '{}'", getThing().getLabel(), zoneConfig.getZoneValue());
172 Bridge bridge = getBridge();
173 initializeThing(bridge != null ? bridge.getStatus() : null);
176 protected YamahaBridgeHandler getBridgeHandler() {
177 Bridge bridge = getBridge();
178 if (bridge == null) {
181 return (YamahaBridgeHandler) bridge.getHandler();
184 protected ProtocolFactory getProtocolFactory() {
185 return getBridgeHandler().getProtocolFactory();
188 protected AbstractConnection getConnection() {
189 return getBridgeHandler().getConnection();
193 public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
194 initializeThing(bridgeStatusInfo.getStatus());
197 private void initializeThing(ThingStatus bridgeStatus) {
198 YamahaBridgeHandler bridgeHandler = getBridgeHandler();
199 if (bridgeHandler != null && bridgeStatus != null) {
200 if (bridgeStatus == ThingStatus.ONLINE) {
201 if (zoneConfig == null || zoneConfig.getZone() == null) {
202 String msg = String.format(
203 "Zone not set or invalid zone name used: '%s'. It needs to be on of: '%s'",
204 zoneConfig.getZoneValue(), Arrays.toString(Zone.values()));
205 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, msg);
206 logger.info("{}", msg);
208 if (zoneControl == null) {
209 YamahaBridgeHandler brHandler = getBridgeHandler();
211 zoneControl = getProtocolFactory().ZoneControl(getConnection(), zoneConfig, this,
212 brHandler::getInputConverter, getDeviceInformationState());
213 zoneAvailableInputs = getProtocolFactory().ZoneAvailableInputs(getConnection(), zoneConfig,
214 this, brHandler::getInputConverter, getDeviceInformationState());
216 updateZoneInformation();
219 updateStatus(ThingStatus.ONLINE);
222 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
224 zoneAvailableInputs = null;
227 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_UNINITIALIZED);
232 * Return true if the zone is set, and zoneControl and zoneAvailableInputs objects have been created.
234 boolean isCorrectlyInitialized() {
235 return zoneConfig != null && zoneConfig.getZone() != null && zoneAvailableInputs != null && zoneControl != null;
239 * Request new zone and available input information
241 void updateZoneInformation() {
242 updateAsyncMakeOfflineIfFail(zoneAvailableInputs);
243 updateAsyncMakeOfflineIfFail(zoneControl);
245 if (inputWithPlayControl != null) {
246 updateAsyncMakeOfflineIfFail(inputWithPlayControl);
249 if (inputWithNavigationControl != null) {
250 updateAsyncMakeOfflineIfFail(inputWithNavigationControl);
253 if (inputWithPresetControl != null) {
254 updateAsyncMakeOfflineIfFail(inputWithPresetControl);
257 if (inputWithDabBandControl != null) {
258 updateAsyncMakeOfflineIfFail(inputWithDabBandControl);
263 public void handleCommand(ChannelUID channelUID, Command command) {
264 if (zoneControl == null) {
268 String id = channelUID.getIdWithoutGroup();
271 if (command instanceof RefreshType) {
272 refreshFromState(channelUID);
278 zoneControl.setPower(((OnOffType) command) == OnOffType.ON);
281 zoneControl.setInput(((StringType) command).toString());
283 case CHANNEL_SURROUND:
284 zoneControl.setSurroundProgram(((StringType) command).toString());
286 case CHANNEL_VOLUME_DB:
287 zoneControl.setVolumeDB(((DecimalType) command).floatValue());
290 if (command instanceof DecimalType) {
291 zoneControl.setVolume(((DecimalType) command).floatValue());
292 } else if (command instanceof IncreaseDecreaseType) {
293 zoneControl.setVolumeRelative(zoneState,
294 (((IncreaseDecreaseType) command) == IncreaseDecreaseType.INCREASE ? 1 : -1)
295 * zoneConfig.getVolumeRelativeChangeFactor());
299 zoneControl.setMute(((OnOffType) command) == OnOffType.ON);
302 zoneControl.setScene(((StringType) command).toString());
304 case CHANNEL_DIALOGUE_LEVEL:
305 zoneControl.setDialogueLevel(((DecimalType) command).intValue());
308 case CHANNEL_HDMI1OUT:
309 zoneControl.setHDMI1Out(((OnOffType) command) == OnOffType.ON);
312 case CHANNEL_HDMI2OUT:
313 zoneControl.setHDMI2Out(((OnOffType) command) == OnOffType.ON);
316 case CHANNEL_NAVIGATION_MENU:
317 if (inputWithNavigationControl == null) {
318 logger.warn("Channel {} not working with {} input!", id, zoneState.inputID);
322 String path = ((StringType) command).toFullString();
323 inputWithNavigationControl.selectItemFullPath(path);
326 case CHANNEL_NAVIGATION_UPDOWN:
327 if (inputWithNavigationControl == null) {
328 logger.warn("Channel {} not working with {} input!", id, zoneState.inputID);
331 if (((UpDownType) command) == UpDownType.UP) {
332 inputWithNavigationControl.goUp();
334 inputWithNavigationControl.goDown();
338 case CHANNEL_NAVIGATION_LEFTRIGHT:
339 if (inputWithNavigationControl == null) {
340 logger.warn("Channel {} not working with {} input!", id, zoneState.inputID);
343 if (((UpDownType) command) == UpDownType.UP) {
344 inputWithNavigationControl.goLeft();
346 inputWithNavigationControl.goRight();
350 case CHANNEL_NAVIGATION_SELECT:
351 if (inputWithNavigationControl == null) {
352 logger.warn("Channel {} not working with {} input!", id, zoneState.inputID);
355 inputWithNavigationControl.selectCurrentItem();
358 case CHANNEL_NAVIGATION_BACK:
359 if (inputWithNavigationControl == null) {
360 logger.warn("Channel {} not working with {} input!", id, zoneState.inputID);
363 inputWithNavigationControl.goBack();
366 case CHANNEL_NAVIGATION_BACKTOROOT:
367 if (inputWithNavigationControl == null) {
368 logger.warn("Channel {} not working with {} input!", id, zoneState.inputID);
371 inputWithNavigationControl.goToRoot();
374 case CHANNEL_PLAYBACK_PRESET:
375 if (inputWithPresetControl == null) {
376 logger.warn("Channel {} not working with {} input!", id, zoneState.inputID);
380 if (command instanceof DecimalType) {
381 inputWithPresetControl.selectItemByPresetNumber(((DecimalType) command).intValue());
382 } else if (command instanceof StringType) {
384 int v = Integer.valueOf(((StringType) command).toString());
385 inputWithPresetControl.selectItemByPresetNumber(v);
386 } catch (NumberFormatException e) {
387 logger.warn("Provide a number for {}", id);
392 case CHANNEL_TUNER_BAND:
393 if (inputWithDabBandControl == null) {
394 logger.warn("Channel {} not working with {} input!", id, zoneState.inputID);
398 if (command instanceof StringType) {
399 inputWithDabBandControl.selectBandByName(command.toString());
401 logger.warn("Provide a string for {}", id);
405 case CHANNEL_PLAYBACK:
406 if (inputWithPlayControl == null) {
407 logger.warn("Channel {} not working with {} input!", id, zoneState.inputID);
411 if (command instanceof PlayPauseType) {
412 PlayPauseType t = ((PlayPauseType) command);
415 inputWithPlayControl.pause();
418 inputWithPlayControl.play();
421 } else if (command instanceof NextPreviousType) {
422 NextPreviousType t = ((NextPreviousType) command);
425 inputWithPlayControl.nextTrack();
428 inputWithPlayControl.previousTrack();
431 } else if (command instanceof DecimalType) {
432 int v = ((DecimalType) command).intValue();
434 inputWithPlayControl.skipREV();
436 inputWithPlayControl.skipFF();
438 } else if (command instanceof StringType) {
439 String v = ((StringType) command).toFullString();
442 inputWithPlayControl.play();
445 inputWithPlayControl.pause();
448 inputWithPlayControl.stop();
451 inputWithPlayControl.skipREV();
454 inputWithPlayControl.skipFF();
457 inputWithPlayControl.nextTrack();
460 inputWithPlayControl.previousTrack();
466 logger.warn("Channel {} not supported!", id);
468 } catch (IOException e) {
469 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
470 } catch (ReceivedMessageParseException e) {
471 // Some AVRs send unexpected responses. We log parser exceptions therefore.
472 logger.debug("Parse error!", e);
477 * Called by handleCommand() if a RefreshType command was received. It will update
478 * the given channel with the correct state.
480 * @param channelUID The channel
482 private void refreshFromState(ChannelUID channelUID) {
483 String id = channelUID.getId();
485 if (id.equals(grpZone(CHANNEL_POWER))) {
486 updateState(channelUID, zoneState.power ? OnOffType.ON : OnOffType.OFF);
488 } else if (id.equals(grpZone(CHANNEL_VOLUME_DB))) {
489 updateState(channelUID, new DecimalType(zoneState.volumeDB));
490 } else if (id.equals(grpZone(CHANNEL_VOLUME))) {
491 updateState(channelUID, new PercentType((int) zoneConfig.getVolumePercentage(zoneState.volumeDB)));
492 } else if (id.equals(grpZone(CHANNEL_MUTE))) {
493 updateState(channelUID, zoneState.mute ? OnOffType.ON : OnOffType.OFF);
494 } else if (id.equals(grpZone(CHANNEL_INPUT))) {
495 updateState(channelUID, new StringType(zoneState.inputID));
496 } else if (id.equals(grpZone(CHANNEL_SURROUND))) {
497 updateState(channelUID, new StringType(zoneState.surroundProgram));
498 } else if (id.equals(grpZone(CHANNEL_SCENE))) {
499 // no state updates available
500 } else if (id.equals(grpZone(CHANNEL_DIALOGUE_LEVEL))) {
501 updateState(channelUID, new DecimalType(zoneState.dialogueLevel));
502 } else if (id.equals(grpZone(CHANNEL_HDMI1OUT))) {
503 updateState(channelUID, zoneState.hdmi1Out ? OnOffType.ON : OnOffType.OFF);
504 } else if (id.equals(grpZone(CHANNEL_HDMI2OUT))) {
505 updateState(channelUID, zoneState.hdmi2Out ? OnOffType.ON : OnOffType.OFF);
507 } else if (id.equals(grpPlayback(CHANNEL_PLAYBACK))) {
508 updateState(channelUID, new StringType(playInfoState.playbackMode));
509 } else if (id.equals(grpPlayback(CHANNEL_PLAYBACK_STATION))) {
510 updateState(channelUID, new StringType(playInfoState.station));
511 } else if (id.equals(grpPlayback(CHANNEL_PLAYBACK_ARTIST))) {
512 updateState(channelUID, new StringType(playInfoState.artist));
513 } else if (id.equals(grpPlayback(CHANNEL_PLAYBACK_ALBUM))) {
514 updateState(channelUID, new StringType(playInfoState.album));
515 } else if (id.equals(grpPlayback(CHANNEL_PLAYBACK_SONG))) {
516 updateState(channelUID, new StringType(playInfoState.song));
517 } else if (id.equals(grpPlayback(CHANNEL_PLAYBACK_SONG_IMAGE_URL))) {
518 updateState(channelUID, new StringType(playInfoState.songImageUrl));
519 } else if (id.equals(grpPlayback(CHANNEL_PLAYBACK_PRESET))) {
520 updateState(channelUID, new DecimalType(presetInfoState.presetChannel));
521 } else if (id.equals(grpPlayback(CHANNEL_TUNER_BAND))) {
522 updateState(channelUID, new StringType(dabBandState.band));
524 } else if (id.equals(grpNav(CHANNEL_NAVIGATION_MENU))) {
525 updateState(channelUID, new StringType(navigationInfoState.getCurrentItemName()));
526 } else if (id.equals(grpNav(CHANNEL_NAVIGATION_LEVEL))) {
527 updateState(channelUID, new DecimalType(navigationInfoState.menuLayer));
528 } else if (id.equals(grpNav(CHANNEL_NAVIGATION_CURRENT_ITEM))) {
529 updateState(channelUID, new DecimalType(navigationInfoState.currentLine));
530 } else if (id.equals(grpNav(CHANNEL_NAVIGATION_TOTAL_ITEMS))) {
531 updateState(channelUID, new DecimalType(navigationInfoState.maxLine));
533 logger.warn("Channel {} not implemented!", id);
538 public void zoneStateChanged(ZoneControlState msg) {
539 boolean inputChanged = !msg.inputID.equals(zoneState.inputID);
542 updateStatus(ThingStatus.ONLINE);
544 updateState(grpZone(CHANNEL_POWER), zoneState.power ? OnOffType.ON : OnOffType.OFF);
545 updateState(grpZone(CHANNEL_INPUT), new StringType(zoneState.inputID));
546 updateState(grpZone(CHANNEL_SURROUND), new StringType(zoneState.surroundProgram));
547 updateState(grpZone(CHANNEL_VOLUME_DB), new DecimalType(zoneState.volumeDB));
548 updateState(grpZone(CHANNEL_VOLUME), new PercentType((int) zoneConfig.getVolumePercentage(zoneState.volumeDB)));
549 updateState(grpZone(CHANNEL_MUTE), zoneState.mute ? OnOffType.ON : OnOffType.OFF);
550 updateState(grpZone(CHANNEL_DIALOGUE_LEVEL), new DecimalType(zoneState.dialogueLevel));
551 updateState(grpZone(CHANNEL_HDMI1OUT), zoneState.hdmi1Out ? OnOffType.ON : OnOffType.OFF);
552 updateState(grpZone(CHANNEL_HDMI2OUT), zoneState.hdmi2Out ? OnOffType.ON : OnOffType.OFF);
554 // If the input changed
561 * Called by {@link #zoneStateChanged(ZoneControlState)} if the input has changed.
562 * Will request updates from {@see InputWithNavigationControl} and {@see InputWithPlayControl}.
564 private void inputChanged() {
565 logger.debug("Input changed to {}", zoneState.inputID);
567 if (!isInputSupported(zoneState.inputID)) {
568 // for now just emit a warning in logs
569 logger.warn("Input {} is not supported on your AVR model", zoneState.inputID);
572 inputChangedCheckForNavigationControl();
573 // Note: the DAB band needs to be initialized before preset and playback
574 inputChangedCheckForDabBand();
575 inputChangedCheckForPlaybackControl();
576 inputChangedCheckForPresetControl();
580 * Checks if the specified input is supported given the detected device feature information.
582 * @param inputID - the input name
583 * @return true when input is supported
585 private boolean isInputSupported(String inputID) {
588 return getDeviceInformationState().features.contains(Feature.SPOTIFY);
591 return getDeviceInformationState().features.contains(Feature.TUNER)
592 || getDeviceInformationState().features.contains(Feature.DAB);
594 // Note: add more inputs here in the future
599 private void inputChangedCheckForNavigationControl() {
600 boolean includeInputWithNavigationControl = false;
602 for (String channelName : CHANNELS_NAVIGATION) {
603 if (isLinked(grpNav(channelName))) {
604 includeInputWithNavigationControl = true;
609 if (includeInputWithNavigationControl) {
610 includeInputWithNavigationControl = InputWithNavigationControl.SUPPORTED_INPUTS.contains(zoneState.inputID);
611 if (!includeInputWithNavigationControl) {
612 logger.debug("Navigation control not supported by {}", zoneState.inputID);
616 logger.trace("Navigation control requested by channel");
618 if (!includeInputWithNavigationControl) {
619 inputWithNavigationControl = null;
620 navigationInfoState.invalidate();
621 navigationUpdated(navigationInfoState);
625 inputWithNavigationControl = getProtocolFactory().InputWithNavigationControl(getConnection(),
626 navigationInfoState, zoneState.inputID, this, getDeviceInformationState());
628 updateAsyncMakeOfflineIfFail(inputWithNavigationControl);
631 private void inputChangedCheckForPlaybackControl() {
632 boolean includeInputWithPlaybackControl = false;
634 for (String channelName : CHANNELS_PLAYBACK) {
635 if (isLinked(grpPlayback(channelName))) {
636 includeInputWithPlaybackControl = true;
641 logger.trace("Playback control requested by channel");
643 if (includeInputWithPlaybackControl) {
644 includeInputWithPlaybackControl = InputWithPlayControl.SUPPORTED_INPUTS.contains(zoneState.inputID);
645 if (!includeInputWithPlaybackControl) {
646 logger.debug("Playback control not supported by {}", zoneState.inputID);
650 if (!includeInputWithPlaybackControl) {
651 inputWithPlayControl = null;
652 playInfoState.invalidate();
653 playInfoUpdated(playInfoState);
658 * The {@link inputChangedCheckForDabBand} needs to be called first before this method, in case the AVR Supports
661 if (inputWithDabBandControl != null) {
662 // When input is Tuner DAB there is no playback control
663 inputWithPlayControl = null;
665 inputWithPlayControl = getProtocolFactory().InputWithPlayControl(getConnection(), zoneState.inputID, this,
666 getBridgeHandler().getConfiguration(), getDeviceInformationState());
668 updateAsyncMakeOfflineIfFail(inputWithPlayControl);
672 private void inputChangedCheckForPresetControl() {
673 boolean includeInput = isLinked(grpPlayback(CHANNEL_PLAYBACK_PRESET));
675 logger.trace("Preset control requested by channel");
678 includeInput = InputWithPresetControl.SUPPORTED_INPUTS.contains(zoneState.inputID);
680 logger.debug("Preset control not supported by {}", zoneState.inputID);
685 inputWithPresetControl = null;
686 presetInfoState.invalidate();
687 presetInfoUpdated(presetInfoState);
692 * The {@link inputChangedCheckForDabBand} needs to be called first before this method, in case the AVR Supports
695 if (inputWithDabBandControl != null) {
696 // When the input is Tuner DAB the control also provides preset functionality
697 inputWithPresetControl = (InputWithPresetControl) inputWithDabBandControl;
698 // Note: No need to update state - it will be already called for DabBand control (see
699 // inputChangedCheckForDabBand)
701 inputWithPresetControl = getProtocolFactory().InputWithPresetControl(getConnection(), zoneState.inputID,
702 this, getDeviceInformationState());
704 updateAsyncMakeOfflineIfFail(inputWithPresetControl);
708 private void inputChangedCheckForDabBand() {
709 boolean includeInput = isLinked(grpPlayback(CHANNEL_TUNER_BAND));
711 logger.trace("Band control requested by channel");
714 // Check if TUNER input is DAB - dual bands radio tuner
715 includeInput = InputWithTunerBandControl.SUPPORTED_INPUTS.contains(zoneState.inputID)
716 && getDeviceInformationState().features.contains(Feature.DAB);
718 logger.debug("Band control not supported by {}", zoneState.inputID);
723 inputWithDabBandControl = null;
724 dabBandState.invalidate();
725 dabBandUpdated(dabBandState);
729 logger.debug("InputWithTunerBandControl created for {}", zoneState.inputID);
730 inputWithDabBandControl = getProtocolFactory().InputWithDabBandControl(zoneState.inputID, getConnection(), this,
731 this, this, getDeviceInformationState());
733 updateAsyncMakeOfflineIfFail(inputWithDabBandControl);
736 protected void updateAsyncMakeOfflineIfFail(IStateUpdatable stateUpdatable) {
737 scheduler.submit(() -> {
739 stateUpdatable.update();
740 } catch (IOException e) {
741 logger.debug("State update error. Changing thing to offline", e);
742 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, e.getMessage());
743 } catch (ReceivedMessageParseException e) {
744 String message = e.getMessage();
745 updateProperty(PROPERTY_LAST_PARSE_ERROR, message != null ? message : "");
746 // Some AVRs send unexpected responses. We log parser exceptions therefore.
747 logger.debug("Parse error!", e);
753 * Once this thing is set up and the AVR is connected, the available inputs for this zone are requested.
754 * The thing is updated with a new CHANNEL_AVAILABLE_INPUT which lists the available inputs for the current zone..
757 public void availableInputsChanged(AvailableInputState msg) {
758 // Update channel type provider with a list of available inputs
759 channelsTypeProviderAvailableInputs.changeAvailableInputs(msg.availableInputs);
761 // Remove the old channel and add the new channel. The channel will be requested from the
762 // yamahaChannelTypeProvider.
763 ChannelUID inputChannelUID = new ChannelUID(thing.getUID(), CHANNEL_GROUP_ZONE, CHANNEL_INPUT);
764 Channel channel = ChannelBuilder.create(inputChannelUID, "String")
765 .withType(channelsTypeProviderAvailableInputs.getChannelTypeUID()).build();
766 updateThing(editThing().withoutChannel(inputChannelUID).withChannel(channel).build());
769 private String grpPlayback(String channelIDWithoutGroup) {
770 return new ChannelUID(thing.getUID(), CHANNEL_GROUP_PLAYBACK, channelIDWithoutGroup).getId();
773 private String grpNav(String channelIDWithoutGroup) {
774 return new ChannelUID(thing.getUID(), CHANNEL_GROUP_NAVIGATION, channelIDWithoutGroup).getId();
777 private String grpZone(String channelIDWithoutGroup) {
778 return new ChannelUID(thing.getUID(), CHANNEL_GROUP_ZONE, channelIDWithoutGroup).getId();
782 public void playInfoUpdated(PlayInfoState msg) {
785 updateState(grpPlayback(CHANNEL_PLAYBACK), new StringType(msg.playbackMode));
786 updateState(grpPlayback(CHANNEL_PLAYBACK_STATION), new StringType(msg.station));
787 updateState(grpPlayback(CHANNEL_PLAYBACK_ARTIST), new StringType(msg.artist));
788 updateState(grpPlayback(CHANNEL_PLAYBACK_ALBUM), new StringType(msg.album));
789 updateState(grpPlayback(CHANNEL_PLAYBACK_SONG), new StringType(msg.song));
790 updateState(grpPlayback(CHANNEL_PLAYBACK_SONG_IMAGE_URL), new StringType(msg.songImageUrl));
794 public void presetInfoUpdated(PresetInfoState msg) {
795 presetInfoState = msg;
797 if (msg.presetChannelNamesChanged) {
798 msg.presetChannelNamesChanged = false;
800 channelsTypeProviderPreset.changePresetNames(msg.presetChannelNames);
802 // Remove the old channel and add the new channel. The channel will be requested from the
803 // channelsTypeProviderPreset.
804 ChannelUID inputChannelUID = new ChannelUID(thing.getUID(), CHANNEL_GROUP_PLAYBACK,
805 CHANNEL_PLAYBACK_PRESET);
806 Channel channel = ChannelBuilder.create(inputChannelUID, "Number")
807 .withType(channelsTypeProviderPreset.getChannelTypeUID()).build();
808 updateThing(editThing().withoutChannel(inputChannelUID).withChannel(channel).build());
811 updateState(grpPlayback(CHANNEL_PLAYBACK_PRESET), new DecimalType(msg.presetChannel));
815 public void dabBandUpdated(DabBandState msg) {
817 updateState(grpPlayback(CHANNEL_TUNER_BAND), new StringType(msg.band));
821 public void navigationUpdated(NavigationControlState msg) {
822 navigationInfoState = msg;
823 updateState(grpNav(CHANNEL_NAVIGATION_MENU), new StringType(msg.menuName));
824 updateState(grpNav(CHANNEL_NAVIGATION_LEVEL), new DecimalType(msg.menuLayer));
825 updateState(grpNav(CHANNEL_NAVIGATION_CURRENT_ITEM), new DecimalType(msg.currentLine));
826 updateState(grpNav(CHANNEL_NAVIGATION_TOTAL_ITEMS), new DecimalType(msg.maxLine));
830 public void navigationError(String msg) {
831 updateProperty(PROPERTY_MENU_ERROR, msg);
832 logger.warn("Navigation error: {}", msg);