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.io.transport.serial.SerialPortManager;
45 import org.openhab.core.library.types.DecimalType;
46 import org.openhab.core.library.types.IncreaseDecreaseType;
47 import org.openhab.core.library.types.NextPreviousType;
48 import org.openhab.core.library.types.OnOffType;
49 import org.openhab.core.library.types.PercentType;
50 import org.openhab.core.library.types.PlayPauseType;
51 import org.openhab.core.library.types.StringType;
52 import org.openhab.core.thing.ChannelUID;
53 import org.openhab.core.thing.Thing;
54 import org.openhab.core.thing.ThingStatus;
55 import org.openhab.core.thing.ThingStatusDetail;
56 import org.openhab.core.thing.binding.BaseThingHandler;
57 import org.openhab.core.types.Command;
58 import org.openhab.core.types.RefreshType;
59 import org.openhab.core.types.State;
60 import org.openhab.core.types.StateOption;
61 import org.openhab.core.types.UnDefType;
62 import org.slf4j.Logger;
63 import org.slf4j.LoggerFactory;
66 * The {@link RotelHandler} is responsible for handling commands, which are sent to one of the channels.
68 * @author Laurent Garnier - Initial contribution
71 public class RotelHandler extends BaseThingHandler implements RotelMessageEventListener {
73 private final Logger logger = LoggerFactory.getLogger(RotelHandler.class);
75 private static final RotelModel DEFAULT_MODEL = RotelModel.RSP1066;
76 private static final long POLLING_INTERVAL = TimeUnit.SECONDS.toSeconds(60);
77 private static final boolean USE_SIMULATED_DEVICE = false;
79 private @Nullable ScheduledFuture<?> reconnectJob;
80 private @Nullable ScheduledFuture<?> powerOnJob;
81 private @Nullable ScheduledFuture<?> powerOffJob;
82 private @Nullable ScheduledFuture<?> powerOnZone2Job;
83 private @Nullable ScheduledFuture<?> powerOnZone3Job;
84 private @Nullable ScheduledFuture<?> powerOnZone4Job;
86 private RotelStateDescriptionOptionProvider stateDescriptionProvider;
87 private SerialPortManager serialPortManager;
89 private RotelConnector connector = new RotelSimuConnector(DEFAULT_MODEL, RotelProtocol.HEX, new HashMap<>(),
92 private int minVolume;
93 private int maxVolume;
94 private int minToneLevel;
95 private int maxToneLevel;
97 private int currentZone = 1;
98 private boolean selectingRecord;
99 private @Nullable Boolean power;
100 private boolean powerZone2;
101 private boolean powerZone3;
102 private boolean powerZone4;
103 private RotelSource source = RotelSource.CAT0_CD;
104 private @Nullable RotelSource recordSource;
105 private @Nullable RotelSource sourceZone2;
106 private @Nullable RotelSource sourceZone3;
107 private @Nullable RotelSource sourceZone4;
108 private RotelDsp dsp = RotelDsp.CAT1_NONE;
110 private boolean mute;
111 private boolean fixedVolumeZone2;
112 private int volumeZone2;
113 private boolean muteZone2;
114 private boolean fixedVolumeZone3;
115 private int volumeZone3;
116 private boolean muteZone3;
117 private boolean fixedVolumeZone4;
118 private int volumeZone4;
119 private boolean muteZone4;
122 private RotelPlayStatus playStatus = RotelPlayStatus.STOPPED;
124 private double frequency;
125 private String frontPanelLine1 = "";
126 private String frontPanelLine2 = "";
127 private int brightness;
129 private Object sequenceLock = new Object();
134 public RotelHandler(Thing thing, RotelStateDescriptionOptionProvider stateDescriptionProvider,
135 SerialPortManager serialPortManager) {
137 this.stateDescriptionProvider = stateDescriptionProvider;
138 this.serialPortManager = serialPortManager;
142 public void initialize() {
143 logger.debug("Start initializing handler for thing {}", getThing().getUID());
145 RotelModel rotelModel;
146 switch (getThing().getThingTypeUID().getId()) {
147 case THING_TYPE_ID_RSP1066:
148 rotelModel = RotelModel.RSP1066;
150 case THING_TYPE_ID_RSP1068:
151 rotelModel = RotelModel.RSP1068;
153 case THING_TYPE_ID_RSP1069:
154 rotelModel = RotelModel.RSP1069;
156 case THING_TYPE_ID_RSP1098:
157 rotelModel = RotelModel.RSP1098;
159 case THING_TYPE_ID_RSP1570:
160 rotelModel = RotelModel.RSP1570;
162 case THING_TYPE_ID_RSP1572:
163 rotelModel = RotelModel.RSP1572;
165 case THING_TYPE_ID_RSX1055:
166 rotelModel = RotelModel.RSX1055;
168 case THING_TYPE_ID_RSX1056:
169 rotelModel = RotelModel.RSX1056;
171 case THING_TYPE_ID_RSX1057:
172 rotelModel = RotelModel.RSX1057;
174 case THING_TYPE_ID_RSX1058:
175 rotelModel = RotelModel.RSX1058;
177 case THING_TYPE_ID_RSX1065:
178 rotelModel = RotelModel.RSX1065;
180 case THING_TYPE_ID_RSX1067:
181 rotelModel = RotelModel.RSX1067;
183 case THING_TYPE_ID_RSX1550:
184 rotelModel = RotelModel.RSX1550;
186 case THING_TYPE_ID_RSX1560:
187 rotelModel = RotelModel.RSX1560;
189 case THING_TYPE_ID_RSX1562:
190 rotelModel = RotelModel.RSX1562;
192 case THING_TYPE_ID_A11:
193 rotelModel = RotelModel.A11;
195 case THING_TYPE_ID_A12:
196 rotelModel = RotelModel.A12;
198 case THING_TYPE_ID_A14:
199 rotelModel = RotelModel.A14;
201 case THING_TYPE_ID_CD11:
202 rotelModel = RotelModel.CD11;
204 case THING_TYPE_ID_CD14:
205 rotelModel = RotelModel.CD14;
207 case THING_TYPE_ID_RA11:
208 rotelModel = RotelModel.RA11;
210 case THING_TYPE_ID_RA12:
211 rotelModel = RotelModel.RA12;
213 case THING_TYPE_ID_RA1570:
214 rotelModel = RotelModel.RA1570;
216 case THING_TYPE_ID_RA1572:
217 rotelModel = RotelModel.RA1572;
219 case THING_TYPE_ID_RA1592:
220 rotelModel = RotelModel.RA1592;
222 case THING_TYPE_ID_RAP1580:
223 rotelModel = RotelModel.RAP1580;
225 case THING_TYPE_ID_RC1570:
226 rotelModel = RotelModel.RC1570;
228 case THING_TYPE_ID_RC1572:
229 rotelModel = RotelModel.RC1572;
231 case THING_TYPE_ID_RC1590:
232 rotelModel = RotelModel.RC1590;
234 case THING_TYPE_ID_RCD1570:
235 rotelModel = RotelModel.RCD1570;
237 case THING_TYPE_ID_RCD1572:
238 rotelModel = RotelModel.RCD1572;
240 case THING_TYPE_ID_RCX1500:
241 rotelModel = RotelModel.RCX1500;
243 case THING_TYPE_ID_RDD1580:
244 rotelModel = RotelModel.RDD1580;
246 case THING_TYPE_ID_RDG1520:
247 case THING_TYPE_ID_RT09:
248 rotelModel = RotelModel.RDG1520;
250 case THING_TYPE_ID_RSP1576:
251 rotelModel = RotelModel.RSP1576;
253 case THING_TYPE_ID_RSP1582:
254 rotelModel = RotelModel.RSP1582;
256 case THING_TYPE_ID_RT11:
257 rotelModel = RotelModel.RT11;
259 case THING_TYPE_ID_RT1570:
260 rotelModel = RotelModel.RT1570;
262 case THING_TYPE_ID_T11:
263 rotelModel = RotelModel.T11;
265 case THING_TYPE_ID_T14:
266 rotelModel = RotelModel.T14;
269 rotelModel = DEFAULT_MODEL;
273 RotelThingConfiguration config = getConfigAs(RotelThingConfiguration.class);
275 RotelProtocol rotelProtocol = RotelProtocol.HEX;
276 if (config.protocol != null && !config.protocol.isEmpty()) {
278 rotelProtocol = RotelProtocol.getFromName(config.protocol);
279 } catch (RotelException e) {
282 Map<String, String> properties = editProperties();
283 String property = properties.get(RotelBindingConstants.PROPERTY_PROTOCOL);
284 if (property != null && !property.isEmpty()) {
286 rotelProtocol = RotelProtocol.getFromName(property);
287 } catch (RotelException e) {
291 logger.debug("rotelProtocol {}", rotelProtocol.getName());
293 Map<RotelSource, String> sourcesLabels = new HashMap<>();
295 String readerThreadName = "OH-binding-" + getThing().getUID().getAsString();
297 connector = new RotelSimuConnector(rotelModel, rotelProtocol, sourcesLabels, readerThreadName);
299 if (rotelModel.hasVolumeControl()) {
300 maxVolume = rotelModel.getVolumeMax();
301 if (!rotelModel.hasDirectVolumeControl()) {
303 "Set minValue to {} and maxValue to {} for your sitemap widget attached to your volume item.",
304 minVolume, maxVolume);
307 if (rotelModel.hasToneControl()) {
308 maxToneLevel = rotelModel.getToneLevelMax();
309 minToneLevel = -maxToneLevel;
311 "Set minValue to {} and maxValue to {} for your sitemap widget attached to your bass or treble item.",
312 minToneLevel, maxToneLevel);
315 // Check configuration settings
316 String configError = null;
317 if ((config.serialPort == null || config.serialPort.isEmpty())
318 && (config.host == null || config.host.isEmpty())) {
319 configError = "undefined serialPort and host configuration settings; please set one of them";
320 } else if (config.host == null || config.host.isEmpty()) {
321 if (config.serialPort.toLowerCase().startsWith("rfc2217")) {
322 configError = "use host and port configuration settings for a serial over IP connection";
325 if (config.port == null) {
326 configError = "undefined port configuration setting";
327 } else if (config.port <= 0) {
328 configError = "invalid port configuration setting";
332 if (configError != null) {
333 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, configError);
335 for (RotelSource src : rotelModel.getSources()) {
336 // Consider custom input labels
338 switch (src.getName()) {
340 label = config.inputLabelCd;
343 label = config.inputLabelTuner;
346 label = config.inputLabelTape;
349 label = config.inputLabelPhono;
352 label = config.inputLabelVideo1;
355 label = config.inputLabelVideo2;
358 label = config.inputLabelVideo3;
361 label = config.inputLabelVideo4;
364 label = config.inputLabelVideo5;
367 label = config.inputLabelVideo6;
370 label = config.inputLabelUsb;
373 label = config.inputLabelMulti;
378 sourcesLabels.put(src, (label == null || label.isEmpty()) ? src.getLabel() : label);
381 if (USE_SIMULATED_DEVICE) {
382 connector = new RotelSimuConnector(rotelModel, rotelProtocol, sourcesLabels, readerThreadName);
383 } else if (config.serialPort != null) {
384 connector = new RotelSerialConnector(serialPortManager, config.serialPort, rotelModel, rotelProtocol,
385 sourcesLabels, readerThreadName);
387 connector = new RotelIpConnector(config.host, config.port, rotelModel, rotelProtocol, sourcesLabels,
391 if (rotelModel.hasSourceControl()) {
392 stateDescriptionProvider.setStateOptions(new ChannelUID(getThing().getUID(), CHANNEL_SOURCE),
393 getStateOptions(rotelModel.getSources(), sourcesLabels));
394 stateDescriptionProvider.setStateOptions(new ChannelUID(getThing().getUID(), CHANNEL_MAIN_SOURCE),
395 getStateOptions(rotelModel.getSources(), sourcesLabels));
396 stateDescriptionProvider.setStateOptions(
397 new ChannelUID(getThing().getUID(), CHANNEL_MAIN_RECORD_SOURCE),
398 getStateOptions(rotelModel.getRecordSources(), sourcesLabels));
400 if (rotelModel.hasZone2SourceControl()) {
401 stateDescriptionProvider.setStateOptions(new ChannelUID(getThing().getUID(), CHANNEL_ZONE2_SOURCE),
402 getStateOptions(rotelModel.getZone2Sources(), sourcesLabels));
404 if (rotelModel.hasZone3SourceControl()) {
405 stateDescriptionProvider.setStateOptions(new ChannelUID(getThing().getUID(), CHANNEL_ZONE3_SOURCE),
406 getStateOptions(rotelModel.getZone3Sources(), sourcesLabels));
408 if (rotelModel.hasZone4SourceControl()) {
409 stateDescriptionProvider.setStateOptions(new ChannelUID(getThing().getUID(), CHANNEL_ZONE4_SOURCE),
410 getStateOptions(rotelModel.getZone4Sources(), sourcesLabels));
412 if (rotelModel.hasDspControl()) {
413 stateDescriptionProvider.setStateOptions(new ChannelUID(getThing().getUID(), CHANNEL_DSP),
414 rotelModel.getDspStateOptions());
415 stateDescriptionProvider.setStateOptions(new ChannelUID(getThing().getUID(), CHANNEL_MAIN_DSP),
416 rotelModel.getDspStateOptions());
419 updateStatus(ThingStatus.UNKNOWN);
421 scheduleReconnectJob();
424 logger.debug("Finished initializing!");
428 public void dispose() {
429 logger.debug("Disposing handler for thing {}", getThing().getUID());
432 cancelPowerOnZone2Job();
433 cancelPowerOnZone3Job();
434 cancelPowerOnZone4Job();
435 cancelReconnectJob();
440 public List<StateOption> getStateOptions(List<RotelSource> list, Map<RotelSource, String> sourcesLabels) {
441 List<StateOption> options = new ArrayList<>();
442 for (RotelSource item : list) {
443 String label = sourcesLabels.get(item);
444 options.add(new StateOption(item.getName(), label == null ? item.getLabel() : label));
450 public void handleCommand(ChannelUID channelUID, Command command) {
451 String channel = channelUID.getId();
453 if (getThing().getStatus() != ThingStatus.ONLINE) {
454 logger.debug("Thing is not ONLINE; command {} from channel {} is ignored", command, channel);
458 if (command instanceof RefreshType) {
459 updateChannelState(channel);
463 if (!connector.isConnected()) {
464 logger.debug("Command {} from channel {} is ignored: connection not established", command, channel);
470 boolean success = true;
471 synchronized (sequenceLock) {
475 case CHANNEL_MAIN_POWER:
476 handlePowerCmd(channel, command, getPowerOnCommand(), getPowerOffCommand());
478 case CHANNEL_ZONE2_POWER:
479 if (connector.getModel().hasZone2Commands()) {
480 handlePowerCmd(channel, command, RotelCommand.ZONE2_POWER_ON, RotelCommand.ZONE2_POWER_OFF);
481 } else if (connector.getModel().getNbAdditionalZones() == 1) {
482 if (isPowerOn() || powerZone2) {
483 selectZone(2, connector.getModel().getZoneSelectCmd());
485 connector.sendCommand(RotelCommand.ZONE_SELECT);
488 logger.debug("Command {} from channel {} failed: unavailable feature", command, channel);
491 case CHANNEL_ZONE3_POWER:
492 if (connector.getModel().hasZone3Commands()) {
493 handlePowerCmd(channel, command, RotelCommand.ZONE3_POWER_ON, RotelCommand.ZONE3_POWER_OFF);
496 logger.debug("Command {} from channel {} failed: unavailable feature", command, channel);
499 case CHANNEL_ZONE4_POWER:
500 if (connector.getModel().hasZone4Commands()) {
501 handlePowerCmd(channel, command, RotelCommand.ZONE4_POWER_ON, RotelCommand.ZONE4_POWER_OFF);
504 logger.debug("Command {} from channel {} failed: unavailable feature", command, channel);
508 case CHANNEL_MAIN_SOURCE:
511 logger.debug("Command {} from channel {} ignored: device in standby", command, channel);
513 src = connector.getModel().getSourceFromName(command.toString());
514 cmd = connector.getModel().hasOtherThanPrimaryCommands() ? src.getMainZoneCommand()
517 connector.sendCommand(cmd);
520 logger.debug("Command {} from channel {} failed: undefined source command", command,
525 case CHANNEL_MAIN_RECORD_SOURCE:
528 logger.debug("Command {} from channel {} ignored: device in standby", command, channel);
529 } else if (connector.getModel().hasOtherThanPrimaryCommands()) {
530 src = connector.getModel().getSourceFromName(command.toString());
531 cmd = src.getRecordCommand();
533 connector.sendCommand(cmd);
536 logger.debug("Command {} from channel {} failed: undefined record source command",
540 src = connector.getModel().getSourceFromName(command.toString());
541 cmd = src.getCommand();
543 connector.sendCommand(RotelCommand.RECORD_FONCTION_SELECT);
545 connector.sendCommand(cmd);
548 logger.debug("Command {} from channel {} failed: undefined source command", command,
553 case CHANNEL_ZONE2_SOURCE:
556 logger.debug("Command {} from channel {} ignored: zone 2 in standby", command, channel);
557 } else if (connector.getModel().hasZone2Commands()) {
558 src = connector.getModel().getSourceFromName(command.toString());
559 cmd = src.getZone2Command();
561 connector.sendCommand(cmd);
564 logger.debug("Command {} from channel {} failed: undefined zone 2 source command",
567 } else if (connector.getModel().getNbAdditionalZones() >= 1) {
568 src = connector.getModel().getSourceFromName(command.toString());
569 cmd = src.getCommand();
571 selectZone(2, connector.getModel().getZoneSelectCmd());
572 connector.sendCommand(cmd);
575 logger.debug("Command {} from channel {} failed: undefined source command", command,
580 logger.debug("Command {} from channel {} failed: unavailable feature", command, channel);
583 case CHANNEL_ZONE3_SOURCE:
586 logger.debug("Command {} from channel {} ignored: zone 3 in standby", command, channel);
587 } else if (connector.getModel().hasZone3Commands()) {
588 src = connector.getModel().getSourceFromName(command.toString());
589 cmd = src.getZone3Command();
591 connector.sendCommand(cmd);
594 logger.debug("Command {} from channel {} failed: undefined zone 3 source command",
599 logger.debug("Command {} from channel {} failed: unavailable feature", command, channel);
602 case CHANNEL_ZONE4_SOURCE:
605 logger.debug("Command {} from channel {} ignored: zone 4 in standby", command, channel);
606 } else if (connector.getModel().hasZone4Commands()) {
607 src = connector.getModel().getSourceFromName(command.toString());
608 cmd = src.getZone4Command();
610 connector.sendCommand(cmd);
613 logger.debug("Command {} from channel {} failed: undefined zone 4 source command",
618 logger.debug("Command {} from channel {} failed: unavailable feature", command, channel);
622 case CHANNEL_MAIN_DSP:
625 logger.debug("Command {} from channel {} ignored: device in standby", command, channel);
627 connector.sendCommand(connector.getModel().getCommandFromDspName(command.toString()));
631 case CHANNEL_MAIN_VOLUME:
634 logger.debug("Command {} from channel {} ignored: device in standby", command, channel);
635 } else if (connector.getModel().hasVolumeControl()) {
636 handleVolumeCmd(volume, channel, command, getVolumeUpCommand(), getVolumeDownCommand(),
637 RotelCommand.VOLUME_SET);
640 logger.debug("Command {} from channel {} failed: unavailable feature", command, channel);
643 case CHANNEL_MAIN_VOLUME_UP_DOWN:
646 logger.debug("Command {} from channel {} ignored: device in standby", command, channel);
647 } else if (connector.getModel().hasVolumeControl()) {
648 handleVolumeCmd(volume, channel, command, getVolumeUpCommand(), getVolumeDownCommand(),
652 logger.debug("Command {} from channel {} failed: unavailable feature", command, channel);
655 case CHANNEL_ZONE2_VOLUME:
658 logger.debug("Command {} from channel {} ignored: zone 2 in standby", command, channel);
659 } else if (fixedVolumeZone2) {
661 logger.debug("Command {} from channel {} ignored: fixed volume in zone 2", command,
663 } else if (connector.getModel().hasVolumeControl()
664 && connector.getModel().getNbAdditionalZones() >= 1) {
665 if (connector.getModel().hasZone2Commands()) {
666 handleVolumeCmd(volumeZone2, channel, command, RotelCommand.ZONE2_VOLUME_UP,
667 RotelCommand.ZONE2_VOLUME_DOWN, RotelCommand.ZONE2_VOLUME_SET);
669 selectZone(2, connector.getModel().getZoneSelectCmd());
670 handleVolumeCmd(volumeZone2, channel, command, RotelCommand.VOLUME_UP,
671 RotelCommand.VOLUME_DOWN, RotelCommand.VOLUME_SET);
675 logger.debug("Command {} from channel {} failed: unavailable feature", command, channel);
678 case CHANNEL_ZONE2_VOLUME_UP_DOWN:
681 logger.debug("Command {} from channel {} ignored: zone 2 in standby", command, channel);
682 } else if (fixedVolumeZone2) {
684 logger.debug("Command {} from channel {} ignored: fixed volume in zone 2", command,
686 } else if (connector.getModel().hasVolumeControl()
687 && connector.getModel().getNbAdditionalZones() >= 1) {
688 if (connector.getModel().hasZone2Commands()) {
689 handleVolumeCmd(volumeZone2, channel, command, RotelCommand.ZONE2_VOLUME_UP,
690 RotelCommand.ZONE2_VOLUME_DOWN, null);
692 selectZone(2, connector.getModel().getZoneSelectCmd());
693 handleVolumeCmd(volumeZone2, channel, command, RotelCommand.VOLUME_UP,
694 RotelCommand.VOLUME_DOWN, null);
698 logger.debug("Command {} from channel {} failed: unavailable feature", command, channel);
701 case CHANNEL_ZONE3_VOLUME:
704 logger.debug("Command {} from channel {} ignored: zone 3 in standby", command, channel);
705 } else if (fixedVolumeZone3) {
707 logger.debug("Command {} from channel {} ignored: fixed volume in zone 3", command,
709 } else if (connector.getModel().hasVolumeControl() && connector.getModel().hasZone3Commands()) {
710 handleVolumeCmd(volumeZone3, channel, command, RotelCommand.ZONE3_VOLUME_UP,
711 RotelCommand.ZONE3_VOLUME_DOWN, RotelCommand.ZONE3_VOLUME_SET);
714 logger.debug("Command {} from channel {} failed: unavailable feature", command, channel);
717 case CHANNEL_ZONE4_VOLUME:
720 logger.debug("Command {} from channel {} ignored: zone 4 in standby", command, channel);
721 } else if (fixedVolumeZone4) {
723 logger.debug("Command {} from channel {} ignored: fixed volume in zone 4", command,
725 } else if (connector.getModel().hasVolumeControl() && connector.getModel().hasZone4Commands()) {
726 handleVolumeCmd(volumeZone4, channel, command, RotelCommand.ZONE4_VOLUME_UP,
727 RotelCommand.ZONE4_VOLUME_DOWN, RotelCommand.ZONE4_VOLUME_SET);
730 logger.debug("Command {} from channel {} failed: unavailable feature", command, channel);
734 case CHANNEL_MAIN_MUTE:
737 logger.debug("Command {} from channel {} ignored: device in standby", command, channel);
738 } else if (connector.getModel().hasVolumeControl()) {
739 handleMuteCmd(connector.getProtocol() == RotelProtocol.HEX, channel, command,
740 getMuteOnCommand(), getMuteOffCommand(), getMuteToggleCommand());
743 logger.debug("Command {} from channel {} failed: unavailable feature", command, channel);
746 case CHANNEL_ZONE2_MUTE:
749 logger.debug("Command {} from channel {} ignored: zone 2 in standby", command, channel);
750 } else if (connector.getModel().hasVolumeControl() && connector.getModel().hasZone2Commands()) {
751 handleMuteCmd(false, channel, command, RotelCommand.ZONE2_MUTE_ON,
752 RotelCommand.ZONE2_MUTE_OFF, RotelCommand.ZONE2_MUTE_TOGGLE);
755 logger.debug("Command {} from channel {} failed: unavailable feature", command, channel);
758 case CHANNEL_ZONE3_MUTE:
761 logger.debug("Command {} from channel {} ignored: zone 3 in standby", command, channel);
762 } else if (connector.getModel().hasVolumeControl() && connector.getModel().hasZone3Commands()) {
763 handleMuteCmd(false, channel, command, RotelCommand.ZONE3_MUTE_ON,
764 RotelCommand.ZONE3_MUTE_OFF, RotelCommand.ZONE3_MUTE_TOGGLE);
767 logger.debug("Command {} from channel {} failed: unavailable feature", command, channel);
770 case CHANNEL_ZONE4_MUTE:
773 logger.debug("Command {} from channel {} ignored: zone 4 in standby", command, channel);
774 } else if (connector.getModel().hasVolumeControl() && connector.getModel().hasZone4Commands()) {
775 handleMuteCmd(false, channel, command, RotelCommand.ZONE4_MUTE_ON,
776 RotelCommand.ZONE4_MUTE_OFF, RotelCommand.ZONE4_MUTE_TOGGLE);
779 logger.debug("Command {} from channel {} failed: unavailable feature", command, channel);
783 case CHANNEL_MAIN_BASS:
786 logger.debug("Command {} from channel {} ignored: device in standby", command, channel);
788 handleToneCmd(bass, channel, command, 2, RotelCommand.BASS_UP, RotelCommand.BASS_DOWN,
789 RotelCommand.BASS_SET);
793 case CHANNEL_MAIN_TREBLE:
796 logger.debug("Command {} from channel {} ignored: device in standby", command, channel);
798 handleToneCmd(treble, channel, command, 1, RotelCommand.TREBLE_UP, RotelCommand.TREBLE_DOWN,
799 RotelCommand.TREBLE_SET);
802 case CHANNEL_PLAY_CONTROL:
805 logger.debug("Command {} from channel {} ignored: device in standby", command, channel);
806 } else if (command instanceof PlayPauseType && command == PlayPauseType.PLAY) {
807 connector.sendCommand(RotelCommand.PLAY);
808 } else if (command instanceof PlayPauseType && command == PlayPauseType.PAUSE) {
809 connector.sendCommand(RotelCommand.PAUSE);
810 if (connector.getProtocol() == RotelProtocol.ASCII_V1
811 && connector.getModel() != RotelModel.RCD1570
812 && connector.getModel() != RotelModel.RCD1572
813 && connector.getModel() != RotelModel.RCX1500) {
815 connector.sendCommand(RotelCommand.PLAY_STATUS);
817 } else if (command instanceof NextPreviousType && command == NextPreviousType.NEXT) {
818 connector.sendCommand(RotelCommand.TRACK_FORWARD);
819 } else if (command instanceof NextPreviousType && command == NextPreviousType.PREVIOUS) {
820 connector.sendCommand(RotelCommand.TRACK_BACKWORD);
823 logger.debug("Command {} from channel {} failed: invalid command value", command, channel);
826 case CHANNEL_BRIGHTNESS:
829 logger.debug("Command {} from channel {} ignored: device in standby", command, channel);
830 } else if (!connector.getModel().hasDimmerControl()) {
832 logger.debug("Command {} from channel {} failed: unavailable feature", command, channel);
833 } else if (command instanceof PercentType) {
834 int dimmer = (int) Math.round(((PercentType) command).doubleValue() / 100.0
835 * (connector.getModel().getDimmerLevelMax()
836 - connector.getModel().getDimmerLevelMin()))
837 + connector.getModel().getDimmerLevelMin();
838 connector.sendCommand(RotelCommand.DIMMER_LEVEL_SET, dimmer);
841 logger.debug("Command {} from channel {} failed: invalid command value", command, channel);
846 logger.debug("Command {} from channel {} failed: nnexpected command", command, channel);
850 logger.debug("Command {} from channel {} succeeded", command, channel);
852 updateChannelState(channel);
854 } catch (RotelException e) {
855 logger.debug("Command {} from channel {} failed: {}", command, channel, e.getMessage());
856 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Sending command failed");
858 scheduleReconnectJob();
859 } catch (InterruptedException e) {
860 logger.debug("Command {} from channel {} interrupted: {}", command, channel, e.getMessage());
861 Thread.currentThread().interrupt();
867 * Handle a power ON/OFF command
869 * @param channel the channel
870 * @param command the received channel command (OnOffType)
871 * @param onCmd the command to be sent to the device to power it ON
872 * @param offCmd the command to be sent to the device to power it OFF
874 * @throws RotelException in case of communication error with the device
876 private void handlePowerCmd(String channel, Command command, RotelCommand onCmd, RotelCommand offCmd)
877 throws RotelException {
878 if (command instanceof OnOffType && command == OnOffType.ON) {
879 connector.sendCommand(onCmd);
880 } else if (command instanceof OnOffType && command == OnOffType.OFF) {
881 connector.sendCommand(offCmd);
883 logger.debug("Command {} from channel {} failed: invalid command value", command, channel);
888 * Handle a volume command
890 * @param current the current volume
891 * @param channel the channel
892 * @param command the received channel command (IncreaseDecreaseType or DecimalType)
893 * @param upCmd the command to be sent to the device to increase the volume
894 * @param downCmd the command to be sent to the device to decrease the volume
895 * @param setCmd the command to be sent to the device to set the volume at a value
897 * @throws RotelException in case of communication error with the device
899 private void handleVolumeCmd(int current, String channel, Command command, RotelCommand upCmd, RotelCommand downCmd,
900 @Nullable RotelCommand setCmd) throws RotelException {
901 if (command instanceof IncreaseDecreaseType && command == IncreaseDecreaseType.INCREASE) {
902 connector.sendCommand(upCmd);
903 } else if (command instanceof IncreaseDecreaseType && command == IncreaseDecreaseType.DECREASE) {
904 connector.sendCommand(downCmd);
905 } else if (command instanceof DecimalType && setCmd == null) {
906 int value = ((DecimalType) command).intValue();
907 if (value >= minVolume && value <= maxVolume) {
908 if (value > current) {
909 connector.sendCommand(upCmd);
910 } else if (value < current) {
911 connector.sendCommand(downCmd);
914 } else if (command instanceof PercentType && setCmd != null) {
915 int value = (int) Math.round(((PercentType) command).doubleValue() / 100.0 * (maxVolume - minVolume))
917 connector.sendCommand(setCmd, value);
919 logger.debug("Command {} from channel {} failed: invalid command value", command, channel);
924 * Handle a mute command
926 * @param onlyToggle true if only the toggle command must be used
927 * @param channel the channel
928 * @param command the received channel command (OnOffType)
929 * @param onCmd the command to be sent to the device to mute
930 * @param offCmd the command to be sent to the device to unmute
931 * @param toggleCmd the command to be sent to the device to toggle the mute state
933 * @throws RotelException in case of communication error with the device
935 private void handleMuteCmd(boolean onlyToggle, String channel, Command command, RotelCommand onCmd,
936 RotelCommand offCmd, RotelCommand toggleCmd) throws RotelException {
937 if (command instanceof OnOffType) {
939 connector.sendCommand(toggleCmd);
940 } else if (command == OnOffType.ON) {
941 connector.sendCommand(onCmd);
942 } else if (command == OnOffType.OFF) {
943 connector.sendCommand(offCmd);
946 logger.debug("Command {} from channel {} failed: invalid command value", command, channel);
951 * Handle a tone level adjustment command (bass or treble)
953 * @param current the current tone level
954 * @param channel the channel
955 * @param command the received channel command (IncreaseDecreaseType or DecimalType)
956 * @param nbSelect the number of TONE_CONTROL_SELECT commands to be run to display the right tone (bass or treble)
957 * @param upCmd the command to be sent to the device to increase the tone level
958 * @param downCmd the command to be sent to the device to decrease the tone level
959 * @param setCmd the command to be sent to the device to set the tone level at a value
961 * @throws RotelException in case of communication error with the device
962 * @throws InterruptedException in case of interruption during a thread sleep
964 private void handleToneCmd(int current, String channel, Command command, int nbSelect, RotelCommand upCmd,
965 RotelCommand downCmd, RotelCommand setCmd) throws RotelException, InterruptedException {
966 if (command instanceof IncreaseDecreaseType && command == IncreaseDecreaseType.INCREASE) {
967 selectToneControl(nbSelect);
968 connector.sendCommand(upCmd);
969 } else if (command instanceof IncreaseDecreaseType && command == IncreaseDecreaseType.DECREASE) {
970 selectToneControl(nbSelect);
971 connector.sendCommand(downCmd);
972 } else if (command instanceof DecimalType) {
973 int value = ((DecimalType) command).intValue();
974 if (value >= minToneLevel && value <= maxToneLevel) {
975 if (connector.getProtocol() != RotelProtocol.HEX) {
976 connector.sendCommand(setCmd, value);
977 } else if (value > current) {
978 selectToneControl(nbSelect);
979 connector.sendCommand(upCmd);
980 } else if (value < current) {
981 selectToneControl(nbSelect);
982 connector.sendCommand(downCmd);
986 logger.debug("Command {} from channel {} failed: invalid command value", command, channel);
991 * Run a sequence of commands to display the current tone level (bass or treble) on the device front panel
993 * @param nbSelect the number of TONE_CONTROL_SELECT commands to be run to display the right tone (bass or treble)
995 * @throws RotelException in case of communication error with the device
996 * @throws InterruptedException in case of interruption during a thread sleep
998 private void selectToneControl(int nbSelect) throws RotelException, InterruptedException {
999 // No tone control select command for RSX-1065
1000 if (connector.getProtocol() == RotelProtocol.HEX && connector.getModel() != RotelModel.RSX1065) {
1001 selectFeature(nbSelect, RotelCommand.RECORD_FONCTION_SELECT, RotelCommand.TONE_CONTROL_SELECT);
1006 * Run a sequence of commands to display a particular zone on the device front panel
1008 * @param zone the zone to be displayed (1 for main zone)
1009 * @param selectCommand the command to be sent to the device to switch the display between zones
1011 * @throws RotelException in case of communication error with the device
1012 * @throws InterruptedException in case of interruption during a thread sleep
1014 private void selectZone(int zone, @Nullable RotelCommand selectCommand)
1015 throws RotelException, InterruptedException {
1016 if (connector.getProtocol() == RotelProtocol.HEX && connector.getModel().getNbAdditionalZones() >= 1
1017 && zone >= 1 && zone != currentZone && selectCommand != null) {
1019 if (zone < currentZone) {
1020 nbSelect = zone + connector.getModel().getNbAdditionalZones() - currentZone;
1021 if (isPowerOn() && selectCommand == RotelCommand.RECORD_FONCTION_SELECT) {
1025 nbSelect = zone - currentZone;
1026 if (isPowerOn() && currentZone == 1 && selectCommand == RotelCommand.RECORD_FONCTION_SELECT
1027 && !selectingRecord) {
1031 selectFeature(nbSelect, null, selectCommand);
1036 * Run a sequence of commands to display a particular feature on the device front panel
1038 * @param nbSelect the number of select commands to be run
1039 * @param preCmd the initial command to be sent to the device (before the select commands)
1040 * @param selectCmd the select command to be sent to the device
1042 * @throws RotelException in case of communication error with the device
1043 * @throws InterruptedException in case of interruption during a thread sleep
1045 private void selectFeature(int nbSelect, @Nullable RotelCommand preCmd, RotelCommand selectCmd)
1046 throws RotelException, InterruptedException {
1047 if (connector.getProtocol() == RotelProtocol.HEX) {
1048 if (preCmd != null) {
1049 connector.sendCommand(preCmd);
1052 for (int i = 1; i <= nbSelect; i++) {
1053 connector.sendCommand(selectCmd);
1060 * Open the connection with the Rotel device
1062 * @return true if the connection is opened successfully or flase if not
1064 private synchronized boolean openConnection() {
1065 connector.addEventListener(this);
1068 } catch (RotelException e) {
1069 logger.debug("openConnection() failed: {}", e.getMessage());
1071 logger.debug("openConnection(): {}", connector.isConnected() ? "connected" : "disconnected");
1072 return connector.isConnected();
1076 * Close the connection with the Rotel device
1078 private synchronized void closeConnection() {
1080 connector.removeEventListener(this);
1081 logger.debug("closeConnection(): disconnected");
1085 public void onNewMessageEvent(EventObject event) {
1086 cancelPowerOffJob();
1088 RotelMessageEvent evt = (RotelMessageEvent) event;
1089 logger.debug("onNewMessageEvent: key {} = {}", evt.getKey(), evt.getValue());
1091 String key = evt.getKey();
1092 String value = evt.getValue().trim();
1093 if (!RotelConnector.KEY_ERROR.equals(key)) {
1094 updateStatus(ThingStatus.ONLINE);
1098 case RotelConnector.KEY_ERROR:
1099 logger.debug("Reading feedback message failed");
1100 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Reading thread ended");
1103 case RotelConnector.KEY_LINE1:
1104 frontPanelLine1 = value;
1105 updateChannelState(CHANNEL_LINE1);
1107 case RotelConnector.KEY_LINE2:
1108 frontPanelLine2 = value;
1109 updateChannelState(CHANNEL_LINE2);
1111 case RotelConnector.KEY_ZONE:
1112 currentZone = Integer.parseInt(value);
1114 case RotelConnector.KEY_RECORD_SEL:
1115 selectingRecord = RotelConnector.MSG_VALUE_ON.equalsIgnoreCase(value);
1117 case RotelConnector.KEY_POWER:
1118 if (RotelConnector.POWER_ON.equalsIgnoreCase(value)) {
1120 } else if (RotelConnector.STANDBY.equalsIgnoreCase(value)) {
1122 } else if (RotelConnector.POWER_OFF_DELAYED.equalsIgnoreCase(value)) {
1123 schedulePowerOffJob(false);
1125 throw new RotelException("Invalid value");
1128 case RotelConnector.KEY_POWER_ZONE2:
1129 if (RotelConnector.POWER_ON.equalsIgnoreCase(value)) {
1130 handlePowerOnZone2();
1131 } else if (RotelConnector.STANDBY.equalsIgnoreCase(value)) {
1132 handlePowerOffZone2();
1134 throw new RotelException("Invalid value");
1137 case RotelConnector.KEY_POWER_ZONE3:
1138 if (RotelConnector.POWER_ON.equalsIgnoreCase(value)) {
1139 handlePowerOnZone3();
1140 } else if (RotelConnector.STANDBY.equalsIgnoreCase(value)) {
1141 handlePowerOffZone3();
1143 throw new RotelException("Invalid value");
1146 case RotelConnector.KEY_POWER_ZONE4:
1147 if (RotelConnector.POWER_ON.equalsIgnoreCase(value)) {
1148 handlePowerOnZone4();
1149 } else if (RotelConnector.STANDBY.equalsIgnoreCase(value)) {
1150 handlePowerOffZone4();
1152 throw new RotelException("Invalid value");
1155 case RotelConnector.KEY_VOLUME_MIN:
1156 minVolume = Integer.parseInt(value);
1157 if (!connector.getModel().hasDirectVolumeControl()) {
1158 logger.info("Set minValue to {} for your sitemap widget attached to your volume item.",
1162 case RotelConnector.KEY_VOLUME_MAX:
1163 maxVolume = Integer.parseInt(value);
1164 if (!connector.getModel().hasDirectVolumeControl()) {
1165 logger.info("Set maxValue to {} for your sitemap widget attached to your volume item.",
1169 case RotelConnector.KEY_VOLUME:
1170 if (RotelConnector.MSG_VALUE_MIN.equalsIgnoreCase(value)) {
1172 } else if (RotelConnector.MSG_VALUE_MAX.equalsIgnoreCase(value)) {
1175 volume = Integer.parseInt(value);
1177 updateChannelState(CHANNEL_VOLUME);
1178 updateChannelState(CHANNEL_MAIN_VOLUME);
1179 updateChannelState(CHANNEL_MAIN_VOLUME_UP_DOWN);
1181 case RotelConnector.KEY_MUTE:
1182 if (RotelConnector.MSG_VALUE_ON.equalsIgnoreCase(value)) {
1184 updateChannelState(CHANNEL_MUTE);
1185 updateChannelState(CHANNEL_MAIN_MUTE);
1186 } else if (RotelConnector.MSG_VALUE_OFF.equalsIgnoreCase(value)) {
1188 updateChannelState(CHANNEL_MUTE);
1189 updateChannelState(CHANNEL_MAIN_MUTE);
1191 throw new RotelException("Invalid value");
1194 case RotelConnector.KEY_VOLUME_ZONE2:
1195 fixedVolumeZone2 = false;
1196 if (RotelConnector.MSG_VALUE_FIX.equalsIgnoreCase(value)) {
1197 fixedVolumeZone2 = true;
1198 } else if (RotelConnector.MSG_VALUE_MIN.equalsIgnoreCase(value)) {
1199 volumeZone2 = minVolume;
1200 } else if (RotelConnector.MSG_VALUE_MAX.equalsIgnoreCase(value)) {
1201 volumeZone2 = maxVolume;
1203 volumeZone2 = Integer.parseInt(value);
1205 updateChannelState(CHANNEL_ZONE2_VOLUME);
1206 updateChannelState(CHANNEL_ZONE2_VOLUME_UP_DOWN);
1208 case RotelConnector.KEY_VOLUME_ZONE3:
1209 fixedVolumeZone3 = false;
1210 if (RotelConnector.MSG_VALUE_FIX.equalsIgnoreCase(value)) {
1211 fixedVolumeZone3 = true;
1212 } else if (RotelConnector.MSG_VALUE_MIN.equalsIgnoreCase(value)) {
1213 volumeZone3 = minVolume;
1214 } else if (RotelConnector.MSG_VALUE_MAX.equalsIgnoreCase(value)) {
1215 volumeZone3 = maxVolume;
1217 volumeZone3 = Integer.parseInt(value);
1219 updateChannelState(CHANNEL_ZONE3_VOLUME);
1221 case RotelConnector.KEY_VOLUME_ZONE4:
1222 fixedVolumeZone4 = false;
1223 if (RotelConnector.MSG_VALUE_FIX.equalsIgnoreCase(value)) {
1224 fixedVolumeZone4 = true;
1225 } else if (RotelConnector.MSG_VALUE_MIN.equalsIgnoreCase(value)) {
1226 volumeZone4 = minVolume;
1227 } else if (RotelConnector.MSG_VALUE_MAX.equalsIgnoreCase(value)) {
1228 volumeZone4 = maxVolume;
1230 volumeZone4 = Integer.parseInt(value);
1232 updateChannelState(CHANNEL_ZONE4_VOLUME);
1234 case RotelConnector.KEY_MUTE_ZONE2:
1235 if (RotelConnector.MSG_VALUE_ON.equalsIgnoreCase(value)) {
1237 updateChannelState(CHANNEL_ZONE2_MUTE);
1238 } else if (RotelConnector.MSG_VALUE_OFF.equalsIgnoreCase(value)) {
1240 updateChannelState(CHANNEL_ZONE2_MUTE);
1242 throw new RotelException("Invalid value");
1245 case RotelConnector.KEY_MUTE_ZONE3:
1246 if (RotelConnector.MSG_VALUE_ON.equalsIgnoreCase(value)) {
1248 updateChannelState(CHANNEL_ZONE3_MUTE);
1249 } else if (RotelConnector.MSG_VALUE_OFF.equalsIgnoreCase(value)) {
1251 updateChannelState(CHANNEL_ZONE3_MUTE);
1253 throw new RotelException("Invalid value");
1256 case RotelConnector.KEY_MUTE_ZONE4:
1257 if (RotelConnector.MSG_VALUE_ON.equalsIgnoreCase(value)) {
1259 updateChannelState(CHANNEL_ZONE4_MUTE);
1260 } else if (RotelConnector.MSG_VALUE_OFF.equalsIgnoreCase(value)) {
1262 updateChannelState(CHANNEL_ZONE4_MUTE);
1264 throw new RotelException("Invalid value");
1267 case RotelConnector.KEY_TONE_MAX:
1268 maxToneLevel = Integer.parseInt(value);
1269 minToneLevel = -maxToneLevel;
1271 "Set minValue to {} and maxValue to {} for your sitemap widget attached to your bass or treble item.",
1272 minToneLevel, maxToneLevel);
1274 case RotelConnector.KEY_BASS:
1275 if (RotelConnector.MSG_VALUE_MIN.equalsIgnoreCase(value)) {
1276 bass = minToneLevel;
1277 } else if (RotelConnector.MSG_VALUE_MAX.equalsIgnoreCase(value)) {
1278 bass = maxToneLevel;
1280 bass = Integer.parseInt(value);
1282 updateChannelState(CHANNEL_BASS);
1283 updateChannelState(CHANNEL_MAIN_BASS);
1285 case RotelConnector.KEY_TREBLE:
1286 if (RotelConnector.MSG_VALUE_MIN.equalsIgnoreCase(value)) {
1287 treble = minToneLevel;
1288 } else if (RotelConnector.MSG_VALUE_MAX.equalsIgnoreCase(value)) {
1289 treble = maxToneLevel;
1291 treble = Integer.parseInt(value);
1293 updateChannelState(CHANNEL_TREBLE);
1294 updateChannelState(CHANNEL_MAIN_TREBLE);
1296 case RotelConnector.KEY_SOURCE:
1297 source = connector.getModel().getSourceFromCommand(RotelCommand.getFromAsciiCommand(value));
1298 updateChannelState(CHANNEL_SOURCE);
1299 updateChannelState(CHANNEL_MAIN_SOURCE);
1301 case RotelConnector.KEY_RECORD:
1302 recordSource = connector.getModel()
1303 .getRecordSourceFromCommand(RotelCommand.getFromAsciiCommand(value));
1304 updateChannelState(CHANNEL_MAIN_RECORD_SOURCE);
1306 case RotelConnector.KEY_SOURCE_ZONE2:
1307 sourceZone2 = connector.getModel()
1308 .getZone2SourceFromCommand(RotelCommand.getFromAsciiCommand(value));
1309 updateChannelState(CHANNEL_ZONE2_SOURCE);
1311 case RotelConnector.KEY_SOURCE_ZONE3:
1312 sourceZone3 = connector.getModel()
1313 .getZone3SourceFromCommand(RotelCommand.getFromAsciiCommand(value));
1314 updateChannelState(CHANNEL_ZONE3_SOURCE);
1316 case RotelConnector.KEY_SOURCE_ZONE4:
1317 sourceZone4 = connector.getModel()
1318 .getZone4SourceFromCommand(RotelCommand.getFromAsciiCommand(value));
1319 updateChannelState(CHANNEL_ZONE4_SOURCE);
1321 case RotelConnector.KEY_DSP_MODE:
1322 if ("dolby_pliix_movie".equals(value)) {
1323 value = "dolby_plii_movie";
1324 } else if ("dolby_pliix_music".equals(value)) {
1325 value = "dolby_plii_music";
1326 } else if ("dolby_pliix_game".equals(value)) {
1327 value = "dolby_plii_game";
1329 dsp = connector.getModel().getDspFromFeedback(value);
1330 logger.debug("DSP {}", dsp.getName());
1331 updateChannelState(CHANNEL_DSP);
1332 updateChannelState(CHANNEL_MAIN_DSP);
1334 case RotelConnector.KEY1_PLAY_STATUS:
1335 case RotelConnector.KEY2_PLAY_STATUS:
1336 if (RotelConnector.PLAY.equalsIgnoreCase(value)) {
1337 playStatus = RotelPlayStatus.PLAYING;
1338 updateChannelState(CHANNEL_PLAY_CONTROL);
1339 } else if (RotelConnector.PAUSE.equalsIgnoreCase(value)) {
1340 playStatus = RotelPlayStatus.PAUSED;
1341 updateChannelState(CHANNEL_PLAY_CONTROL);
1342 } else if (RotelConnector.STOP.equalsIgnoreCase(value)) {
1343 playStatus = RotelPlayStatus.STOPPED;
1344 updateChannelState(CHANNEL_PLAY_CONTROL);
1346 throw new RotelException("Invalid value");
1349 case RotelConnector.KEY_TRACK:
1350 if (source.getName().equals("CD") && !connector.getModel().hasSourceControl()) {
1351 track = Integer.parseInt(value);
1352 updateChannelState(CHANNEL_TRACK);
1355 case RotelConnector.KEY_FREQ:
1356 if (RotelConnector.MSG_VALUE_OFF.equalsIgnoreCase(value)) {
1359 // Suppress a potential ending "k" or "K"
1360 if (value.toUpperCase().endsWith("K")) {
1361 value = value.substring(0, value.length() - 1);
1363 frequency = Double.parseDouble(value);
1365 updateChannelState(CHANNEL_FREQUENCY);
1367 case RotelConnector.KEY_DIMMER:
1368 brightness = Integer.parseInt(value);
1369 updateChannelState(CHANNEL_BRIGHTNESS);
1371 case RotelConnector.KEY_UPDATE_MODE:
1372 case RotelConnector.KEY_DISPLAY_UPDATE:
1375 logger.debug("onNewMessageEvent: unhandled key {}", key);
1378 } catch (NumberFormatException | RotelException e) {
1379 logger.debug("Invalid value {} for key {}", value, key);
1384 * Handle the received information that device power (main zone) is ON
1386 private void handlePowerOn() {
1387 Boolean prev = power;
1389 updateChannelState(CHANNEL_POWER);
1390 updateChannelState(CHANNEL_MAIN_POWER);
1391 if ((prev == null) || !prev) {
1392 schedulePowerOnJob();
1397 * Handle the received information that device power (main zone) is OFF
1399 private void handlePowerOff() {
1402 updateChannelState(CHANNEL_POWER);
1403 updateChannelState(CHANNEL_MAIN_POWER);
1404 updateChannelState(CHANNEL_SOURCE);
1405 updateChannelState(CHANNEL_MAIN_SOURCE);
1406 updateChannelState(CHANNEL_MAIN_RECORD_SOURCE);
1407 updateChannelState(CHANNEL_DSP);
1408 updateChannelState(CHANNEL_MAIN_DSP);
1409 updateChannelState(CHANNEL_VOLUME);
1410 updateChannelState(CHANNEL_MAIN_VOLUME);
1411 updateChannelState(CHANNEL_MAIN_VOLUME_UP_DOWN);
1412 updateChannelState(CHANNEL_MUTE);
1413 updateChannelState(CHANNEL_MAIN_MUTE);
1414 updateChannelState(CHANNEL_BASS);
1415 updateChannelState(CHANNEL_MAIN_BASS);
1416 updateChannelState(CHANNEL_TREBLE);
1417 updateChannelState(CHANNEL_MAIN_TREBLE);
1418 updateChannelState(CHANNEL_PLAY_CONTROL);
1419 updateChannelState(CHANNEL_TRACK);
1420 updateChannelState(CHANNEL_FREQUENCY);
1421 updateChannelState(CHANNEL_BRIGHTNESS);
1425 * Handle the received information that zone 2 power is ON
1427 private void handlePowerOnZone2() {
1428 boolean prev = powerZone2;
1430 updateChannelState(CHANNEL_ZONE2_POWER);
1432 schedulePowerOnZone2Job();
1437 * Handle the received information that zone 2 power is OFF
1439 private void handlePowerOffZone2() {
1440 cancelPowerOnZone2Job();
1442 updateChannelState(CHANNEL_ZONE2_POWER);
1443 updateChannelState(CHANNEL_ZONE2_SOURCE);
1444 updateChannelState(CHANNEL_ZONE2_VOLUME);
1445 updateChannelState(CHANNEL_ZONE2_VOLUME_UP_DOWN);
1446 updateChannelState(CHANNEL_ZONE2_MUTE);
1450 * Handle the received information that zone 3 power is ON
1452 private void handlePowerOnZone3() {
1453 boolean prev = powerZone3;
1455 updateChannelState(CHANNEL_ZONE3_POWER);
1457 schedulePowerOnZone3Job();
1462 * Handle the received information that zone 3 power is OFF
1464 private void handlePowerOffZone3() {
1465 cancelPowerOnZone3Job();
1467 updateChannelState(CHANNEL_ZONE3_POWER);
1468 updateChannelState(CHANNEL_ZONE3_SOURCE);
1469 updateChannelState(CHANNEL_ZONE3_VOLUME);
1470 updateChannelState(CHANNEL_ZONE3_MUTE);
1474 * Handle the received information that zone 4 power is ON
1476 private void handlePowerOnZone4() {
1477 boolean prev = powerZone4;
1479 updateChannelState(CHANNEL_ZONE4_POWER);
1481 schedulePowerOnZone4Job();
1486 * Handle the received information that zone 4 power is OFF
1488 private void handlePowerOffZone4() {
1489 cancelPowerOnZone4Job();
1491 updateChannelState(CHANNEL_ZONE4_POWER);
1492 updateChannelState(CHANNEL_ZONE4_SOURCE);
1493 updateChannelState(CHANNEL_ZONE4_VOLUME);
1494 updateChannelState(CHANNEL_ZONE4_MUTE);
1498 * Schedule the job that will consider the device as OFF if no new event is received before its running
1500 * @param switchOffAllZones true if all zones have to be considered as OFF
1502 private void schedulePowerOffJob(boolean switchOffAllZones) {
1503 logger.debug("Schedule power OFF job");
1504 cancelPowerOffJob();
1505 powerOffJob = scheduler.schedule(() -> {
1506 logger.debug("Power OFF job");
1508 if (switchOffAllZones) {
1509 handlePowerOffZone2();
1510 handlePowerOffZone3();
1511 handlePowerOffZone4();
1513 }, 2000, TimeUnit.MILLISECONDS);
1517 * Cancel the job that will consider the device as OFF
1519 private void cancelPowerOffJob() {
1520 ScheduledFuture<?> powerOffJob = this.powerOffJob;
1521 if (powerOffJob != null && !powerOffJob.isCancelled()) {
1522 powerOffJob.cancel(true);
1523 this.powerOffJob = null;
1528 * Schedule the job to run with a few seconds delay when the device power (main zone) switched ON
1530 private void schedulePowerOnJob() {
1531 logger.debug("Schedule power ON job");
1533 powerOnJob = scheduler.schedule(() -> {
1534 synchronized (sequenceLock) {
1535 logger.debug("Power ON job");
1537 switch (connector.getProtocol()) {
1539 if (connector.getModel().getRespNbChars() <= 13
1540 && connector.getModel().hasVolumeControl()) {
1541 connector.sendCommand(getVolumeDownCommand());
1543 connector.sendCommand(getVolumeUpCommand());
1546 if (connector.getModel().getNbAdditionalZones() >= 1) {
1547 if (currentZone != 1 && connector.getModel()
1548 .getZoneSelectCmd() == RotelCommand.RECORD_FONCTION_SELECT) {
1549 selectZone(1, connector.getModel().getZoneSelectCmd());
1550 } else if (!selectingRecord) {
1551 connector.sendCommand(RotelCommand.RECORD_FONCTION_SELECT);
1555 connector.sendCommand(RotelCommand.RECORD_FONCTION_SELECT);
1558 if (connector.getModel().hasToneControl()) {
1559 if (connector.getModel() == RotelModel.RSX1065) {
1560 // No tone control select command
1561 connector.sendCommand(RotelCommand.TREBLE_DOWN);
1563 connector.sendCommand(RotelCommand.TREBLE_UP);
1565 connector.sendCommand(RotelCommand.BASS_DOWN);
1567 connector.sendCommand(RotelCommand.BASS_UP);
1570 selectFeature(2, null, RotelCommand.TONE_CONTROL_SELECT);
1575 if (connector.getModel() != RotelModel.RAP1580 && connector.getModel() != RotelModel.RDD1580
1576 && connector.getModel() != RotelModel.RSP1576
1577 && connector.getModel() != RotelModel.RSP1582) {
1578 connector.sendCommand(RotelCommand.UPDATE_AUTO);
1581 if (connector.getModel().hasSourceControl()) {
1582 connector.sendCommand(RotelCommand.SOURCE);
1585 if (connector.getModel().hasVolumeControl() || connector.getModel().hasToneControl()) {
1586 if (connector.getModel().hasVolumeControl()
1587 && connector.getModel() != RotelModel.RAP1580
1588 && connector.getModel() != RotelModel.RSP1576
1589 && connector.getModel() != RotelModel.RSP1582) {
1590 connector.sendCommand(RotelCommand.VOLUME_GET_MIN);
1592 connector.sendCommand(RotelCommand.VOLUME_GET_MAX);
1595 if (connector.getModel().hasToneControl()) {
1596 connector.sendCommand(RotelCommand.TONE_MAX);
1599 // Wait enough to be sure to get the min/max values requested just before
1601 if (connector.getModel().hasVolumeControl()) {
1602 connector.sendCommand(RotelCommand.VOLUME_GET);
1604 if (connector.getModel() != RotelModel.RA11
1605 && connector.getModel() != RotelModel.RA12
1606 && connector.getModel() != RotelModel.RCX1500) {
1607 connector.sendCommand(RotelCommand.MUTE);
1611 if (connector.getModel().hasToneControl()) {
1612 connector.sendCommand(RotelCommand.BASS);
1614 connector.sendCommand(RotelCommand.TREBLE);
1618 if (connector.getModel().hasPlayControl()) {
1619 if (connector.getModel() != RotelModel.RCD1570
1620 && connector.getModel() != RotelModel.RCD1572
1621 && (connector.getModel() != RotelModel.RCX1500
1622 || !source.getName().equals("CD"))) {
1623 connector.sendCommand(RotelCommand.PLAY_STATUS);
1626 connector.sendCommand(RotelCommand.CD_PLAY_STATUS);
1630 if (connector.getModel().hasDspControl()) {
1631 connector.sendCommand(RotelCommand.DSP_MODE);
1634 if (connector.getModel().canGetFrequency()) {
1635 connector.sendCommand(RotelCommand.FREQUENCY);
1638 if (connector.getModel().hasDimmerControl() && connector.getModel().canGetDimmerLevel()) {
1639 connector.sendCommand(RotelCommand.DIMMER_LEVEL_GET);
1644 connector.sendCommand(RotelCommand.UPDATE_AUTO);
1646 if (connector.getModel().hasSourceControl()) {
1647 connector.sendCommand(RotelCommand.SOURCE);
1650 if (connector.getModel().hasVolumeControl()) {
1651 connector.sendCommand(RotelCommand.VOLUME_GET);
1653 connector.sendCommand(RotelCommand.MUTE);
1656 if (connector.getModel().hasToneControl()) {
1657 connector.sendCommand(RotelCommand.BASS);
1659 connector.sendCommand(RotelCommand.TREBLE);
1662 if (connector.getModel().hasPlayControl()) {
1663 connector.sendCommand(RotelCommand.PLAY_STATUS);
1665 if (source.getName().equals("CD") && !connector.getModel().hasSourceControl()) {
1666 connector.sendCommand(RotelCommand.TRACK);
1670 if (connector.getModel().hasDspControl()) {
1671 connector.sendCommand(RotelCommand.DSP_MODE);
1674 if (connector.getModel().canGetFrequency()) {
1675 connector.sendCommand(RotelCommand.FREQUENCY);
1678 if (connector.getModel().hasDimmerControl() && connector.getModel().canGetDimmerLevel()) {
1679 connector.sendCommand(RotelCommand.DIMMER_LEVEL_GET);
1684 } catch (RotelException e) {
1685 logger.debug("Init sequence failed: {}", e.getMessage());
1686 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Init sequence failed");
1688 } catch (InterruptedException e) {
1689 logger.debug("Init sequence interrupted: {}", e.getMessage());
1690 Thread.currentThread().interrupt();
1693 }, 2500, TimeUnit.MILLISECONDS);
1697 * Cancel the job scheduled when the device power (main zone) switched ON
1699 private void cancelPowerOnJob() {
1700 ScheduledFuture<?> powerOnJob = this.powerOnJob;
1701 if (powerOnJob != null && !powerOnJob.isCancelled()) {
1702 powerOnJob.cancel(true);
1703 this.powerOnJob = null;
1708 * Schedule the job to run with a few seconds delay when the zone 2 power switched ON
1710 private void schedulePowerOnZone2Job() {
1711 logger.debug("Schedule power ON zone 2 job");
1712 cancelPowerOnZone2Job();
1713 powerOnZone2Job = scheduler.schedule(() -> {
1714 synchronized (sequenceLock) {
1715 logger.debug("Power ON zone 2 job");
1717 if (connector.getProtocol() == RotelProtocol.HEX
1718 && connector.getModel().getNbAdditionalZones() >= 1) {
1719 selectZone(2, connector.getModel().getZoneSelectCmd());
1720 connector.sendCommand(connector.getModel().hasZone2Commands() ? RotelCommand.ZONE2_VOLUME_DOWN
1721 : RotelCommand.VOLUME_DOWN);
1723 connector.sendCommand(connector.getModel().hasZone2Commands() ? RotelCommand.ZONE2_VOLUME_UP
1724 : RotelCommand.VOLUME_UP);
1727 } catch (RotelException e) {
1728 logger.debug("Init sequence zone 2 failed: {}", e.getMessage());
1729 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
1730 "Init sequence zone 2 failed");
1732 } catch (InterruptedException e) {
1733 logger.debug("Init sequence zone 2 interrupted: {}", e.getMessage());
1734 Thread.currentThread().interrupt();
1737 }, 2500, TimeUnit.MILLISECONDS);
1741 * Cancel the job scheduled when the zone 2 power switched ON
1743 private void cancelPowerOnZone2Job() {
1744 ScheduledFuture<?> powerOnZone2Job = this.powerOnZone2Job;
1745 if (powerOnZone2Job != null && !powerOnZone2Job.isCancelled()) {
1746 powerOnZone2Job.cancel(true);
1747 this.powerOnZone2Job = null;
1752 * Schedule the job to run with a few seconds delay when the zone 3 power switched ON
1754 private void schedulePowerOnZone3Job() {
1755 logger.debug("Schedule power ON zone 3 job");
1756 cancelPowerOnZone3Job();
1757 powerOnZone3Job = scheduler.schedule(() -> {
1758 synchronized (sequenceLock) {
1759 logger.debug("Power ON zone 3 job");
1761 if (connector.getProtocol() == RotelProtocol.HEX
1762 && connector.getModel().getNbAdditionalZones() >= 2) {
1763 selectZone(3, connector.getModel().getZoneSelectCmd());
1764 connector.sendCommand(connector.getModel().hasZone3Commands() ? RotelCommand.ZONE3_VOLUME_DOWN
1765 : RotelCommand.VOLUME_DOWN);
1767 connector.sendCommand(connector.getModel().hasZone3Commands() ? RotelCommand.ZONE3_VOLUME_UP
1768 : RotelCommand.VOLUME_UP);
1771 } catch (RotelException e) {
1772 logger.debug("Init sequence zone 3 failed: {}", e.getMessage());
1773 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
1774 "Init sequence zone 3 failed");
1776 } catch (InterruptedException e) {
1777 logger.debug("Init sequence zone 3 interrupted: {}", e.getMessage());
1778 Thread.currentThread().interrupt();
1781 }, 2500, TimeUnit.MILLISECONDS);
1785 * Cancel the job scheduled when the zone 3 power switched ON
1787 private void cancelPowerOnZone3Job() {
1788 ScheduledFuture<?> powerOnZone3Job = this.powerOnZone3Job;
1789 if (powerOnZone3Job != null && !powerOnZone3Job.isCancelled()) {
1790 powerOnZone3Job.cancel(true);
1791 this.powerOnZone3Job = null;
1796 * Schedule the job to run with a few seconds delay when the zone 4 power switched ON
1798 private void schedulePowerOnZone4Job() {
1799 logger.debug("Schedule power ON zone 4 job");
1800 cancelPowerOnZone4Job();
1801 powerOnZone4Job = scheduler.schedule(() -> {
1802 synchronized (sequenceLock) {
1803 logger.debug("Power ON zone 4 job");
1805 if (connector.getProtocol() == RotelProtocol.HEX
1806 && connector.getModel().getNbAdditionalZones() >= 3) {
1807 selectZone(4, connector.getModel().getZoneSelectCmd());
1808 connector.sendCommand(connector.getModel().hasZone4Commands() ? RotelCommand.ZONE4_VOLUME_DOWN
1809 : RotelCommand.VOLUME_DOWN);
1811 connector.sendCommand(connector.getModel().hasZone4Commands() ? RotelCommand.ZONE4_VOLUME_UP
1812 : RotelCommand.VOLUME_UP);
1815 } catch (RotelException e) {
1816 logger.debug("Init sequence zone 4 failed: {}", e.getMessage());
1817 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
1818 "Init sequence zone 4 failed");
1820 } catch (InterruptedException e) {
1821 logger.debug("Init sequence zone 4 interrupted: {}", e.getMessage());
1822 Thread.currentThread().interrupt();
1825 }, 2500, TimeUnit.MILLISECONDS);
1829 * Cancel the job scheduled when the zone 4 power switched ON
1831 private void cancelPowerOnZone4Job() {
1832 ScheduledFuture<?> powerOnZone4Job = this.powerOnZone4Job;
1833 if (powerOnZone4Job != null && !powerOnZone4Job.isCancelled()) {
1834 powerOnZone4Job.cancel(true);
1835 this.powerOnZone4Job = null;
1840 * Schedule the reconnection job
1842 private void scheduleReconnectJob() {
1843 logger.debug("Schedule reconnect job");
1844 cancelReconnectJob();
1845 reconnectJob = scheduler.scheduleWithFixedDelay(() -> {
1846 if (!connector.isConnected()) {
1847 logger.debug("Trying to reconnect...");
1850 String error = null;
1851 if (openConnection()) {
1852 synchronized (sequenceLock) {
1853 schedulePowerOffJob(true);
1855 connector.sendCommand(connector.getModel().getPowerStateCmd());
1856 } catch (RotelException e) {
1857 error = "First command after connection failed";
1858 logger.debug("{}: {}", error, e.getMessage());
1859 cancelPowerOffJob();
1864 error = "Reconnection failed";
1866 if (error != null) {
1868 handlePowerOffZone2();
1869 handlePowerOffZone3();
1870 handlePowerOffZone4();
1871 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, error);
1873 updateStatus(ThingStatus.ONLINE);
1876 }, 1, POLLING_INTERVAL, TimeUnit.SECONDS);
1880 * Cancel the reconnection job
1882 private void cancelReconnectJob() {
1883 ScheduledFuture<?> reconnectJob = this.reconnectJob;
1884 if (reconnectJob != null && !reconnectJob.isCancelled()) {
1885 reconnectJob.cancel(true);
1886 this.reconnectJob = null;
1891 * Update the state of a channel
1893 * @param channel the channel
1895 private void updateChannelState(String channel) {
1896 if (!isLinked(channel)) {
1899 State state = UnDefType.UNDEF;
1902 case CHANNEL_MAIN_POWER:
1903 if (power != null) {
1904 state = power ? OnOffType.ON : OnOffType.OFF;
1907 case CHANNEL_ZONE2_POWER:
1908 state = powerZone2 ? OnOffType.ON : OnOffType.OFF;
1910 case CHANNEL_ZONE3_POWER:
1911 state = powerZone3 ? OnOffType.ON : OnOffType.OFF;
1913 case CHANNEL_ZONE4_POWER:
1914 state = powerZone4 ? OnOffType.ON : OnOffType.OFF;
1916 case CHANNEL_SOURCE:
1917 case CHANNEL_MAIN_SOURCE:
1919 state = new StringType(source.getName());
1922 case CHANNEL_MAIN_RECORD_SOURCE:
1923 RotelSource recordSource = this.recordSource;
1924 if (isPowerOn() && recordSource != null) {
1925 state = new StringType(recordSource.getName());
1928 case CHANNEL_ZONE2_SOURCE:
1929 RotelSource sourceZone2 = this.sourceZone2;
1930 if (powerZone2 && sourceZone2 != null) {
1931 state = new StringType(sourceZone2.getName());
1934 case CHANNEL_ZONE3_SOURCE:
1935 RotelSource sourceZone3 = this.sourceZone3;
1936 if (powerZone3 && sourceZone3 != null) {
1937 state = new StringType(sourceZone3.getName());
1940 case CHANNEL_ZONE4_SOURCE:
1941 RotelSource sourceZone4 = this.sourceZone4;
1942 if (powerZone4 && sourceZone4 != null) {
1943 state = new StringType(sourceZone4.getName());
1947 case CHANNEL_MAIN_DSP:
1949 state = new StringType(dsp.getName());
1952 case CHANNEL_VOLUME:
1953 case CHANNEL_MAIN_VOLUME:
1955 long volumePct = Math
1956 .round((double) (volume - minVolume) / (double) (maxVolume - minVolume) * 100.0);
1957 state = new PercentType(BigDecimal.valueOf(volumePct));
1960 case CHANNEL_MAIN_VOLUME_UP_DOWN:
1962 state = new DecimalType(volume);
1965 case CHANNEL_ZONE2_VOLUME:
1966 if (powerZone2 && !fixedVolumeZone2) {
1967 long volumePct = Math
1968 .round((double) (volumeZone2 - minVolume) / (double) (maxVolume - minVolume) * 100.0);
1969 state = new PercentType(BigDecimal.valueOf(volumePct));
1972 case CHANNEL_ZONE2_VOLUME_UP_DOWN:
1973 if (powerZone2 && !fixedVolumeZone2) {
1974 state = new DecimalType(volumeZone2);
1977 case CHANNEL_ZONE3_VOLUME:
1978 if (powerZone3 && !fixedVolumeZone3) {
1979 long volumePct = Math
1980 .round((double) (volumeZone3 - minVolume) / (double) (maxVolume - minVolume) * 100.0);
1981 state = new PercentType(BigDecimal.valueOf(volumePct));
1984 case CHANNEL_ZONE4_VOLUME:
1985 if (powerZone4 && !fixedVolumeZone4) {
1986 long volumePct = Math
1987 .round((double) (volumeZone4 - minVolume) / (double) (maxVolume - minVolume) * 100.0);
1988 state = new PercentType(BigDecimal.valueOf(volumePct));
1992 case CHANNEL_MAIN_MUTE:
1994 state = mute ? OnOffType.ON : OnOffType.OFF;
1997 case CHANNEL_ZONE2_MUTE:
1999 state = muteZone2 ? OnOffType.ON : OnOffType.OFF;
2002 case CHANNEL_ZONE3_MUTE:
2004 state = muteZone3 ? OnOffType.ON : OnOffType.OFF;
2007 case CHANNEL_ZONE4_MUTE:
2009 state = muteZone4 ? OnOffType.ON : OnOffType.OFF;
2013 case CHANNEL_MAIN_BASS:
2015 state = new DecimalType(bass);
2018 case CHANNEL_TREBLE:
2019 case CHANNEL_MAIN_TREBLE:
2021 state = new DecimalType(treble);
2025 if (track > 0 && isPowerOn()) {
2026 state = new DecimalType(track);
2029 case CHANNEL_PLAY_CONTROL:
2031 switch (playStatus) {
2033 state = PlayPauseType.PLAY;
2037 state = PlayPauseType.PAUSE;
2042 case CHANNEL_FREQUENCY:
2043 if (frequency > 0.0 && isPowerOn()) {
2044 state = new DecimalType(frequency);
2048 state = new StringType(frontPanelLine1);
2051 state = new StringType(frontPanelLine2);
2053 case CHANNEL_BRIGHTNESS:
2054 if (isPowerOn() && connector.getModel().hasDimmerControl()) {
2055 long dimmerPct = Math.round((double) (brightness - connector.getModel().getDimmerLevelMin())
2056 / (double) (connector.getModel().getDimmerLevelMax()
2057 - connector.getModel().getDimmerLevelMin())
2059 state = new PercentType(BigDecimal.valueOf(dimmerPct));
2065 updateState(channel, state);
2069 * Inform about the main zone power state
2071 * @return true if main zone power state is known and known as ON
2073 private boolean isPowerOn() {
2074 Boolean power = this.power;
2075 return power != null && power.booleanValue();
2079 * Get the command to be used for main zone POWER ON
2081 * @return the command
2083 private RotelCommand getPowerOnCommand() {
2084 return connector.getModel().hasOtherThanPrimaryCommands() ? RotelCommand.MAIN_ZONE_POWER_ON
2085 : RotelCommand.POWER_ON;
2089 * Get the command to be used for main zone POWER OFF
2091 * @return the command
2093 private RotelCommand getPowerOffCommand() {
2094 return connector.getModel().hasOtherThanPrimaryCommands() ? RotelCommand.MAIN_ZONE_POWER_OFF
2095 : RotelCommand.POWER_OFF;
2099 * Get the command to be used for main zone VOLUME UP
2101 * @return the command
2103 private RotelCommand getVolumeUpCommand() {
2104 return connector.getModel().hasOtherThanPrimaryCommands() ? RotelCommand.MAIN_ZONE_VOLUME_UP
2105 : RotelCommand.VOLUME_UP;
2109 * Get the command to be used for main zone VOLUME DOWN
2111 * @return the command
2113 private RotelCommand getVolumeDownCommand() {
2114 return connector.getModel().hasOtherThanPrimaryCommands() ? RotelCommand.MAIN_ZONE_VOLUME_DOWN
2115 : RotelCommand.VOLUME_DOWN;
2119 * Get the command to be used for main zone MUTE ON
2121 * @return the command
2123 private RotelCommand getMuteOnCommand() {
2124 return connector.getModel().hasOtherThanPrimaryCommands() ? RotelCommand.MAIN_ZONE_MUTE_ON
2125 : RotelCommand.MUTE_ON;
2129 * Get the command to be used for main zone MUTE OFF
2131 * @return the command
2133 private RotelCommand getMuteOffCommand() {
2134 return connector.getModel().hasOtherThanPrimaryCommands() ? RotelCommand.MAIN_ZONE_MUTE_OFF
2135 : RotelCommand.MUTE_OFF;
2139 * Get the command to be used for main zone MUTE TOGGLE
2141 * @return the command
2143 private RotelCommand getMuteToggleCommand() {
2144 return connector.getModel().hasOtherThanPrimaryCommands() ? RotelCommand.MAIN_ZONE_MUTE_TOGGLE
2145 : RotelCommand.MUTE_TOGGLE;