2 * Copyright (c) 2010-2021 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.rotel.internal.handler;
15 import static org.openhab.binding.rotel.internal.RotelBindingConstants.*;
17 import java.math.BigDecimal;
18 import java.util.ArrayList;
19 import java.util.EventObject;
20 import java.util.HashMap;
21 import java.util.List;
23 import java.util.concurrent.ScheduledFuture;
24 import java.util.concurrent.TimeUnit;
26 import org.eclipse.jdt.annotation.NonNullByDefault;
27 import org.eclipse.jdt.annotation.Nullable;
28 import org.openhab.binding.rotel.internal.RotelBindingConstants;
29 import org.openhab.binding.rotel.internal.RotelException;
30 import org.openhab.binding.rotel.internal.RotelModel;
31 import org.openhab.binding.rotel.internal.RotelPlayStatus;
32 import org.openhab.binding.rotel.internal.RotelStateDescriptionOptionProvider;
33 import org.openhab.binding.rotel.internal.communication.RotelCommand;
34 import org.openhab.binding.rotel.internal.communication.RotelConnector;
35 import org.openhab.binding.rotel.internal.communication.RotelDsp;
36 import org.openhab.binding.rotel.internal.communication.RotelIpConnector;
37 import org.openhab.binding.rotel.internal.communication.RotelMessageEvent;
38 import org.openhab.binding.rotel.internal.communication.RotelMessageEventListener;
39 import org.openhab.binding.rotel.internal.communication.RotelProtocol;
40 import org.openhab.binding.rotel.internal.communication.RotelSerialConnector;
41 import org.openhab.binding.rotel.internal.communication.RotelSimuConnector;
42 import org.openhab.binding.rotel.internal.communication.RotelSource;
43 import org.openhab.binding.rotel.internal.configuration.RotelThingConfiguration;
44 import org.openhab.core.i18n.LocaleProvider;
45 import org.openhab.core.i18n.TranslationProvider;
46 import org.openhab.core.io.transport.serial.SerialPortManager;
47 import org.openhab.core.library.types.DecimalType;
48 import org.openhab.core.library.types.IncreaseDecreaseType;
49 import org.openhab.core.library.types.NextPreviousType;
50 import org.openhab.core.library.types.OnOffType;
51 import org.openhab.core.library.types.PercentType;
52 import org.openhab.core.library.types.PlayPauseType;
53 import org.openhab.core.library.types.StringType;
54 import org.openhab.core.thing.ChannelUID;
55 import org.openhab.core.thing.Thing;
56 import org.openhab.core.thing.ThingStatus;
57 import org.openhab.core.thing.ThingStatusDetail;
58 import org.openhab.core.thing.binding.BaseThingHandler;
59 import org.openhab.core.types.Command;
60 import org.openhab.core.types.RefreshType;
61 import org.openhab.core.types.State;
62 import org.openhab.core.types.StateOption;
63 import org.openhab.core.types.UnDefType;
64 import org.osgi.framework.Bundle;
65 import org.osgi.framework.FrameworkUtil;
66 import org.slf4j.Logger;
67 import org.slf4j.LoggerFactory;
70 * The {@link RotelHandler} is responsible for handling commands, which are sent to one of the channels.
72 * @author Laurent Garnier - Initial contribution
75 public class RotelHandler extends BaseThingHandler implements RotelMessageEventListener {
77 private final Logger logger = LoggerFactory.getLogger(RotelHandler.class);
79 private static final RotelModel DEFAULT_MODEL = RotelModel.RSP1066;
80 private static final long POLLING_INTERVAL = TimeUnit.SECONDS.toSeconds(60);
81 private static final boolean USE_SIMULATED_DEVICE = false;
83 private @Nullable ScheduledFuture<?> reconnectJob;
84 private @Nullable ScheduledFuture<?> powerOnJob;
85 private @Nullable ScheduledFuture<?> powerOffJob;
86 private @Nullable ScheduledFuture<?> powerOnZone2Job;
87 private @Nullable ScheduledFuture<?> powerOnZone3Job;
88 private @Nullable ScheduledFuture<?> powerOnZone4Job;
90 private RotelStateDescriptionOptionProvider stateDescriptionProvider;
91 private SerialPortManager serialPortManager;
92 private TranslationProvider i18nProvider;
93 private LocaleProvider localeProvider;
94 private Bundle bundle;
96 private RotelConnector connector = new RotelSimuConnector(DEFAULT_MODEL, RotelProtocol.HEX, new HashMap<>(),
99 private int minVolume;
100 private int maxVolume;
101 private int minToneLevel;
102 private int maxToneLevel;
104 private int currentZone = 1;
105 private boolean selectingRecord;
106 private @Nullable Boolean power;
107 private boolean powerZone2;
108 private boolean powerZone3;
109 private boolean powerZone4;
110 private RotelSource source = RotelSource.CAT0_CD;
111 private @Nullable RotelSource recordSource;
112 private @Nullable RotelSource sourceZone2;
113 private @Nullable RotelSource sourceZone3;
114 private @Nullable RotelSource sourceZone4;
115 private RotelDsp dsp = RotelDsp.CAT1_NONE;
117 private boolean mute;
118 private boolean fixedVolumeZone2;
119 private int volumeZone2;
120 private boolean muteZone2;
121 private boolean fixedVolumeZone3;
122 private int volumeZone3;
123 private boolean muteZone3;
124 private boolean fixedVolumeZone4;
125 private int volumeZone4;
126 private boolean muteZone4;
129 private RotelPlayStatus playStatus = RotelPlayStatus.STOPPED;
131 private double frequency;
132 private String frontPanelLine1 = "";
133 private String frontPanelLine2 = "";
134 private int brightness;
136 private Object sequenceLock = new Object();
141 public RotelHandler(Thing thing, RotelStateDescriptionOptionProvider stateDescriptionProvider,
142 SerialPortManager serialPortManager, TranslationProvider i18nProvider, LocaleProvider localeProvider) {
144 this.stateDescriptionProvider = stateDescriptionProvider;
145 this.serialPortManager = serialPortManager;
146 this.i18nProvider = i18nProvider;
147 this.localeProvider = localeProvider;
148 this.bundle = FrameworkUtil.getBundle(this.getClass()).getBundleContext().getBundle();
152 public void initialize() {
153 logger.debug("Start initializing handler for thing {}", getThing().getUID());
155 RotelModel rotelModel;
156 switch (getThing().getThingTypeUID().getId()) {
157 case THING_TYPE_ID_RSP1066:
158 rotelModel = RotelModel.RSP1066;
160 case THING_TYPE_ID_RSP1068:
161 rotelModel = RotelModel.RSP1068;
163 case THING_TYPE_ID_RSP1069:
164 rotelModel = RotelModel.RSP1069;
166 case THING_TYPE_ID_RSP1098:
167 rotelModel = RotelModel.RSP1098;
169 case THING_TYPE_ID_RSP1570:
170 rotelModel = RotelModel.RSP1570;
172 case THING_TYPE_ID_RSP1572:
173 rotelModel = RotelModel.RSP1572;
175 case THING_TYPE_ID_RSX1055:
176 rotelModel = RotelModel.RSX1055;
178 case THING_TYPE_ID_RSX1056:
179 rotelModel = RotelModel.RSX1056;
181 case THING_TYPE_ID_RSX1057:
182 rotelModel = RotelModel.RSX1057;
184 case THING_TYPE_ID_RSX1058:
185 rotelModel = RotelModel.RSX1058;
187 case THING_TYPE_ID_RSX1065:
188 rotelModel = RotelModel.RSX1065;
190 case THING_TYPE_ID_RSX1067:
191 rotelModel = RotelModel.RSX1067;
193 case THING_TYPE_ID_RSX1550:
194 rotelModel = RotelModel.RSX1550;
196 case THING_TYPE_ID_RSX1560:
197 rotelModel = RotelModel.RSX1560;
199 case THING_TYPE_ID_RSX1562:
200 rotelModel = RotelModel.RSX1562;
202 case THING_TYPE_ID_A11:
203 rotelModel = RotelModel.A11;
205 case THING_TYPE_ID_A12:
206 rotelModel = RotelModel.A12;
208 case THING_TYPE_ID_A14:
209 rotelModel = RotelModel.A14;
211 case THING_TYPE_ID_CD11:
212 rotelModel = RotelModel.CD11;
214 case THING_TYPE_ID_CD14:
215 rotelModel = RotelModel.CD14;
217 case THING_TYPE_ID_RA11:
218 rotelModel = RotelModel.RA11;
220 case THING_TYPE_ID_RA12:
221 rotelModel = RotelModel.RA12;
223 case THING_TYPE_ID_RA1570:
224 rotelModel = RotelModel.RA1570;
226 case THING_TYPE_ID_RA1572:
227 rotelModel = RotelModel.RA1572;
229 case THING_TYPE_ID_RA1592:
230 rotelModel = RotelModel.RA1592;
232 case THING_TYPE_ID_RAP1580:
233 rotelModel = RotelModel.RAP1580;
235 case THING_TYPE_ID_RC1570:
236 rotelModel = RotelModel.RC1570;
238 case THING_TYPE_ID_RC1572:
239 rotelModel = RotelModel.RC1572;
241 case THING_TYPE_ID_RC1590:
242 rotelModel = RotelModel.RC1590;
244 case THING_TYPE_ID_RCD1570:
245 rotelModel = RotelModel.RCD1570;
247 case THING_TYPE_ID_RCD1572:
248 rotelModel = RotelModel.RCD1572;
250 case THING_TYPE_ID_RCX1500:
251 rotelModel = RotelModel.RCX1500;
253 case THING_TYPE_ID_RDD1580:
254 rotelModel = RotelModel.RDD1580;
256 case THING_TYPE_ID_RDG1520:
257 case THING_TYPE_ID_RT09:
258 rotelModel = RotelModel.RDG1520;
260 case THING_TYPE_ID_RSP1576:
261 rotelModel = RotelModel.RSP1576;
263 case THING_TYPE_ID_RSP1582:
264 rotelModel = RotelModel.RSP1582;
266 case THING_TYPE_ID_RT11:
267 rotelModel = RotelModel.RT11;
269 case THING_TYPE_ID_RT1570:
270 rotelModel = RotelModel.RT1570;
272 case THING_TYPE_ID_T11:
273 rotelModel = RotelModel.T11;
275 case THING_TYPE_ID_T14:
276 rotelModel = RotelModel.T14;
279 rotelModel = DEFAULT_MODEL;
283 RotelThingConfiguration config = getConfigAs(RotelThingConfiguration.class);
285 RotelProtocol rotelProtocol = RotelProtocol.HEX;
286 if (config.protocol != null && !config.protocol.isEmpty()) {
288 rotelProtocol = RotelProtocol.getFromName(config.protocol);
289 } catch (RotelException e) {
292 Map<String, String> properties = editProperties();
293 String property = properties.get(RotelBindingConstants.PROPERTY_PROTOCOL);
294 if (property != null && !property.isEmpty()) {
296 rotelProtocol = RotelProtocol.getFromName(property);
297 } catch (RotelException e) {
301 logger.debug("rotelProtocol {}", rotelProtocol.getName());
303 Map<RotelSource, String> sourcesCustomLabels = new HashMap<>();
304 Map<RotelSource, String> sourcesLabels = new HashMap<>();
306 String readerThreadName = "OH-binding-" + getThing().getUID().getAsString();
308 connector = new RotelSimuConnector(rotelModel, rotelProtocol, sourcesLabels, readerThreadName);
310 if (rotelModel.hasVolumeControl()) {
311 maxVolume = rotelModel.getVolumeMax();
312 if (!rotelModel.hasDirectVolumeControl()) {
314 "Set minValue to {} and maxValue to {} for your sitemap widget attached to your volume item.",
315 minVolume, maxVolume);
318 if (rotelModel.hasToneControl()) {
319 maxToneLevel = rotelModel.getToneLevelMax();
320 minToneLevel = -maxToneLevel;
322 "Set minValue to {} and maxValue to {} for your sitemap widget attached to your bass or treble item.",
323 minToneLevel, maxToneLevel);
326 // Check configuration settings
327 String configError = null;
328 if ((config.serialPort == null || config.serialPort.isEmpty())
329 && (config.host == null || config.host.isEmpty())) {
330 configError = "@text/offline.config-error-unknown-serialport-and-host";
331 } else if (config.host == null || config.host.isEmpty()) {
332 if (config.serialPort.toLowerCase().startsWith("rfc2217")) {
333 configError = "@text/offline.config-error-invalid-serial-over-ip";
336 if (config.port == null) {
337 configError = "@text/offline.config-error-unknown-port";
338 } else if (config.port <= 0) {
339 configError = "@text/offline.config-error-invalid-port";
343 if (configError != null) {
344 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, configError);
346 for (RotelSource src : rotelModel.getSources()) {
347 // Consider custom input labels
349 switch (src.getName()) {
351 label = config.inputLabelCd;
354 label = config.inputLabelTuner;
357 label = config.inputLabelTape;
360 label = config.inputLabelPhono;
363 label = config.inputLabelVideo1;
366 label = config.inputLabelVideo2;
369 label = config.inputLabelVideo3;
372 label = config.inputLabelVideo4;
375 label = config.inputLabelVideo5;
378 label = config.inputLabelVideo6;
381 label = config.inputLabelUsb;
384 label = config.inputLabelMulti;
389 if (label != null && !label.isEmpty()) {
390 sourcesCustomLabels.put(src, label);
392 sourcesLabels.put(src, (label == null || label.isEmpty()) ? src.getLabel() : label);
395 if (USE_SIMULATED_DEVICE) {
396 connector = new RotelSimuConnector(rotelModel, rotelProtocol, sourcesLabels, readerThreadName);
397 } else if (config.serialPort != null) {
398 connector = new RotelSerialConnector(serialPortManager, config.serialPort, rotelModel, rotelProtocol,
399 sourcesLabels, readerThreadName);
401 connector = new RotelIpConnector(config.host, config.port, rotelModel, rotelProtocol, sourcesLabels,
405 if (rotelModel.hasSourceControl()) {
406 stateDescriptionProvider.setStateOptions(new ChannelUID(getThing().getUID(), CHANNEL_SOURCE),
407 getStateOptions(rotelModel.getSources(), sourcesCustomLabels));
408 stateDescriptionProvider.setStateOptions(new ChannelUID(getThing().getUID(), CHANNEL_MAIN_SOURCE),
409 getStateOptions(rotelModel.getSources(), sourcesCustomLabels));
410 stateDescriptionProvider.setStateOptions(
411 new ChannelUID(getThing().getUID(), CHANNEL_MAIN_RECORD_SOURCE),
412 getStateOptions(rotelModel.getRecordSources(), sourcesCustomLabels));
414 if (rotelModel.hasZone2SourceControl()) {
415 stateDescriptionProvider.setStateOptions(new ChannelUID(getThing().getUID(), CHANNEL_ZONE2_SOURCE),
416 getStateOptions(rotelModel.getZone2Sources(), sourcesCustomLabels));
418 if (rotelModel.hasZone3SourceControl()) {
419 stateDescriptionProvider.setStateOptions(new ChannelUID(getThing().getUID(), CHANNEL_ZONE3_SOURCE),
420 getStateOptions(rotelModel.getZone3Sources(), sourcesCustomLabels));
422 if (rotelModel.hasZone4SourceControl()) {
423 stateDescriptionProvider.setStateOptions(new ChannelUID(getThing().getUID(), CHANNEL_ZONE4_SOURCE),
424 getStateOptions(rotelModel.getZone4Sources(), sourcesCustomLabels));
426 if (rotelModel.hasDspControl()) {
427 stateDescriptionProvider.setStateOptions(new ChannelUID(getThing().getUID(), CHANNEL_DSP),
428 rotelModel.getDspStateOptions());
429 stateDescriptionProvider.setStateOptions(new ChannelUID(getThing().getUID(), CHANNEL_MAIN_DSP),
430 rotelModel.getDspStateOptions());
433 updateStatus(ThingStatus.UNKNOWN);
435 scheduleReconnectJob();
438 logger.debug("Finished initializing!");
442 public void dispose() {
443 logger.debug("Disposing handler for thing {}", getThing().getUID());
446 cancelPowerOnZone2Job();
447 cancelPowerOnZone3Job();
448 cancelPowerOnZone4Job();
449 cancelReconnectJob();
454 public List<StateOption> getStateOptions(List<RotelSource> list, Map<RotelSource, String> sourcesLabels) {
455 List<StateOption> options = new ArrayList<>();
456 for (RotelSource item : list) {
457 String label = sourcesLabels.get(item);
458 String key = "source." + item.getName();
459 String label2 = i18nProvider.getText(bundle, key, key, localeProvider.getLocale());
460 if (label2 == null || label2.isEmpty()) {
463 options.add(new StateOption(item.getName(), label == null ? label2 : label));
469 public void handleCommand(ChannelUID channelUID, Command command) {
470 String channel = channelUID.getId();
472 if (getThing().getStatus() != ThingStatus.ONLINE) {
473 logger.debug("Thing is not ONLINE; command {} from channel {} is ignored", command, channel);
477 if (command instanceof RefreshType) {
478 updateChannelState(channel);
482 if (!connector.isConnected()) {
483 logger.debug("Command {} from channel {} is ignored: connection not established", command, channel);
489 boolean success = true;
490 synchronized (sequenceLock) {
494 case CHANNEL_MAIN_POWER:
495 handlePowerCmd(channel, command, getPowerOnCommand(), getPowerOffCommand());
497 case CHANNEL_ZONE2_POWER:
498 if (connector.getModel().hasZone2Commands()) {
499 handlePowerCmd(channel, command, RotelCommand.ZONE2_POWER_ON, RotelCommand.ZONE2_POWER_OFF);
500 } else if (connector.getModel().getNbAdditionalZones() == 1) {
501 if (isPowerOn() || powerZone2) {
502 selectZone(2, connector.getModel().getZoneSelectCmd());
504 connector.sendCommand(RotelCommand.ZONE_SELECT);
507 logger.debug("Command {} from channel {} failed: unavailable feature", command, channel);
510 case CHANNEL_ZONE3_POWER:
511 if (connector.getModel().hasZone3Commands()) {
512 handlePowerCmd(channel, command, RotelCommand.ZONE3_POWER_ON, RotelCommand.ZONE3_POWER_OFF);
515 logger.debug("Command {} from channel {} failed: unavailable feature", command, channel);
518 case CHANNEL_ZONE4_POWER:
519 if (connector.getModel().hasZone4Commands()) {
520 handlePowerCmd(channel, command, RotelCommand.ZONE4_POWER_ON, RotelCommand.ZONE4_POWER_OFF);
523 logger.debug("Command {} from channel {} failed: unavailable feature", command, channel);
527 case CHANNEL_MAIN_SOURCE:
530 logger.debug("Command {} from channel {} ignored: device in standby", command, channel);
532 src = connector.getModel().getSourceFromName(command.toString());
533 cmd = connector.getModel().hasOtherThanPrimaryCommands() ? src.getMainZoneCommand()
536 connector.sendCommand(cmd);
539 logger.debug("Command {} from channel {} failed: undefined source command", command,
544 case CHANNEL_MAIN_RECORD_SOURCE:
547 logger.debug("Command {} from channel {} ignored: device in standby", command, channel);
548 } else if (connector.getModel().hasOtherThanPrimaryCommands()) {
549 src = connector.getModel().getSourceFromName(command.toString());
550 cmd = src.getRecordCommand();
552 connector.sendCommand(cmd);
555 logger.debug("Command {} from channel {} failed: undefined record source command",
559 src = connector.getModel().getSourceFromName(command.toString());
560 cmd = src.getCommand();
562 connector.sendCommand(RotelCommand.RECORD_FONCTION_SELECT);
564 connector.sendCommand(cmd);
567 logger.debug("Command {} from channel {} failed: undefined source command", command,
572 case CHANNEL_ZONE2_SOURCE:
575 logger.debug("Command {} from channel {} ignored: zone 2 in standby", command, channel);
576 } else if (connector.getModel().hasZone2Commands()) {
577 src = connector.getModel().getSourceFromName(command.toString());
578 cmd = src.getZone2Command();
580 connector.sendCommand(cmd);
583 logger.debug("Command {} from channel {} failed: undefined zone 2 source command",
586 } else if (connector.getModel().getNbAdditionalZones() >= 1) {
587 src = connector.getModel().getSourceFromName(command.toString());
588 cmd = src.getCommand();
590 selectZone(2, connector.getModel().getZoneSelectCmd());
591 connector.sendCommand(cmd);
594 logger.debug("Command {} from channel {} failed: undefined source command", command,
599 logger.debug("Command {} from channel {} failed: unavailable feature", command, channel);
602 case CHANNEL_ZONE3_SOURCE:
605 logger.debug("Command {} from channel {} ignored: zone 3 in standby", command, channel);
606 } else if (connector.getModel().hasZone3Commands()) {
607 src = connector.getModel().getSourceFromName(command.toString());
608 cmd = src.getZone3Command();
610 connector.sendCommand(cmd);
613 logger.debug("Command {} from channel {} failed: undefined zone 3 source command",
618 logger.debug("Command {} from channel {} failed: unavailable feature", command, channel);
621 case CHANNEL_ZONE4_SOURCE:
624 logger.debug("Command {} from channel {} ignored: zone 4 in standby", command, channel);
625 } else if (connector.getModel().hasZone4Commands()) {
626 src = connector.getModel().getSourceFromName(command.toString());
627 cmd = src.getZone4Command();
629 connector.sendCommand(cmd);
632 logger.debug("Command {} from channel {} failed: undefined zone 4 source command",
637 logger.debug("Command {} from channel {} failed: unavailable feature", command, channel);
641 case CHANNEL_MAIN_DSP:
644 logger.debug("Command {} from channel {} ignored: device in standby", command, channel);
646 connector.sendCommand(connector.getModel().getCommandFromDspName(command.toString()));
650 case CHANNEL_MAIN_VOLUME:
653 logger.debug("Command {} from channel {} ignored: device in standby", command, channel);
654 } else if (connector.getModel().hasVolumeControl()) {
655 handleVolumeCmd(volume, channel, command, getVolumeUpCommand(), getVolumeDownCommand(),
656 RotelCommand.VOLUME_SET);
659 logger.debug("Command {} from channel {} failed: unavailable feature", command, channel);
662 case CHANNEL_MAIN_VOLUME_UP_DOWN:
665 logger.debug("Command {} from channel {} ignored: device in standby", command, channel);
666 } else if (connector.getModel().hasVolumeControl()) {
667 handleVolumeCmd(volume, channel, command, getVolumeUpCommand(), getVolumeDownCommand(),
671 logger.debug("Command {} from channel {} failed: unavailable feature", command, channel);
674 case CHANNEL_ZONE2_VOLUME:
677 logger.debug("Command {} from channel {} ignored: zone 2 in standby", command, channel);
678 } else if (fixedVolumeZone2) {
680 logger.debug("Command {} from channel {} ignored: fixed volume in zone 2", command,
682 } else if (connector.getModel().hasVolumeControl()
683 && connector.getModel().getNbAdditionalZones() >= 1) {
684 if (connector.getModel().hasZone2Commands()) {
685 handleVolumeCmd(volumeZone2, channel, command, RotelCommand.ZONE2_VOLUME_UP,
686 RotelCommand.ZONE2_VOLUME_DOWN, RotelCommand.ZONE2_VOLUME_SET);
688 selectZone(2, connector.getModel().getZoneSelectCmd());
689 handleVolumeCmd(volumeZone2, channel, command, RotelCommand.VOLUME_UP,
690 RotelCommand.VOLUME_DOWN, RotelCommand.VOLUME_SET);
694 logger.debug("Command {} from channel {} failed: unavailable feature", command, channel);
697 case CHANNEL_ZONE2_VOLUME_UP_DOWN:
700 logger.debug("Command {} from channel {} ignored: zone 2 in standby", command, channel);
701 } else if (fixedVolumeZone2) {
703 logger.debug("Command {} from channel {} ignored: fixed volume in zone 2", command,
705 } else if (connector.getModel().hasVolumeControl()
706 && connector.getModel().getNbAdditionalZones() >= 1) {
707 if (connector.getModel().hasZone2Commands()) {
708 handleVolumeCmd(volumeZone2, channel, command, RotelCommand.ZONE2_VOLUME_UP,
709 RotelCommand.ZONE2_VOLUME_DOWN, null);
711 selectZone(2, connector.getModel().getZoneSelectCmd());
712 handleVolumeCmd(volumeZone2, channel, command, RotelCommand.VOLUME_UP,
713 RotelCommand.VOLUME_DOWN, null);
717 logger.debug("Command {} from channel {} failed: unavailable feature", command, channel);
720 case CHANNEL_ZONE3_VOLUME:
723 logger.debug("Command {} from channel {} ignored: zone 3 in standby", command, channel);
724 } else if (fixedVolumeZone3) {
726 logger.debug("Command {} from channel {} ignored: fixed volume in zone 3", command,
728 } else if (connector.getModel().hasVolumeControl() && connector.getModel().hasZone3Commands()) {
729 handleVolumeCmd(volumeZone3, channel, command, RotelCommand.ZONE3_VOLUME_UP,
730 RotelCommand.ZONE3_VOLUME_DOWN, RotelCommand.ZONE3_VOLUME_SET);
733 logger.debug("Command {} from channel {} failed: unavailable feature", command, channel);
736 case CHANNEL_ZONE4_VOLUME:
739 logger.debug("Command {} from channel {} ignored: zone 4 in standby", command, channel);
740 } else if (fixedVolumeZone4) {
742 logger.debug("Command {} from channel {} ignored: fixed volume in zone 4", command,
744 } else if (connector.getModel().hasVolumeControl() && connector.getModel().hasZone4Commands()) {
745 handleVolumeCmd(volumeZone4, channel, command, RotelCommand.ZONE4_VOLUME_UP,
746 RotelCommand.ZONE4_VOLUME_DOWN, RotelCommand.ZONE4_VOLUME_SET);
749 logger.debug("Command {} from channel {} failed: unavailable feature", command, channel);
753 case CHANNEL_MAIN_MUTE:
756 logger.debug("Command {} from channel {} ignored: device in standby", command, channel);
757 } else if (connector.getModel().hasVolumeControl()) {
758 handleMuteCmd(connector.getProtocol() == RotelProtocol.HEX, channel, command,
759 getMuteOnCommand(), getMuteOffCommand(), getMuteToggleCommand());
762 logger.debug("Command {} from channel {} failed: unavailable feature", command, channel);
765 case CHANNEL_ZONE2_MUTE:
768 logger.debug("Command {} from channel {} ignored: zone 2 in standby", command, channel);
769 } else if (connector.getModel().hasVolumeControl() && connector.getModel().hasZone2Commands()) {
770 handleMuteCmd(false, channel, command, RotelCommand.ZONE2_MUTE_ON,
771 RotelCommand.ZONE2_MUTE_OFF, RotelCommand.ZONE2_MUTE_TOGGLE);
774 logger.debug("Command {} from channel {} failed: unavailable feature", command, channel);
777 case CHANNEL_ZONE3_MUTE:
780 logger.debug("Command {} from channel {} ignored: zone 3 in standby", command, channel);
781 } else if (connector.getModel().hasVolumeControl() && connector.getModel().hasZone3Commands()) {
782 handleMuteCmd(false, channel, command, RotelCommand.ZONE3_MUTE_ON,
783 RotelCommand.ZONE3_MUTE_OFF, RotelCommand.ZONE3_MUTE_TOGGLE);
786 logger.debug("Command {} from channel {} failed: unavailable feature", command, channel);
789 case CHANNEL_ZONE4_MUTE:
792 logger.debug("Command {} from channel {} ignored: zone 4 in standby", command, channel);
793 } else if (connector.getModel().hasVolumeControl() && connector.getModel().hasZone4Commands()) {
794 handleMuteCmd(false, channel, command, RotelCommand.ZONE4_MUTE_ON,
795 RotelCommand.ZONE4_MUTE_OFF, RotelCommand.ZONE4_MUTE_TOGGLE);
798 logger.debug("Command {} from channel {} failed: unavailable feature", command, channel);
802 case CHANNEL_MAIN_BASS:
805 logger.debug("Command {} from channel {} ignored: device in standby", command, channel);
807 handleToneCmd(bass, channel, command, 2, RotelCommand.BASS_UP, RotelCommand.BASS_DOWN,
808 RotelCommand.BASS_SET);
812 case CHANNEL_MAIN_TREBLE:
815 logger.debug("Command {} from channel {} ignored: device in standby", command, channel);
817 handleToneCmd(treble, channel, command, 1, RotelCommand.TREBLE_UP, RotelCommand.TREBLE_DOWN,
818 RotelCommand.TREBLE_SET);
821 case CHANNEL_PLAY_CONTROL:
824 logger.debug("Command {} from channel {} ignored: device in standby", command, channel);
825 } else if (command instanceof PlayPauseType && command == PlayPauseType.PLAY) {
826 connector.sendCommand(RotelCommand.PLAY);
827 } else if (command instanceof PlayPauseType && command == PlayPauseType.PAUSE) {
828 connector.sendCommand(RotelCommand.PAUSE);
829 if (connector.getProtocol() == RotelProtocol.ASCII_V1
830 && connector.getModel() != RotelModel.RCD1570
831 && connector.getModel() != RotelModel.RCD1572
832 && connector.getModel() != RotelModel.RCX1500) {
834 connector.sendCommand(RotelCommand.PLAY_STATUS);
836 } else if (command instanceof NextPreviousType && command == NextPreviousType.NEXT) {
837 connector.sendCommand(RotelCommand.TRACK_FORWARD);
838 } else if (command instanceof NextPreviousType && command == NextPreviousType.PREVIOUS) {
839 connector.sendCommand(RotelCommand.TRACK_BACKWORD);
842 logger.debug("Command {} from channel {} failed: invalid command value", command, channel);
845 case CHANNEL_BRIGHTNESS:
848 logger.debug("Command {} from channel {} ignored: device in standby", command, channel);
849 } else if (!connector.getModel().hasDimmerControl()) {
851 logger.debug("Command {} from channel {} failed: unavailable feature", command, channel);
852 } else if (command instanceof PercentType) {
853 int dimmer = (int) Math.round(((PercentType) command).doubleValue() / 100.0
854 * (connector.getModel().getDimmerLevelMax()
855 - connector.getModel().getDimmerLevelMin()))
856 + connector.getModel().getDimmerLevelMin();
857 connector.sendCommand(RotelCommand.DIMMER_LEVEL_SET, dimmer);
860 logger.debug("Command {} from channel {} failed: invalid command value", command, channel);
865 logger.debug("Command {} from channel {} failed: nnexpected command", command, channel);
869 logger.debug("Command {} from channel {} succeeded", command, channel);
871 updateChannelState(channel);
873 } catch (RotelException e) {
874 logger.debug("Command {} from channel {} failed: {}", command, channel, e.getMessage());
875 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
876 "@text/offline.comm-error-sending-command");
878 scheduleReconnectJob();
879 } catch (InterruptedException e) {
880 logger.debug("Command {} from channel {} interrupted: {}", command, channel, e.getMessage());
881 Thread.currentThread().interrupt();
887 * Handle a power ON/OFF command
889 * @param channel the channel
890 * @param command the received channel command (OnOffType)
891 * @param onCmd the command to be sent to the device to power it ON
892 * @param offCmd the command to be sent to the device to power it OFF
894 * @throws RotelException in case of communication error with the device
896 private void handlePowerCmd(String channel, Command command, RotelCommand onCmd, RotelCommand offCmd)
897 throws RotelException {
898 if (command instanceof OnOffType && command == OnOffType.ON) {
899 connector.sendCommand(onCmd);
900 } else if (command instanceof OnOffType && command == OnOffType.OFF) {
901 connector.sendCommand(offCmd);
903 logger.debug("Command {} from channel {} failed: invalid command value", command, channel);
908 * Handle a volume command
910 * @param current the current volume
911 * @param channel the channel
912 * @param command the received channel command (IncreaseDecreaseType or DecimalType)
913 * @param upCmd the command to be sent to the device to increase the volume
914 * @param downCmd the command to be sent to the device to decrease the volume
915 * @param setCmd the command to be sent to the device to set the volume at a value
917 * @throws RotelException in case of communication error with the device
919 private void handleVolumeCmd(int current, String channel, Command command, RotelCommand upCmd, RotelCommand downCmd,
920 @Nullable RotelCommand setCmd) throws RotelException {
921 if (command instanceof IncreaseDecreaseType && command == IncreaseDecreaseType.INCREASE) {
922 connector.sendCommand(upCmd);
923 } else if (command instanceof IncreaseDecreaseType && command == IncreaseDecreaseType.DECREASE) {
924 connector.sendCommand(downCmd);
925 } else if (command instanceof DecimalType && setCmd == null) {
926 int value = ((DecimalType) command).intValue();
927 if (value >= minVolume && value <= maxVolume) {
928 if (value > current) {
929 connector.sendCommand(upCmd);
930 } else if (value < current) {
931 connector.sendCommand(downCmd);
934 } else if (command instanceof PercentType && setCmd != null) {
935 int value = (int) Math.round(((PercentType) command).doubleValue() / 100.0 * (maxVolume - minVolume))
937 connector.sendCommand(setCmd, value);
939 logger.debug("Command {} from channel {} failed: invalid command value", command, channel);
944 * Handle a mute command
946 * @param onlyToggle true if only the toggle command must be used
947 * @param channel the channel
948 * @param command the received channel command (OnOffType)
949 * @param onCmd the command to be sent to the device to mute
950 * @param offCmd the command to be sent to the device to unmute
951 * @param toggleCmd the command to be sent to the device to toggle the mute state
953 * @throws RotelException in case of communication error with the device
955 private void handleMuteCmd(boolean onlyToggle, String channel, Command command, RotelCommand onCmd,
956 RotelCommand offCmd, RotelCommand toggleCmd) throws RotelException {
957 if (command instanceof OnOffType) {
959 connector.sendCommand(toggleCmd);
960 } else if (command == OnOffType.ON) {
961 connector.sendCommand(onCmd);
962 } else if (command == OnOffType.OFF) {
963 connector.sendCommand(offCmd);
966 logger.debug("Command {} from channel {} failed: invalid command value", command, channel);
971 * Handle a tone level adjustment command (bass or treble)
973 * @param current the current tone level
974 * @param channel the channel
975 * @param command the received channel command (IncreaseDecreaseType or DecimalType)
976 * @param nbSelect the number of TONE_CONTROL_SELECT commands to be run to display the right tone (bass or treble)
977 * @param upCmd the command to be sent to the device to increase the tone level
978 * @param downCmd the command to be sent to the device to decrease the tone level
979 * @param setCmd the command to be sent to the device to set the tone level at a value
981 * @throws RotelException in case of communication error with the device
982 * @throws InterruptedException in case of interruption during a thread sleep
984 private void handleToneCmd(int current, String channel, Command command, int nbSelect, RotelCommand upCmd,
985 RotelCommand downCmd, RotelCommand setCmd) throws RotelException, InterruptedException {
986 if (command instanceof IncreaseDecreaseType && command == IncreaseDecreaseType.INCREASE) {
987 selectToneControl(nbSelect);
988 connector.sendCommand(upCmd);
989 } else if (command instanceof IncreaseDecreaseType && command == IncreaseDecreaseType.DECREASE) {
990 selectToneControl(nbSelect);
991 connector.sendCommand(downCmd);
992 } else if (command instanceof DecimalType) {
993 int value = ((DecimalType) command).intValue();
994 if (value >= minToneLevel && value <= maxToneLevel) {
995 if (connector.getProtocol() != RotelProtocol.HEX) {
996 connector.sendCommand(setCmd, value);
997 } else if (value > current) {
998 selectToneControl(nbSelect);
999 connector.sendCommand(upCmd);
1000 } else if (value < current) {
1001 selectToneControl(nbSelect);
1002 connector.sendCommand(downCmd);
1006 logger.debug("Command {} from channel {} failed: invalid command value", command, channel);
1011 * Run a sequence of commands to display the current tone level (bass or treble) on the device front panel
1013 * @param nbSelect the number of TONE_CONTROL_SELECT commands to be run to display the right tone (bass or treble)
1015 * @throws RotelException in case of communication error with the device
1016 * @throws InterruptedException in case of interruption during a thread sleep
1018 private void selectToneControl(int nbSelect) throws RotelException, InterruptedException {
1019 // No tone control select command for RSX-1065
1020 if (connector.getProtocol() == RotelProtocol.HEX && connector.getModel() != RotelModel.RSX1065) {
1021 selectFeature(nbSelect, RotelCommand.RECORD_FONCTION_SELECT, RotelCommand.TONE_CONTROL_SELECT);
1026 * Run a sequence of commands to display a particular zone on the device front panel
1028 * @param zone the zone to be displayed (1 for main zone)
1029 * @param selectCommand the command to be sent to the device to switch the display between zones
1031 * @throws RotelException in case of communication error with the device
1032 * @throws InterruptedException in case of interruption during a thread sleep
1034 private void selectZone(int zone, @Nullable RotelCommand selectCommand)
1035 throws RotelException, InterruptedException {
1036 if (connector.getProtocol() == RotelProtocol.HEX && connector.getModel().getNbAdditionalZones() >= 1
1037 && zone >= 1 && zone != currentZone && selectCommand != null) {
1039 if (zone < currentZone) {
1040 nbSelect = zone + connector.getModel().getNbAdditionalZones() - currentZone;
1041 if (isPowerOn() && selectCommand == RotelCommand.RECORD_FONCTION_SELECT) {
1045 nbSelect = zone - currentZone;
1046 if (isPowerOn() && currentZone == 1 && selectCommand == RotelCommand.RECORD_FONCTION_SELECT
1047 && !selectingRecord) {
1051 selectFeature(nbSelect, null, selectCommand);
1056 * Run a sequence of commands to display a particular feature on the device front panel
1058 * @param nbSelect the number of select commands to be run
1059 * @param preCmd the initial command to be sent to the device (before the select commands)
1060 * @param selectCmd the select command to be sent to the device
1062 * @throws RotelException in case of communication error with the device
1063 * @throws InterruptedException in case of interruption during a thread sleep
1065 private void selectFeature(int nbSelect, @Nullable RotelCommand preCmd, RotelCommand selectCmd)
1066 throws RotelException, InterruptedException {
1067 if (connector.getProtocol() == RotelProtocol.HEX) {
1068 if (preCmd != null) {
1069 connector.sendCommand(preCmd);
1072 for (int i = 1; i <= nbSelect; i++) {
1073 connector.sendCommand(selectCmd);
1080 * Open the connection with the Rotel device
1082 * @return true if the connection is opened successfully or flase if not
1084 private synchronized boolean openConnection() {
1085 connector.addEventListener(this);
1088 } catch (RotelException e) {
1089 logger.debug("openConnection() failed", e);
1091 logger.debug("openConnection(): {}", connector.isConnected() ? "connected" : "disconnected");
1092 return connector.isConnected();
1096 * Close the connection with the Rotel device
1098 private synchronized void closeConnection() {
1100 connector.removeEventListener(this);
1101 logger.debug("closeConnection(): disconnected");
1105 public void onNewMessageEvent(EventObject event) {
1106 cancelPowerOffJob();
1108 RotelMessageEvent evt = (RotelMessageEvent) event;
1109 logger.debug("onNewMessageEvent: key {} = {}", evt.getKey(), evt.getValue());
1111 String key = evt.getKey();
1112 String value = evt.getValue().trim();
1113 if (!RotelConnector.KEY_ERROR.equals(key)) {
1114 updateStatus(ThingStatus.ONLINE);
1118 case RotelConnector.KEY_ERROR:
1119 logger.debug("Reading feedback message failed");
1120 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
1121 "@text/offline.comm-error-reading-thread");
1124 case RotelConnector.KEY_LINE1:
1125 frontPanelLine1 = value;
1126 updateChannelState(CHANNEL_LINE1);
1128 case RotelConnector.KEY_LINE2:
1129 frontPanelLine2 = value;
1130 updateChannelState(CHANNEL_LINE2);
1132 case RotelConnector.KEY_ZONE:
1133 currentZone = Integer.parseInt(value);
1135 case RotelConnector.KEY_RECORD_SEL:
1136 selectingRecord = RotelConnector.MSG_VALUE_ON.equalsIgnoreCase(value);
1138 case RotelConnector.KEY_POWER:
1139 if (RotelConnector.POWER_ON.equalsIgnoreCase(value)) {
1141 } else if (RotelConnector.STANDBY.equalsIgnoreCase(value)) {
1143 } else if (RotelConnector.POWER_OFF_DELAYED.equalsIgnoreCase(value)) {
1144 schedulePowerOffJob(false);
1146 throw new RotelException("Invalid value");
1149 case RotelConnector.KEY_POWER_ZONE2:
1150 if (RotelConnector.POWER_ON.equalsIgnoreCase(value)) {
1151 handlePowerOnZone2();
1152 } else if (RotelConnector.STANDBY.equalsIgnoreCase(value)) {
1153 handlePowerOffZone2();
1155 throw new RotelException("Invalid value");
1158 case RotelConnector.KEY_POWER_ZONE3:
1159 if (RotelConnector.POWER_ON.equalsIgnoreCase(value)) {
1160 handlePowerOnZone3();
1161 } else if (RotelConnector.STANDBY.equalsIgnoreCase(value)) {
1162 handlePowerOffZone3();
1164 throw new RotelException("Invalid value");
1167 case RotelConnector.KEY_POWER_ZONE4:
1168 if (RotelConnector.POWER_ON.equalsIgnoreCase(value)) {
1169 handlePowerOnZone4();
1170 } else if (RotelConnector.STANDBY.equalsIgnoreCase(value)) {
1171 handlePowerOffZone4();
1173 throw new RotelException("Invalid value");
1176 case RotelConnector.KEY_VOLUME_MIN:
1177 minVolume = Integer.parseInt(value);
1178 if (!connector.getModel().hasDirectVolumeControl()) {
1179 logger.info("Set minValue to {} for your sitemap widget attached to your volume item.",
1183 case RotelConnector.KEY_VOLUME_MAX:
1184 maxVolume = Integer.parseInt(value);
1185 if (!connector.getModel().hasDirectVolumeControl()) {
1186 logger.info("Set maxValue to {} for your sitemap widget attached to your volume item.",
1190 case RotelConnector.KEY_VOLUME:
1191 if (RotelConnector.MSG_VALUE_MIN.equalsIgnoreCase(value)) {
1193 } else if (RotelConnector.MSG_VALUE_MAX.equalsIgnoreCase(value)) {
1196 volume = Integer.parseInt(value);
1198 updateChannelState(CHANNEL_VOLUME);
1199 updateChannelState(CHANNEL_MAIN_VOLUME);
1200 updateChannelState(CHANNEL_MAIN_VOLUME_UP_DOWN);
1202 case RotelConnector.KEY_MUTE:
1203 if (RotelConnector.MSG_VALUE_ON.equalsIgnoreCase(value)) {
1205 updateChannelState(CHANNEL_MUTE);
1206 updateChannelState(CHANNEL_MAIN_MUTE);
1207 } else if (RotelConnector.MSG_VALUE_OFF.equalsIgnoreCase(value)) {
1209 updateChannelState(CHANNEL_MUTE);
1210 updateChannelState(CHANNEL_MAIN_MUTE);
1212 throw new RotelException("Invalid value");
1215 case RotelConnector.KEY_VOLUME_ZONE2:
1216 fixedVolumeZone2 = false;
1217 if (RotelConnector.MSG_VALUE_FIX.equalsIgnoreCase(value)) {
1218 fixedVolumeZone2 = true;
1219 } else if (RotelConnector.MSG_VALUE_MIN.equalsIgnoreCase(value)) {
1220 volumeZone2 = minVolume;
1221 } else if (RotelConnector.MSG_VALUE_MAX.equalsIgnoreCase(value)) {
1222 volumeZone2 = maxVolume;
1224 volumeZone2 = Integer.parseInt(value);
1226 updateChannelState(CHANNEL_ZONE2_VOLUME);
1227 updateChannelState(CHANNEL_ZONE2_VOLUME_UP_DOWN);
1229 case RotelConnector.KEY_VOLUME_ZONE3:
1230 fixedVolumeZone3 = false;
1231 if (RotelConnector.MSG_VALUE_FIX.equalsIgnoreCase(value)) {
1232 fixedVolumeZone3 = true;
1233 } else if (RotelConnector.MSG_VALUE_MIN.equalsIgnoreCase(value)) {
1234 volumeZone3 = minVolume;
1235 } else if (RotelConnector.MSG_VALUE_MAX.equalsIgnoreCase(value)) {
1236 volumeZone3 = maxVolume;
1238 volumeZone3 = Integer.parseInt(value);
1240 updateChannelState(CHANNEL_ZONE3_VOLUME);
1242 case RotelConnector.KEY_VOLUME_ZONE4:
1243 fixedVolumeZone4 = false;
1244 if (RotelConnector.MSG_VALUE_FIX.equalsIgnoreCase(value)) {
1245 fixedVolumeZone4 = true;
1246 } else if (RotelConnector.MSG_VALUE_MIN.equalsIgnoreCase(value)) {
1247 volumeZone4 = minVolume;
1248 } else if (RotelConnector.MSG_VALUE_MAX.equalsIgnoreCase(value)) {
1249 volumeZone4 = maxVolume;
1251 volumeZone4 = Integer.parseInt(value);
1253 updateChannelState(CHANNEL_ZONE4_VOLUME);
1255 case RotelConnector.KEY_MUTE_ZONE2:
1256 if (RotelConnector.MSG_VALUE_ON.equalsIgnoreCase(value)) {
1258 updateChannelState(CHANNEL_ZONE2_MUTE);
1259 } else if (RotelConnector.MSG_VALUE_OFF.equalsIgnoreCase(value)) {
1261 updateChannelState(CHANNEL_ZONE2_MUTE);
1263 throw new RotelException("Invalid value");
1266 case RotelConnector.KEY_MUTE_ZONE3:
1267 if (RotelConnector.MSG_VALUE_ON.equalsIgnoreCase(value)) {
1269 updateChannelState(CHANNEL_ZONE3_MUTE);
1270 } else if (RotelConnector.MSG_VALUE_OFF.equalsIgnoreCase(value)) {
1272 updateChannelState(CHANNEL_ZONE3_MUTE);
1274 throw new RotelException("Invalid value");
1277 case RotelConnector.KEY_MUTE_ZONE4:
1278 if (RotelConnector.MSG_VALUE_ON.equalsIgnoreCase(value)) {
1280 updateChannelState(CHANNEL_ZONE4_MUTE);
1281 } else if (RotelConnector.MSG_VALUE_OFF.equalsIgnoreCase(value)) {
1283 updateChannelState(CHANNEL_ZONE4_MUTE);
1285 throw new RotelException("Invalid value");
1288 case RotelConnector.KEY_TONE_MAX:
1289 maxToneLevel = Integer.parseInt(value);
1290 minToneLevel = -maxToneLevel;
1292 "Set minValue to {} and maxValue to {} for your sitemap widget attached to your bass or treble item.",
1293 minToneLevel, maxToneLevel);
1295 case RotelConnector.KEY_BASS:
1296 if (RotelConnector.MSG_VALUE_MIN.equalsIgnoreCase(value)) {
1297 bass = minToneLevel;
1298 } else if (RotelConnector.MSG_VALUE_MAX.equalsIgnoreCase(value)) {
1299 bass = maxToneLevel;
1301 bass = Integer.parseInt(value);
1303 updateChannelState(CHANNEL_BASS);
1304 updateChannelState(CHANNEL_MAIN_BASS);
1306 case RotelConnector.KEY_TREBLE:
1307 if (RotelConnector.MSG_VALUE_MIN.equalsIgnoreCase(value)) {
1308 treble = minToneLevel;
1309 } else if (RotelConnector.MSG_VALUE_MAX.equalsIgnoreCase(value)) {
1310 treble = maxToneLevel;
1312 treble = Integer.parseInt(value);
1314 updateChannelState(CHANNEL_TREBLE);
1315 updateChannelState(CHANNEL_MAIN_TREBLE);
1317 case RotelConnector.KEY_SOURCE:
1318 source = connector.getModel().getSourceFromCommand(RotelCommand.getFromAsciiCommand(value));
1319 updateChannelState(CHANNEL_SOURCE);
1320 updateChannelState(CHANNEL_MAIN_SOURCE);
1322 case RotelConnector.KEY_RECORD:
1323 recordSource = connector.getModel()
1324 .getRecordSourceFromCommand(RotelCommand.getFromAsciiCommand(value));
1325 updateChannelState(CHANNEL_MAIN_RECORD_SOURCE);
1327 case RotelConnector.KEY_SOURCE_ZONE2:
1328 sourceZone2 = connector.getModel()
1329 .getZone2SourceFromCommand(RotelCommand.getFromAsciiCommand(value));
1330 updateChannelState(CHANNEL_ZONE2_SOURCE);
1332 case RotelConnector.KEY_SOURCE_ZONE3:
1333 sourceZone3 = connector.getModel()
1334 .getZone3SourceFromCommand(RotelCommand.getFromAsciiCommand(value));
1335 updateChannelState(CHANNEL_ZONE3_SOURCE);
1337 case RotelConnector.KEY_SOURCE_ZONE4:
1338 sourceZone4 = connector.getModel()
1339 .getZone4SourceFromCommand(RotelCommand.getFromAsciiCommand(value));
1340 updateChannelState(CHANNEL_ZONE4_SOURCE);
1342 case RotelConnector.KEY_DSP_MODE:
1343 if ("dolby_pliix_movie".equals(value)) {
1344 value = "dolby_plii_movie";
1345 } else if ("dolby_pliix_music".equals(value)) {
1346 value = "dolby_plii_music";
1347 } else if ("dolby_pliix_game".equals(value)) {
1348 value = "dolby_plii_game";
1350 dsp = connector.getModel().getDspFromFeedback(value);
1351 logger.debug("DSP {}", dsp.getName());
1352 updateChannelState(CHANNEL_DSP);
1353 updateChannelState(CHANNEL_MAIN_DSP);
1355 case RotelConnector.KEY1_PLAY_STATUS:
1356 case RotelConnector.KEY2_PLAY_STATUS:
1357 if (RotelConnector.PLAY.equalsIgnoreCase(value)) {
1358 playStatus = RotelPlayStatus.PLAYING;
1359 updateChannelState(CHANNEL_PLAY_CONTROL);
1360 } else if (RotelConnector.PAUSE.equalsIgnoreCase(value)) {
1361 playStatus = RotelPlayStatus.PAUSED;
1362 updateChannelState(CHANNEL_PLAY_CONTROL);
1363 } else if (RotelConnector.STOP.equalsIgnoreCase(value)) {
1364 playStatus = RotelPlayStatus.STOPPED;
1365 updateChannelState(CHANNEL_PLAY_CONTROL);
1367 throw new RotelException("Invalid value");
1370 case RotelConnector.KEY_TRACK:
1371 if (source.getName().equals("CD") && !connector.getModel().hasSourceControl()) {
1372 track = Integer.parseInt(value);
1373 updateChannelState(CHANNEL_TRACK);
1376 case RotelConnector.KEY_FREQ:
1377 if (RotelConnector.MSG_VALUE_OFF.equalsIgnoreCase(value)) {
1380 // Suppress a potential ending "k" or "K"
1381 if (value.toUpperCase().endsWith("K")) {
1382 value = value.substring(0, value.length() - 1);
1384 frequency = Double.parseDouble(value);
1386 updateChannelState(CHANNEL_FREQUENCY);
1388 case RotelConnector.KEY_DIMMER:
1389 brightness = Integer.parseInt(value);
1390 updateChannelState(CHANNEL_BRIGHTNESS);
1392 case RotelConnector.KEY_UPDATE_MODE:
1393 case RotelConnector.KEY_DISPLAY_UPDATE:
1396 logger.debug("onNewMessageEvent: unhandled key {}", key);
1399 } catch (NumberFormatException | RotelException e) {
1400 logger.debug("Invalid value {} for key {}", value, key);
1405 * Handle the received information that device power (main zone) is ON
1407 private void handlePowerOn() {
1408 Boolean prev = power;
1410 updateChannelState(CHANNEL_POWER);
1411 updateChannelState(CHANNEL_MAIN_POWER);
1412 if ((prev == null) || !prev) {
1413 schedulePowerOnJob();
1418 * Handle the received information that device power (main zone) is OFF
1420 private void handlePowerOff() {
1423 updateChannelState(CHANNEL_POWER);
1424 updateChannelState(CHANNEL_MAIN_POWER);
1425 updateChannelState(CHANNEL_SOURCE);
1426 updateChannelState(CHANNEL_MAIN_SOURCE);
1427 updateChannelState(CHANNEL_MAIN_RECORD_SOURCE);
1428 updateChannelState(CHANNEL_DSP);
1429 updateChannelState(CHANNEL_MAIN_DSP);
1430 updateChannelState(CHANNEL_VOLUME);
1431 updateChannelState(CHANNEL_MAIN_VOLUME);
1432 updateChannelState(CHANNEL_MAIN_VOLUME_UP_DOWN);
1433 updateChannelState(CHANNEL_MUTE);
1434 updateChannelState(CHANNEL_MAIN_MUTE);
1435 updateChannelState(CHANNEL_BASS);
1436 updateChannelState(CHANNEL_MAIN_BASS);
1437 updateChannelState(CHANNEL_TREBLE);
1438 updateChannelState(CHANNEL_MAIN_TREBLE);
1439 updateChannelState(CHANNEL_PLAY_CONTROL);
1440 updateChannelState(CHANNEL_TRACK);
1441 updateChannelState(CHANNEL_FREQUENCY);
1442 updateChannelState(CHANNEL_BRIGHTNESS);
1446 * Handle the received information that zone 2 power is ON
1448 private void handlePowerOnZone2() {
1449 boolean prev = powerZone2;
1451 updateChannelState(CHANNEL_ZONE2_POWER);
1453 schedulePowerOnZone2Job();
1458 * Handle the received information that zone 2 power is OFF
1460 private void handlePowerOffZone2() {
1461 cancelPowerOnZone2Job();
1463 updateChannelState(CHANNEL_ZONE2_POWER);
1464 updateChannelState(CHANNEL_ZONE2_SOURCE);
1465 updateChannelState(CHANNEL_ZONE2_VOLUME);
1466 updateChannelState(CHANNEL_ZONE2_VOLUME_UP_DOWN);
1467 updateChannelState(CHANNEL_ZONE2_MUTE);
1471 * Handle the received information that zone 3 power is ON
1473 private void handlePowerOnZone3() {
1474 boolean prev = powerZone3;
1476 updateChannelState(CHANNEL_ZONE3_POWER);
1478 schedulePowerOnZone3Job();
1483 * Handle the received information that zone 3 power is OFF
1485 private void handlePowerOffZone3() {
1486 cancelPowerOnZone3Job();
1488 updateChannelState(CHANNEL_ZONE3_POWER);
1489 updateChannelState(CHANNEL_ZONE3_SOURCE);
1490 updateChannelState(CHANNEL_ZONE3_VOLUME);
1491 updateChannelState(CHANNEL_ZONE3_MUTE);
1495 * Handle the received information that zone 4 power is ON
1497 private void handlePowerOnZone4() {
1498 boolean prev = powerZone4;
1500 updateChannelState(CHANNEL_ZONE4_POWER);
1502 schedulePowerOnZone4Job();
1507 * Handle the received information that zone 4 power is OFF
1509 private void handlePowerOffZone4() {
1510 cancelPowerOnZone4Job();
1512 updateChannelState(CHANNEL_ZONE4_POWER);
1513 updateChannelState(CHANNEL_ZONE4_SOURCE);
1514 updateChannelState(CHANNEL_ZONE4_VOLUME);
1515 updateChannelState(CHANNEL_ZONE4_MUTE);
1519 * Schedule the job that will consider the device as OFF if no new event is received before its running
1521 * @param switchOffAllZones true if all zones have to be considered as OFF
1523 private void schedulePowerOffJob(boolean switchOffAllZones) {
1524 logger.debug("Schedule power OFF job");
1525 cancelPowerOffJob();
1526 powerOffJob = scheduler.schedule(() -> {
1527 logger.debug("Power OFF job");
1529 if (switchOffAllZones) {
1530 handlePowerOffZone2();
1531 handlePowerOffZone3();
1532 handlePowerOffZone4();
1534 }, 2000, TimeUnit.MILLISECONDS);
1538 * Cancel the job that will consider the device as OFF
1540 private void cancelPowerOffJob() {
1541 ScheduledFuture<?> powerOffJob = this.powerOffJob;
1542 if (powerOffJob != null && !powerOffJob.isCancelled()) {
1543 powerOffJob.cancel(true);
1544 this.powerOffJob = null;
1549 * Schedule the job to run with a few seconds delay when the device power (main zone) switched ON
1551 private void schedulePowerOnJob() {
1552 logger.debug("Schedule power ON job");
1554 powerOnJob = scheduler.schedule(() -> {
1555 synchronized (sequenceLock) {
1556 logger.debug("Power ON job");
1558 switch (connector.getProtocol()) {
1560 if (connector.getModel().getRespNbChars() <= 13
1561 && connector.getModel().hasVolumeControl()) {
1562 connector.sendCommand(getVolumeDownCommand());
1564 connector.sendCommand(getVolumeUpCommand());
1567 if (connector.getModel().getNbAdditionalZones() >= 1) {
1568 if (currentZone != 1 && connector.getModel()
1569 .getZoneSelectCmd() == RotelCommand.RECORD_FONCTION_SELECT) {
1570 selectZone(1, connector.getModel().getZoneSelectCmd());
1571 } else if (!selectingRecord) {
1572 connector.sendCommand(RotelCommand.RECORD_FONCTION_SELECT);
1576 connector.sendCommand(RotelCommand.RECORD_FONCTION_SELECT);
1579 if (connector.getModel().hasToneControl()) {
1580 if (connector.getModel() == RotelModel.RSX1065) {
1581 // No tone control select command
1582 connector.sendCommand(RotelCommand.TREBLE_DOWN);
1584 connector.sendCommand(RotelCommand.TREBLE_UP);
1586 connector.sendCommand(RotelCommand.BASS_DOWN);
1588 connector.sendCommand(RotelCommand.BASS_UP);
1591 selectFeature(2, null, RotelCommand.TONE_CONTROL_SELECT);
1596 if (connector.getModel() != RotelModel.RAP1580 && connector.getModel() != RotelModel.RDD1580
1597 && connector.getModel() != RotelModel.RSP1576
1598 && connector.getModel() != RotelModel.RSP1582) {
1599 connector.sendCommand(RotelCommand.UPDATE_AUTO);
1602 if (connector.getModel().hasSourceControl()) {
1603 connector.sendCommand(RotelCommand.SOURCE);
1606 if (connector.getModel().hasVolumeControl() || connector.getModel().hasToneControl()) {
1607 if (connector.getModel().hasVolumeControl()
1608 && connector.getModel() != RotelModel.RAP1580
1609 && connector.getModel() != RotelModel.RSP1576
1610 && connector.getModel() != RotelModel.RSP1582) {
1611 connector.sendCommand(RotelCommand.VOLUME_GET_MIN);
1613 connector.sendCommand(RotelCommand.VOLUME_GET_MAX);
1616 if (connector.getModel().hasToneControl()) {
1617 connector.sendCommand(RotelCommand.TONE_MAX);
1620 // Wait enough to be sure to get the min/max values requested just before
1622 if (connector.getModel().hasVolumeControl()) {
1623 connector.sendCommand(RotelCommand.VOLUME_GET);
1625 if (connector.getModel() != RotelModel.RA11
1626 && connector.getModel() != RotelModel.RA12
1627 && connector.getModel() != RotelModel.RCX1500) {
1628 connector.sendCommand(RotelCommand.MUTE);
1632 if (connector.getModel().hasToneControl()) {
1633 connector.sendCommand(RotelCommand.BASS);
1635 connector.sendCommand(RotelCommand.TREBLE);
1639 if (connector.getModel().hasPlayControl()) {
1640 if (connector.getModel() != RotelModel.RCD1570
1641 && connector.getModel() != RotelModel.RCD1572
1642 && (connector.getModel() != RotelModel.RCX1500
1643 || !source.getName().equals("CD"))) {
1644 connector.sendCommand(RotelCommand.PLAY_STATUS);
1647 connector.sendCommand(RotelCommand.CD_PLAY_STATUS);
1651 if (connector.getModel().hasDspControl()) {
1652 connector.sendCommand(RotelCommand.DSP_MODE);
1655 if (connector.getModel().canGetFrequency()) {
1656 connector.sendCommand(RotelCommand.FREQUENCY);
1659 if (connector.getModel().hasDimmerControl() && connector.getModel().canGetDimmerLevel()) {
1660 connector.sendCommand(RotelCommand.DIMMER_LEVEL_GET);
1665 connector.sendCommand(RotelCommand.UPDATE_AUTO);
1667 if (connector.getModel().hasSourceControl()) {
1668 connector.sendCommand(RotelCommand.SOURCE);
1671 if (connector.getModel().hasVolumeControl()) {
1672 connector.sendCommand(RotelCommand.VOLUME_GET);
1674 connector.sendCommand(RotelCommand.MUTE);
1677 if (connector.getModel().hasToneControl()) {
1678 connector.sendCommand(RotelCommand.BASS);
1680 connector.sendCommand(RotelCommand.TREBLE);
1683 if (connector.getModel().hasPlayControl()) {
1684 connector.sendCommand(RotelCommand.PLAY_STATUS);
1686 if (source.getName().equals("CD") && !connector.getModel().hasSourceControl()) {
1687 connector.sendCommand(RotelCommand.TRACK);
1691 if (connector.getModel().hasDspControl()) {
1692 connector.sendCommand(RotelCommand.DSP_MODE);
1695 if (connector.getModel().canGetFrequency()) {
1696 connector.sendCommand(RotelCommand.FREQUENCY);
1699 if (connector.getModel().hasDimmerControl() && connector.getModel().canGetDimmerLevel()) {
1700 connector.sendCommand(RotelCommand.DIMMER_LEVEL_GET);
1705 } catch (RotelException e) {
1706 logger.debug("Init sequence failed: {}", e.getMessage());
1707 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
1708 "@text/offline.comm-error-init-sequence");
1710 } catch (InterruptedException e) {
1711 logger.debug("Init sequence interrupted: {}", e.getMessage());
1712 Thread.currentThread().interrupt();
1715 }, 2500, TimeUnit.MILLISECONDS);
1719 * Cancel the job scheduled when the device power (main zone) switched ON
1721 private void cancelPowerOnJob() {
1722 ScheduledFuture<?> powerOnJob = this.powerOnJob;
1723 if (powerOnJob != null && !powerOnJob.isCancelled()) {
1724 powerOnJob.cancel(true);
1725 this.powerOnJob = null;
1730 * Schedule the job to run with a few seconds delay when the zone 2 power switched ON
1732 private void schedulePowerOnZone2Job() {
1733 logger.debug("Schedule power ON zone 2 job");
1734 cancelPowerOnZone2Job();
1735 powerOnZone2Job = scheduler.schedule(() -> {
1736 synchronized (sequenceLock) {
1737 logger.debug("Power ON zone 2 job");
1739 if (connector.getProtocol() == RotelProtocol.HEX
1740 && connector.getModel().getNbAdditionalZones() >= 1) {
1741 selectZone(2, connector.getModel().getZoneSelectCmd());
1742 connector.sendCommand(connector.getModel().hasZone2Commands() ? RotelCommand.ZONE2_VOLUME_DOWN
1743 : RotelCommand.VOLUME_DOWN);
1745 connector.sendCommand(connector.getModel().hasZone2Commands() ? RotelCommand.ZONE2_VOLUME_UP
1746 : RotelCommand.VOLUME_UP);
1749 } catch (RotelException e) {
1750 logger.debug("Init sequence zone 2 failed: {}", e.getMessage());
1751 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
1752 "@text/offline.comm-error-init-sequence-zone [\"2\"]");
1754 } catch (InterruptedException e) {
1755 logger.debug("Init sequence zone 2 interrupted: {}", e.getMessage());
1756 Thread.currentThread().interrupt();
1759 }, 2500, TimeUnit.MILLISECONDS);
1763 * Cancel the job scheduled when the zone 2 power switched ON
1765 private void cancelPowerOnZone2Job() {
1766 ScheduledFuture<?> powerOnZone2Job = this.powerOnZone2Job;
1767 if (powerOnZone2Job != null && !powerOnZone2Job.isCancelled()) {
1768 powerOnZone2Job.cancel(true);
1769 this.powerOnZone2Job = null;
1774 * Schedule the job to run with a few seconds delay when the zone 3 power switched ON
1776 private void schedulePowerOnZone3Job() {
1777 logger.debug("Schedule power ON zone 3 job");
1778 cancelPowerOnZone3Job();
1779 powerOnZone3Job = scheduler.schedule(() -> {
1780 synchronized (sequenceLock) {
1781 logger.debug("Power ON zone 3 job");
1783 if (connector.getProtocol() == RotelProtocol.HEX
1784 && connector.getModel().getNbAdditionalZones() >= 2) {
1785 selectZone(3, connector.getModel().getZoneSelectCmd());
1786 connector.sendCommand(connector.getModel().hasZone3Commands() ? RotelCommand.ZONE3_VOLUME_DOWN
1787 : RotelCommand.VOLUME_DOWN);
1789 connector.sendCommand(connector.getModel().hasZone3Commands() ? RotelCommand.ZONE3_VOLUME_UP
1790 : RotelCommand.VOLUME_UP);
1793 } catch (RotelException e) {
1794 logger.debug("Init sequence zone 3 failed: {}", e.getMessage());
1795 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
1796 "@text/offline.comm-error-init-sequence-zone [\"3\"]");
1798 } catch (InterruptedException e) {
1799 logger.debug("Init sequence zone 3 interrupted: {}", e.getMessage());
1800 Thread.currentThread().interrupt();
1803 }, 2500, TimeUnit.MILLISECONDS);
1807 * Cancel the job scheduled when the zone 3 power switched ON
1809 private void cancelPowerOnZone3Job() {
1810 ScheduledFuture<?> powerOnZone3Job = this.powerOnZone3Job;
1811 if (powerOnZone3Job != null && !powerOnZone3Job.isCancelled()) {
1812 powerOnZone3Job.cancel(true);
1813 this.powerOnZone3Job = null;
1818 * Schedule the job to run with a few seconds delay when the zone 4 power switched ON
1820 private void schedulePowerOnZone4Job() {
1821 logger.debug("Schedule power ON zone 4 job");
1822 cancelPowerOnZone4Job();
1823 powerOnZone4Job = scheduler.schedule(() -> {
1824 synchronized (sequenceLock) {
1825 logger.debug("Power ON zone 4 job");
1827 if (connector.getProtocol() == RotelProtocol.HEX
1828 && connector.getModel().getNbAdditionalZones() >= 3) {
1829 selectZone(4, connector.getModel().getZoneSelectCmd());
1830 connector.sendCommand(connector.getModel().hasZone4Commands() ? RotelCommand.ZONE4_VOLUME_DOWN
1831 : RotelCommand.VOLUME_DOWN);
1833 connector.sendCommand(connector.getModel().hasZone4Commands() ? RotelCommand.ZONE4_VOLUME_UP
1834 : RotelCommand.VOLUME_UP);
1837 } catch (RotelException e) {
1838 logger.debug("Init sequence zone 4 failed: {}", e.getMessage());
1839 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
1840 "@text/offline.comm-error-init-sequence-zone [\"4\"]");
1842 } catch (InterruptedException e) {
1843 logger.debug("Init sequence zone 4 interrupted: {}", e.getMessage());
1844 Thread.currentThread().interrupt();
1847 }, 2500, TimeUnit.MILLISECONDS);
1851 * Cancel the job scheduled when the zone 4 power switched ON
1853 private void cancelPowerOnZone4Job() {
1854 ScheduledFuture<?> powerOnZone4Job = this.powerOnZone4Job;
1855 if (powerOnZone4Job != null && !powerOnZone4Job.isCancelled()) {
1856 powerOnZone4Job.cancel(true);
1857 this.powerOnZone4Job = null;
1862 * Schedule the reconnection job
1864 private void scheduleReconnectJob() {
1865 logger.debug("Schedule reconnect job");
1866 cancelReconnectJob();
1867 reconnectJob = scheduler.scheduleWithFixedDelay(() -> {
1868 if (!connector.isConnected()) {
1869 logger.debug("Trying to reconnect...");
1872 String error = null;
1873 if (openConnection()) {
1874 synchronized (sequenceLock) {
1875 schedulePowerOffJob(true);
1877 connector.sendCommand(connector.getModel().getPowerStateCmd());
1878 } catch (RotelException e) {
1879 error = "@text/offline.comm-error-first-command-after-reconnection";
1880 logger.debug("First command after connection failed", e);
1881 cancelPowerOffJob();
1886 error = "@text/offline.comm-error-reconnection";
1888 if (error != null) {
1890 handlePowerOffZone2();
1891 handlePowerOffZone3();
1892 handlePowerOffZone4();
1893 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, error);
1895 updateStatus(ThingStatus.ONLINE);
1898 }, 1, POLLING_INTERVAL, TimeUnit.SECONDS);
1902 * Cancel the reconnection job
1904 private void cancelReconnectJob() {
1905 ScheduledFuture<?> reconnectJob = this.reconnectJob;
1906 if (reconnectJob != null && !reconnectJob.isCancelled()) {
1907 reconnectJob.cancel(true);
1908 this.reconnectJob = null;
1913 * Update the state of a channel
1915 * @param channel the channel
1917 private void updateChannelState(String channel) {
1918 if (!isLinked(channel)) {
1921 State state = UnDefType.UNDEF;
1924 case CHANNEL_MAIN_POWER:
1925 if (power != null) {
1926 state = power ? OnOffType.ON : OnOffType.OFF;
1929 case CHANNEL_ZONE2_POWER:
1930 state = powerZone2 ? OnOffType.ON : OnOffType.OFF;
1932 case CHANNEL_ZONE3_POWER:
1933 state = powerZone3 ? OnOffType.ON : OnOffType.OFF;
1935 case CHANNEL_ZONE4_POWER:
1936 state = powerZone4 ? OnOffType.ON : OnOffType.OFF;
1938 case CHANNEL_SOURCE:
1939 case CHANNEL_MAIN_SOURCE:
1941 state = new StringType(source.getName());
1944 case CHANNEL_MAIN_RECORD_SOURCE:
1945 RotelSource recordSource = this.recordSource;
1946 if (isPowerOn() && recordSource != null) {
1947 state = new StringType(recordSource.getName());
1950 case CHANNEL_ZONE2_SOURCE:
1951 RotelSource sourceZone2 = this.sourceZone2;
1952 if (powerZone2 && sourceZone2 != null) {
1953 state = new StringType(sourceZone2.getName());
1956 case CHANNEL_ZONE3_SOURCE:
1957 RotelSource sourceZone3 = this.sourceZone3;
1958 if (powerZone3 && sourceZone3 != null) {
1959 state = new StringType(sourceZone3.getName());
1962 case CHANNEL_ZONE4_SOURCE:
1963 RotelSource sourceZone4 = this.sourceZone4;
1964 if (powerZone4 && sourceZone4 != null) {
1965 state = new StringType(sourceZone4.getName());
1969 case CHANNEL_MAIN_DSP:
1971 state = new StringType(dsp.getName());
1974 case CHANNEL_VOLUME:
1975 case CHANNEL_MAIN_VOLUME:
1977 long volumePct = Math
1978 .round((double) (volume - minVolume) / (double) (maxVolume - minVolume) * 100.0);
1979 state = new PercentType(BigDecimal.valueOf(volumePct));
1982 case CHANNEL_MAIN_VOLUME_UP_DOWN:
1984 state = new DecimalType(volume);
1987 case CHANNEL_ZONE2_VOLUME:
1988 if (powerZone2 && !fixedVolumeZone2) {
1989 long volumePct = Math
1990 .round((double) (volumeZone2 - minVolume) / (double) (maxVolume - minVolume) * 100.0);
1991 state = new PercentType(BigDecimal.valueOf(volumePct));
1994 case CHANNEL_ZONE2_VOLUME_UP_DOWN:
1995 if (powerZone2 && !fixedVolumeZone2) {
1996 state = new DecimalType(volumeZone2);
1999 case CHANNEL_ZONE3_VOLUME:
2000 if (powerZone3 && !fixedVolumeZone3) {
2001 long volumePct = Math
2002 .round((double) (volumeZone3 - minVolume) / (double) (maxVolume - minVolume) * 100.0);
2003 state = new PercentType(BigDecimal.valueOf(volumePct));
2006 case CHANNEL_ZONE4_VOLUME:
2007 if (powerZone4 && !fixedVolumeZone4) {
2008 long volumePct = Math
2009 .round((double) (volumeZone4 - minVolume) / (double) (maxVolume - minVolume) * 100.0);
2010 state = new PercentType(BigDecimal.valueOf(volumePct));
2014 case CHANNEL_MAIN_MUTE:
2016 state = mute ? OnOffType.ON : OnOffType.OFF;
2019 case CHANNEL_ZONE2_MUTE:
2021 state = muteZone2 ? OnOffType.ON : OnOffType.OFF;
2024 case CHANNEL_ZONE3_MUTE:
2026 state = muteZone3 ? OnOffType.ON : OnOffType.OFF;
2029 case CHANNEL_ZONE4_MUTE:
2031 state = muteZone4 ? OnOffType.ON : OnOffType.OFF;
2035 case CHANNEL_MAIN_BASS:
2037 state = new DecimalType(bass);
2040 case CHANNEL_TREBLE:
2041 case CHANNEL_MAIN_TREBLE:
2043 state = new DecimalType(treble);
2047 if (track > 0 && isPowerOn()) {
2048 state = new DecimalType(track);
2051 case CHANNEL_PLAY_CONTROL:
2053 switch (playStatus) {
2055 state = PlayPauseType.PLAY;
2059 state = PlayPauseType.PAUSE;
2064 case CHANNEL_FREQUENCY:
2065 if (frequency > 0.0 && isPowerOn()) {
2066 state = new DecimalType(frequency);
2070 state = new StringType(frontPanelLine1);
2073 state = new StringType(frontPanelLine2);
2075 case CHANNEL_BRIGHTNESS:
2076 if (isPowerOn() && connector.getModel().hasDimmerControl()) {
2077 long dimmerPct = Math.round((double) (brightness - connector.getModel().getDimmerLevelMin())
2078 / (double) (connector.getModel().getDimmerLevelMax()
2079 - connector.getModel().getDimmerLevelMin())
2081 state = new PercentType(BigDecimal.valueOf(dimmerPct));
2087 updateState(channel, state);
2091 * Inform about the main zone power state
2093 * @return true if main zone power state is known and known as ON
2095 private boolean isPowerOn() {
2096 Boolean power = this.power;
2097 return power != null && power.booleanValue();
2101 * Get the command to be used for main zone POWER ON
2103 * @return the command
2105 private RotelCommand getPowerOnCommand() {
2106 return connector.getModel().hasOtherThanPrimaryCommands() ? RotelCommand.MAIN_ZONE_POWER_ON
2107 : RotelCommand.POWER_ON;
2111 * Get the command to be used for main zone POWER OFF
2113 * @return the command
2115 private RotelCommand getPowerOffCommand() {
2116 return connector.getModel().hasOtherThanPrimaryCommands() ? RotelCommand.MAIN_ZONE_POWER_OFF
2117 : RotelCommand.POWER_OFF;
2121 * Get the command to be used for main zone VOLUME UP
2123 * @return the command
2125 private RotelCommand getVolumeUpCommand() {
2126 return connector.getModel().hasOtherThanPrimaryCommands() ? RotelCommand.MAIN_ZONE_VOLUME_UP
2127 : RotelCommand.VOLUME_UP;
2131 * Get the command to be used for main zone VOLUME DOWN
2133 * @return the command
2135 private RotelCommand getVolumeDownCommand() {
2136 return connector.getModel().hasOtherThanPrimaryCommands() ? RotelCommand.MAIN_ZONE_VOLUME_DOWN
2137 : RotelCommand.VOLUME_DOWN;
2141 * Get the command to be used for main zone MUTE ON
2143 * @return the command
2145 private RotelCommand getMuteOnCommand() {
2146 return connector.getModel().hasOtherThanPrimaryCommands() ? RotelCommand.MAIN_ZONE_MUTE_ON
2147 : RotelCommand.MUTE_ON;
2151 * Get the command to be used for main zone MUTE OFF
2153 * @return the command
2155 private RotelCommand getMuteOffCommand() {
2156 return connector.getModel().hasOtherThanPrimaryCommands() ? RotelCommand.MAIN_ZONE_MUTE_OFF
2157 : RotelCommand.MUTE_OFF;
2161 * Get the command to be used for main zone MUTE TOGGLE
2163 * @return the command
2165 private RotelCommand getMuteToggleCommand() {
2166 return connector.getModel().hasOtherThanPrimaryCommands() ? RotelCommand.MAIN_ZONE_MUTE_TOGGLE
2167 : RotelCommand.MUTE_TOGGLE;