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> sourcesCustomLabels = new HashMap<>();
294 Map<RotelSource, String> sourcesLabels = new HashMap<>();
296 String readerThreadName = "OH-binding-" + getThing().getUID().getAsString();
298 connector = new RotelSimuConnector(rotelModel, rotelProtocol, sourcesLabels, readerThreadName);
300 if (rotelModel.hasVolumeControl()) {
301 maxVolume = rotelModel.getVolumeMax();
302 if (!rotelModel.hasDirectVolumeControl()) {
304 "Set minValue to {} and maxValue to {} for your sitemap widget attached to your volume item.",
305 minVolume, maxVolume);
308 if (rotelModel.hasToneControl()) {
309 maxToneLevel = rotelModel.getToneLevelMax();
310 minToneLevel = -maxToneLevel;
312 "Set minValue to {} and maxValue to {} for your sitemap widget attached to your bass or treble item.",
313 minToneLevel, maxToneLevel);
316 // Check configuration settings
317 String configError = null;
318 if ((config.serialPort == null || config.serialPort.isEmpty())
319 && (config.host == null || config.host.isEmpty())) {
320 configError = "@text/offline.config-error-unknown-serialport-and-host";
321 } else if (config.host == null || config.host.isEmpty()) {
322 if (config.serialPort.toLowerCase().startsWith("rfc2217")) {
323 configError = "@text/offline.config-error-invalid-serial-over-ip";
326 if (config.port == null) {
327 configError = "@text/offline.config-error-unknown-port";
328 } else if (config.port <= 0) {
329 configError = "@text/offline.config-error-invalid-port";
333 if (configError != null) {
334 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, configError);
336 for (RotelSource src : rotelModel.getSources()) {
337 // Consider custom input labels
339 switch (src.getName()) {
341 label = config.inputLabelCd;
344 label = config.inputLabelTuner;
347 label = config.inputLabelTape;
350 label = config.inputLabelPhono;
353 label = config.inputLabelVideo1;
356 label = config.inputLabelVideo2;
359 label = config.inputLabelVideo3;
362 label = config.inputLabelVideo4;
365 label = config.inputLabelVideo5;
368 label = config.inputLabelVideo6;
371 label = config.inputLabelUsb;
374 label = config.inputLabelMulti;
379 if (label != null && !label.isEmpty()) {
380 sourcesCustomLabels.put(src, label);
382 sourcesLabels.put(src, (label == null || label.isEmpty()) ? src.getLabel() : label);
385 if (USE_SIMULATED_DEVICE) {
386 connector = new RotelSimuConnector(rotelModel, rotelProtocol, sourcesLabels, readerThreadName);
387 } else if (config.serialPort != null) {
388 connector = new RotelSerialConnector(serialPortManager, config.serialPort, rotelModel, rotelProtocol,
389 sourcesLabels, readerThreadName);
391 connector = new RotelIpConnector(config.host, config.port, rotelModel, rotelProtocol, sourcesLabels,
395 if (rotelModel.hasSourceControl()) {
396 stateDescriptionProvider.setStateOptions(new ChannelUID(getThing().getUID(), CHANNEL_SOURCE),
397 getStateOptions(rotelModel.getSources(), sourcesCustomLabels));
398 stateDescriptionProvider.setStateOptions(new ChannelUID(getThing().getUID(), CHANNEL_MAIN_SOURCE),
399 getStateOptions(rotelModel.getSources(), sourcesCustomLabels));
400 stateDescriptionProvider.setStateOptions(
401 new ChannelUID(getThing().getUID(), CHANNEL_MAIN_RECORD_SOURCE),
402 getStateOptions(rotelModel.getRecordSources(), sourcesCustomLabels));
404 if (rotelModel.hasZone2SourceControl()) {
405 stateDescriptionProvider.setStateOptions(new ChannelUID(getThing().getUID(), CHANNEL_ZONE2_SOURCE),
406 getStateOptions(rotelModel.getZone2Sources(), sourcesCustomLabels));
408 if (rotelModel.hasZone3SourceControl()) {
409 stateDescriptionProvider.setStateOptions(new ChannelUID(getThing().getUID(), CHANNEL_ZONE3_SOURCE),
410 getStateOptions(rotelModel.getZone3Sources(), sourcesCustomLabels));
412 if (rotelModel.hasZone4SourceControl()) {
413 stateDescriptionProvider.setStateOptions(new ChannelUID(getThing().getUID(), CHANNEL_ZONE4_SOURCE),
414 getStateOptions(rotelModel.getZone4Sources(), sourcesCustomLabels));
416 if (rotelModel.hasDspControl()) {
417 stateDescriptionProvider.setStateOptions(new ChannelUID(getThing().getUID(), CHANNEL_DSP),
418 rotelModel.getDspStateOptions());
419 stateDescriptionProvider.setStateOptions(new ChannelUID(getThing().getUID(), CHANNEL_MAIN_DSP),
420 rotelModel.getDspStateOptions());
423 updateStatus(ThingStatus.UNKNOWN);
425 scheduleReconnectJob();
428 logger.debug("Finished initializing!");
432 public void dispose() {
433 logger.debug("Disposing handler for thing {}", getThing().getUID());
436 cancelPowerOnZone2Job();
437 cancelPowerOnZone3Job();
438 cancelPowerOnZone4Job();
439 cancelReconnectJob();
444 public List<StateOption> getStateOptions(List<RotelSource> list, Map<RotelSource, String> sourcesLabels) {
445 List<StateOption> options = new ArrayList<>();
446 for (RotelSource item : list) {
447 String label = sourcesLabels.get(item);
448 options.add(new StateOption(item.getName(), label == null ? ("@text/source." + item.getName()) : label));
454 public void handleCommand(ChannelUID channelUID, Command command) {
455 String channel = channelUID.getId();
457 if (getThing().getStatus() != ThingStatus.ONLINE) {
458 logger.debug("Thing is not ONLINE; command {} from channel {} is ignored", command, channel);
462 if (command instanceof RefreshType) {
463 updateChannelState(channel);
467 if (!connector.isConnected()) {
468 logger.debug("Command {} from channel {} is ignored: connection not established", command, channel);
474 boolean success = true;
475 synchronized (sequenceLock) {
479 case CHANNEL_MAIN_POWER:
480 handlePowerCmd(channel, command, getPowerOnCommand(), getPowerOffCommand());
482 case CHANNEL_ZONE2_POWER:
483 if (connector.getModel().hasZone2Commands()) {
484 handlePowerCmd(channel, command, RotelCommand.ZONE2_POWER_ON, RotelCommand.ZONE2_POWER_OFF);
485 } else if (connector.getModel().getNbAdditionalZones() == 1) {
486 if (isPowerOn() || powerZone2) {
487 selectZone(2, connector.getModel().getZoneSelectCmd());
489 connector.sendCommand(RotelCommand.ZONE_SELECT);
492 logger.debug("Command {} from channel {} failed: unavailable feature", command, channel);
495 case CHANNEL_ZONE3_POWER:
496 if (connector.getModel().hasZone3Commands()) {
497 handlePowerCmd(channel, command, RotelCommand.ZONE3_POWER_ON, RotelCommand.ZONE3_POWER_OFF);
500 logger.debug("Command {} from channel {} failed: unavailable feature", command, channel);
503 case CHANNEL_ZONE4_POWER:
504 if (connector.getModel().hasZone4Commands()) {
505 handlePowerCmd(channel, command, RotelCommand.ZONE4_POWER_ON, RotelCommand.ZONE4_POWER_OFF);
508 logger.debug("Command {} from channel {} failed: unavailable feature", command, channel);
512 case CHANNEL_MAIN_SOURCE:
515 logger.debug("Command {} from channel {} ignored: device in standby", command, channel);
517 src = connector.getModel().getSourceFromName(command.toString());
518 cmd = connector.getModel().hasOtherThanPrimaryCommands() ? src.getMainZoneCommand()
521 connector.sendCommand(cmd);
524 logger.debug("Command {} from channel {} failed: undefined source command", command,
529 case CHANNEL_MAIN_RECORD_SOURCE:
532 logger.debug("Command {} from channel {} ignored: device in standby", command, channel);
533 } else if (connector.getModel().hasOtherThanPrimaryCommands()) {
534 src = connector.getModel().getSourceFromName(command.toString());
535 cmd = src.getRecordCommand();
537 connector.sendCommand(cmd);
540 logger.debug("Command {} from channel {} failed: undefined record source command",
544 src = connector.getModel().getSourceFromName(command.toString());
545 cmd = src.getCommand();
547 connector.sendCommand(RotelCommand.RECORD_FONCTION_SELECT);
549 connector.sendCommand(cmd);
552 logger.debug("Command {} from channel {} failed: undefined source command", command,
557 case CHANNEL_ZONE2_SOURCE:
560 logger.debug("Command {} from channel {} ignored: zone 2 in standby", command, channel);
561 } else if (connector.getModel().hasZone2Commands()) {
562 src = connector.getModel().getSourceFromName(command.toString());
563 cmd = src.getZone2Command();
565 connector.sendCommand(cmd);
568 logger.debug("Command {} from channel {} failed: undefined zone 2 source command",
571 } else if (connector.getModel().getNbAdditionalZones() >= 1) {
572 src = connector.getModel().getSourceFromName(command.toString());
573 cmd = src.getCommand();
575 selectZone(2, connector.getModel().getZoneSelectCmd());
576 connector.sendCommand(cmd);
579 logger.debug("Command {} from channel {} failed: undefined source command", command,
584 logger.debug("Command {} from channel {} failed: unavailable feature", command, channel);
587 case CHANNEL_ZONE3_SOURCE:
590 logger.debug("Command {} from channel {} ignored: zone 3 in standby", command, channel);
591 } else if (connector.getModel().hasZone3Commands()) {
592 src = connector.getModel().getSourceFromName(command.toString());
593 cmd = src.getZone3Command();
595 connector.sendCommand(cmd);
598 logger.debug("Command {} from channel {} failed: undefined zone 3 source command",
603 logger.debug("Command {} from channel {} failed: unavailable feature", command, channel);
606 case CHANNEL_ZONE4_SOURCE:
609 logger.debug("Command {} from channel {} ignored: zone 4 in standby", command, channel);
610 } else if (connector.getModel().hasZone4Commands()) {
611 src = connector.getModel().getSourceFromName(command.toString());
612 cmd = src.getZone4Command();
614 connector.sendCommand(cmd);
617 logger.debug("Command {} from channel {} failed: undefined zone 4 source command",
622 logger.debug("Command {} from channel {} failed: unavailable feature", command, channel);
626 case CHANNEL_MAIN_DSP:
629 logger.debug("Command {} from channel {} ignored: device in standby", command, channel);
631 connector.sendCommand(connector.getModel().getCommandFromDspName(command.toString()));
635 case CHANNEL_MAIN_VOLUME:
638 logger.debug("Command {} from channel {} ignored: device in standby", command, channel);
639 } else if (connector.getModel().hasVolumeControl()) {
640 handleVolumeCmd(volume, channel, command, getVolumeUpCommand(), getVolumeDownCommand(),
641 RotelCommand.VOLUME_SET);
644 logger.debug("Command {} from channel {} failed: unavailable feature", command, channel);
647 case CHANNEL_MAIN_VOLUME_UP_DOWN:
650 logger.debug("Command {} from channel {} ignored: device in standby", command, channel);
651 } else if (connector.getModel().hasVolumeControl()) {
652 handleVolumeCmd(volume, channel, command, getVolumeUpCommand(), getVolumeDownCommand(),
656 logger.debug("Command {} from channel {} failed: unavailable feature", command, channel);
659 case CHANNEL_ZONE2_VOLUME:
662 logger.debug("Command {} from channel {} ignored: zone 2 in standby", command, channel);
663 } else if (fixedVolumeZone2) {
665 logger.debug("Command {} from channel {} ignored: fixed volume in zone 2", command,
667 } else if (connector.getModel().hasVolumeControl()
668 && connector.getModel().getNbAdditionalZones() >= 1) {
669 if (connector.getModel().hasZone2Commands()) {
670 handleVolumeCmd(volumeZone2, channel, command, RotelCommand.ZONE2_VOLUME_UP,
671 RotelCommand.ZONE2_VOLUME_DOWN, RotelCommand.ZONE2_VOLUME_SET);
673 selectZone(2, connector.getModel().getZoneSelectCmd());
674 handleVolumeCmd(volumeZone2, channel, command, RotelCommand.VOLUME_UP,
675 RotelCommand.VOLUME_DOWN, RotelCommand.VOLUME_SET);
679 logger.debug("Command {} from channel {} failed: unavailable feature", command, channel);
682 case CHANNEL_ZONE2_VOLUME_UP_DOWN:
685 logger.debug("Command {} from channel {} ignored: zone 2 in standby", command, channel);
686 } else if (fixedVolumeZone2) {
688 logger.debug("Command {} from channel {} ignored: fixed volume in zone 2", command,
690 } else if (connector.getModel().hasVolumeControl()
691 && connector.getModel().getNbAdditionalZones() >= 1) {
692 if (connector.getModel().hasZone2Commands()) {
693 handleVolumeCmd(volumeZone2, channel, command, RotelCommand.ZONE2_VOLUME_UP,
694 RotelCommand.ZONE2_VOLUME_DOWN, null);
696 selectZone(2, connector.getModel().getZoneSelectCmd());
697 handleVolumeCmd(volumeZone2, channel, command, RotelCommand.VOLUME_UP,
698 RotelCommand.VOLUME_DOWN, null);
702 logger.debug("Command {} from channel {} failed: unavailable feature", command, channel);
705 case CHANNEL_ZONE3_VOLUME:
708 logger.debug("Command {} from channel {} ignored: zone 3 in standby", command, channel);
709 } else if (fixedVolumeZone3) {
711 logger.debug("Command {} from channel {} ignored: fixed volume in zone 3", command,
713 } else if (connector.getModel().hasVolumeControl() && connector.getModel().hasZone3Commands()) {
714 handleVolumeCmd(volumeZone3, channel, command, RotelCommand.ZONE3_VOLUME_UP,
715 RotelCommand.ZONE3_VOLUME_DOWN, RotelCommand.ZONE3_VOLUME_SET);
718 logger.debug("Command {} from channel {} failed: unavailable feature", command, channel);
721 case CHANNEL_ZONE4_VOLUME:
724 logger.debug("Command {} from channel {} ignored: zone 4 in standby", command, channel);
725 } else if (fixedVolumeZone4) {
727 logger.debug("Command {} from channel {} ignored: fixed volume in zone 4", command,
729 } else if (connector.getModel().hasVolumeControl() && connector.getModel().hasZone4Commands()) {
730 handleVolumeCmd(volumeZone4, channel, command, RotelCommand.ZONE4_VOLUME_UP,
731 RotelCommand.ZONE4_VOLUME_DOWN, RotelCommand.ZONE4_VOLUME_SET);
734 logger.debug("Command {} from channel {} failed: unavailable feature", command, channel);
738 case CHANNEL_MAIN_MUTE:
741 logger.debug("Command {} from channel {} ignored: device in standby", command, channel);
742 } else if (connector.getModel().hasVolumeControl()) {
743 handleMuteCmd(connector.getProtocol() == RotelProtocol.HEX, channel, command,
744 getMuteOnCommand(), getMuteOffCommand(), getMuteToggleCommand());
747 logger.debug("Command {} from channel {} failed: unavailable feature", command, channel);
750 case CHANNEL_ZONE2_MUTE:
753 logger.debug("Command {} from channel {} ignored: zone 2 in standby", command, channel);
754 } else if (connector.getModel().hasVolumeControl() && connector.getModel().hasZone2Commands()) {
755 handleMuteCmd(false, channel, command, RotelCommand.ZONE2_MUTE_ON,
756 RotelCommand.ZONE2_MUTE_OFF, RotelCommand.ZONE2_MUTE_TOGGLE);
759 logger.debug("Command {} from channel {} failed: unavailable feature", command, channel);
762 case CHANNEL_ZONE3_MUTE:
765 logger.debug("Command {} from channel {} ignored: zone 3 in standby", command, channel);
766 } else if (connector.getModel().hasVolumeControl() && connector.getModel().hasZone3Commands()) {
767 handleMuteCmd(false, channel, command, RotelCommand.ZONE3_MUTE_ON,
768 RotelCommand.ZONE3_MUTE_OFF, RotelCommand.ZONE3_MUTE_TOGGLE);
771 logger.debug("Command {} from channel {} failed: unavailable feature", command, channel);
774 case CHANNEL_ZONE4_MUTE:
777 logger.debug("Command {} from channel {} ignored: zone 4 in standby", command, channel);
778 } else if (connector.getModel().hasVolumeControl() && connector.getModel().hasZone4Commands()) {
779 handleMuteCmd(false, channel, command, RotelCommand.ZONE4_MUTE_ON,
780 RotelCommand.ZONE4_MUTE_OFF, RotelCommand.ZONE4_MUTE_TOGGLE);
783 logger.debug("Command {} from channel {} failed: unavailable feature", command, channel);
787 case CHANNEL_MAIN_BASS:
790 logger.debug("Command {} from channel {} ignored: device in standby", command, channel);
792 handleToneCmd(bass, channel, command, 2, RotelCommand.BASS_UP, RotelCommand.BASS_DOWN,
793 RotelCommand.BASS_SET);
797 case CHANNEL_MAIN_TREBLE:
800 logger.debug("Command {} from channel {} ignored: device in standby", command, channel);
802 handleToneCmd(treble, channel, command, 1, RotelCommand.TREBLE_UP, RotelCommand.TREBLE_DOWN,
803 RotelCommand.TREBLE_SET);
806 case CHANNEL_PLAY_CONTROL:
809 logger.debug("Command {} from channel {} ignored: device in standby", command, channel);
810 } else if (command instanceof PlayPauseType && command == PlayPauseType.PLAY) {
811 connector.sendCommand(RotelCommand.PLAY);
812 } else if (command instanceof PlayPauseType && command == PlayPauseType.PAUSE) {
813 connector.sendCommand(RotelCommand.PAUSE);
814 if (connector.getProtocol() == RotelProtocol.ASCII_V1
815 && connector.getModel() != RotelModel.RCD1570
816 && connector.getModel() != RotelModel.RCD1572
817 && connector.getModel() != RotelModel.RCX1500) {
819 connector.sendCommand(RotelCommand.PLAY_STATUS);
821 } else if (command instanceof NextPreviousType && command == NextPreviousType.NEXT) {
822 connector.sendCommand(RotelCommand.TRACK_FORWARD);
823 } else if (command instanceof NextPreviousType && command == NextPreviousType.PREVIOUS) {
824 connector.sendCommand(RotelCommand.TRACK_BACKWORD);
827 logger.debug("Command {} from channel {} failed: invalid command value", command, channel);
830 case CHANNEL_BRIGHTNESS:
833 logger.debug("Command {} from channel {} ignored: device in standby", command, channel);
834 } else if (!connector.getModel().hasDimmerControl()) {
836 logger.debug("Command {} from channel {} failed: unavailable feature", command, channel);
837 } else if (command instanceof PercentType) {
838 int dimmer = (int) Math.round(((PercentType) command).doubleValue() / 100.0
839 * (connector.getModel().getDimmerLevelMax()
840 - connector.getModel().getDimmerLevelMin()))
841 + connector.getModel().getDimmerLevelMin();
842 connector.sendCommand(RotelCommand.DIMMER_LEVEL_SET, dimmer);
845 logger.debug("Command {} from channel {} failed: invalid command value", command, channel);
850 logger.debug("Command {} from channel {} failed: nnexpected command", command, channel);
854 logger.debug("Command {} from channel {} succeeded", command, channel);
856 updateChannelState(channel);
858 } catch (RotelException e) {
859 logger.debug("Command {} from channel {} failed: {}", command, channel, e.getMessage());
860 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
861 "@text/offline.comm-error-sending-command");
863 scheduleReconnectJob();
864 } catch (InterruptedException e) {
865 logger.debug("Command {} from channel {} interrupted: {}", command, channel, e.getMessage());
866 Thread.currentThread().interrupt();
872 * Handle a power ON/OFF command
874 * @param channel the channel
875 * @param command the received channel command (OnOffType)
876 * @param onCmd the command to be sent to the device to power it ON
877 * @param offCmd the command to be sent to the device to power it OFF
879 * @throws RotelException in case of communication error with the device
881 private void handlePowerCmd(String channel, Command command, RotelCommand onCmd, RotelCommand offCmd)
882 throws RotelException {
883 if (command instanceof OnOffType && command == OnOffType.ON) {
884 connector.sendCommand(onCmd);
885 } else if (command instanceof OnOffType && command == OnOffType.OFF) {
886 connector.sendCommand(offCmd);
888 logger.debug("Command {} from channel {} failed: invalid command value", command, channel);
893 * Handle a volume command
895 * @param current the current volume
896 * @param channel the channel
897 * @param command the received channel command (IncreaseDecreaseType or DecimalType)
898 * @param upCmd the command to be sent to the device to increase the volume
899 * @param downCmd the command to be sent to the device to decrease the volume
900 * @param setCmd the command to be sent to the device to set the volume at a value
902 * @throws RotelException in case of communication error with the device
904 private void handleVolumeCmd(int current, String channel, Command command, RotelCommand upCmd, RotelCommand downCmd,
905 @Nullable RotelCommand setCmd) throws RotelException {
906 if (command instanceof IncreaseDecreaseType && command == IncreaseDecreaseType.INCREASE) {
907 connector.sendCommand(upCmd);
908 } else if (command instanceof IncreaseDecreaseType && command == IncreaseDecreaseType.DECREASE) {
909 connector.sendCommand(downCmd);
910 } else if (command instanceof DecimalType && setCmd == null) {
911 int value = ((DecimalType) command).intValue();
912 if (value >= minVolume && value <= maxVolume) {
913 if (value > current) {
914 connector.sendCommand(upCmd);
915 } else if (value < current) {
916 connector.sendCommand(downCmd);
919 } else if (command instanceof PercentType && setCmd != null) {
920 int value = (int) Math.round(((PercentType) command).doubleValue() / 100.0 * (maxVolume - minVolume))
922 connector.sendCommand(setCmd, value);
924 logger.debug("Command {} from channel {} failed: invalid command value", command, channel);
929 * Handle a mute command
931 * @param onlyToggle true if only the toggle command must be used
932 * @param channel the channel
933 * @param command the received channel command (OnOffType)
934 * @param onCmd the command to be sent to the device to mute
935 * @param offCmd the command to be sent to the device to unmute
936 * @param toggleCmd the command to be sent to the device to toggle the mute state
938 * @throws RotelException in case of communication error with the device
940 private void handleMuteCmd(boolean onlyToggle, String channel, Command command, RotelCommand onCmd,
941 RotelCommand offCmd, RotelCommand toggleCmd) throws RotelException {
942 if (command instanceof OnOffType) {
944 connector.sendCommand(toggleCmd);
945 } else if (command == OnOffType.ON) {
946 connector.sendCommand(onCmd);
947 } else if (command == OnOffType.OFF) {
948 connector.sendCommand(offCmd);
951 logger.debug("Command {} from channel {} failed: invalid command value", command, channel);
956 * Handle a tone level adjustment command (bass or treble)
958 * @param current the current tone level
959 * @param channel the channel
960 * @param command the received channel command (IncreaseDecreaseType or DecimalType)
961 * @param nbSelect the number of TONE_CONTROL_SELECT commands to be run to display the right tone (bass or treble)
962 * @param upCmd the command to be sent to the device to increase the tone level
963 * @param downCmd the command to be sent to the device to decrease the tone level
964 * @param setCmd the command to be sent to the device to set the tone level at a value
966 * @throws RotelException in case of communication error with the device
967 * @throws InterruptedException in case of interruption during a thread sleep
969 private void handleToneCmd(int current, String channel, Command command, int nbSelect, RotelCommand upCmd,
970 RotelCommand downCmd, RotelCommand setCmd) throws RotelException, InterruptedException {
971 if (command instanceof IncreaseDecreaseType && command == IncreaseDecreaseType.INCREASE) {
972 selectToneControl(nbSelect);
973 connector.sendCommand(upCmd);
974 } else if (command instanceof IncreaseDecreaseType && command == IncreaseDecreaseType.DECREASE) {
975 selectToneControl(nbSelect);
976 connector.sendCommand(downCmd);
977 } else if (command instanceof DecimalType) {
978 int value = ((DecimalType) command).intValue();
979 if (value >= minToneLevel && value <= maxToneLevel) {
980 if (connector.getProtocol() != RotelProtocol.HEX) {
981 connector.sendCommand(setCmd, value);
982 } else if (value > current) {
983 selectToneControl(nbSelect);
984 connector.sendCommand(upCmd);
985 } else if (value < current) {
986 selectToneControl(nbSelect);
987 connector.sendCommand(downCmd);
991 logger.debug("Command {} from channel {} failed: invalid command value", command, channel);
996 * Run a sequence of commands to display the current tone level (bass or treble) on the device front panel
998 * @param nbSelect the number of TONE_CONTROL_SELECT commands to be run to display the right tone (bass or treble)
1000 * @throws RotelException in case of communication error with the device
1001 * @throws InterruptedException in case of interruption during a thread sleep
1003 private void selectToneControl(int nbSelect) throws RotelException, InterruptedException {
1004 // No tone control select command for RSX-1065
1005 if (connector.getProtocol() == RotelProtocol.HEX && connector.getModel() != RotelModel.RSX1065) {
1006 selectFeature(nbSelect, RotelCommand.RECORD_FONCTION_SELECT, RotelCommand.TONE_CONTROL_SELECT);
1011 * Run a sequence of commands to display a particular zone on the device front panel
1013 * @param zone the zone to be displayed (1 for main zone)
1014 * @param selectCommand the command to be sent to the device to switch the display between zones
1016 * @throws RotelException in case of communication error with the device
1017 * @throws InterruptedException in case of interruption during a thread sleep
1019 private void selectZone(int zone, @Nullable RotelCommand selectCommand)
1020 throws RotelException, InterruptedException {
1021 if (connector.getProtocol() == RotelProtocol.HEX && connector.getModel().getNbAdditionalZones() >= 1
1022 && zone >= 1 && zone != currentZone && selectCommand != null) {
1024 if (zone < currentZone) {
1025 nbSelect = zone + connector.getModel().getNbAdditionalZones() - currentZone;
1026 if (isPowerOn() && selectCommand == RotelCommand.RECORD_FONCTION_SELECT) {
1030 nbSelect = zone - currentZone;
1031 if (isPowerOn() && currentZone == 1 && selectCommand == RotelCommand.RECORD_FONCTION_SELECT
1032 && !selectingRecord) {
1036 selectFeature(nbSelect, null, selectCommand);
1041 * Run a sequence of commands to display a particular feature on the device front panel
1043 * @param nbSelect the number of select commands to be run
1044 * @param preCmd the initial command to be sent to the device (before the select commands)
1045 * @param selectCmd the select command to be sent to the device
1047 * @throws RotelException in case of communication error with the device
1048 * @throws InterruptedException in case of interruption during a thread sleep
1050 private void selectFeature(int nbSelect, @Nullable RotelCommand preCmd, RotelCommand selectCmd)
1051 throws RotelException, InterruptedException {
1052 if (connector.getProtocol() == RotelProtocol.HEX) {
1053 if (preCmd != null) {
1054 connector.sendCommand(preCmd);
1057 for (int i = 1; i <= nbSelect; i++) {
1058 connector.sendCommand(selectCmd);
1065 * Open the connection with the Rotel device
1067 * @return true if the connection is opened successfully or flase if not
1069 private synchronized boolean openConnection() {
1070 connector.addEventListener(this);
1073 } catch (RotelException e) {
1074 logger.debug("openConnection() failed", e);
1076 logger.debug("openConnection(): {}", connector.isConnected() ? "connected" : "disconnected");
1077 return connector.isConnected();
1081 * Close the connection with the Rotel device
1083 private synchronized void closeConnection() {
1085 connector.removeEventListener(this);
1086 logger.debug("closeConnection(): disconnected");
1090 public void onNewMessageEvent(EventObject event) {
1091 cancelPowerOffJob();
1093 RotelMessageEvent evt = (RotelMessageEvent) event;
1094 logger.debug("onNewMessageEvent: key {} = {}", evt.getKey(), evt.getValue());
1096 String key = evt.getKey();
1097 String value = evt.getValue().trim();
1098 if (!RotelConnector.KEY_ERROR.equals(key)) {
1099 updateStatus(ThingStatus.ONLINE);
1103 case RotelConnector.KEY_ERROR:
1104 logger.debug("Reading feedback message failed");
1105 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
1106 "@text/offline.comm-error-reading-thread");
1109 case RotelConnector.KEY_LINE1:
1110 frontPanelLine1 = value;
1111 updateChannelState(CHANNEL_LINE1);
1113 case RotelConnector.KEY_LINE2:
1114 frontPanelLine2 = value;
1115 updateChannelState(CHANNEL_LINE2);
1117 case RotelConnector.KEY_ZONE:
1118 currentZone = Integer.parseInt(value);
1120 case RotelConnector.KEY_RECORD_SEL:
1121 selectingRecord = RotelConnector.MSG_VALUE_ON.equalsIgnoreCase(value);
1123 case RotelConnector.KEY_POWER:
1124 if (RotelConnector.POWER_ON.equalsIgnoreCase(value)) {
1126 } else if (RotelConnector.STANDBY.equalsIgnoreCase(value)) {
1128 } else if (RotelConnector.POWER_OFF_DELAYED.equalsIgnoreCase(value)) {
1129 schedulePowerOffJob(false);
1131 throw new RotelException("Invalid value");
1134 case RotelConnector.KEY_POWER_ZONE2:
1135 if (RotelConnector.POWER_ON.equalsIgnoreCase(value)) {
1136 handlePowerOnZone2();
1137 } else if (RotelConnector.STANDBY.equalsIgnoreCase(value)) {
1138 handlePowerOffZone2();
1140 throw new RotelException("Invalid value");
1143 case RotelConnector.KEY_POWER_ZONE3:
1144 if (RotelConnector.POWER_ON.equalsIgnoreCase(value)) {
1145 handlePowerOnZone3();
1146 } else if (RotelConnector.STANDBY.equalsIgnoreCase(value)) {
1147 handlePowerOffZone3();
1149 throw new RotelException("Invalid value");
1152 case RotelConnector.KEY_POWER_ZONE4:
1153 if (RotelConnector.POWER_ON.equalsIgnoreCase(value)) {
1154 handlePowerOnZone4();
1155 } else if (RotelConnector.STANDBY.equalsIgnoreCase(value)) {
1156 handlePowerOffZone4();
1158 throw new RotelException("Invalid value");
1161 case RotelConnector.KEY_VOLUME_MIN:
1162 minVolume = Integer.parseInt(value);
1163 if (!connector.getModel().hasDirectVolumeControl()) {
1164 logger.info("Set minValue to {} for your sitemap widget attached to your volume item.",
1168 case RotelConnector.KEY_VOLUME_MAX:
1169 maxVolume = Integer.parseInt(value);
1170 if (!connector.getModel().hasDirectVolumeControl()) {
1171 logger.info("Set maxValue to {} for your sitemap widget attached to your volume item.",
1175 case RotelConnector.KEY_VOLUME:
1176 if (RotelConnector.MSG_VALUE_MIN.equalsIgnoreCase(value)) {
1178 } else if (RotelConnector.MSG_VALUE_MAX.equalsIgnoreCase(value)) {
1181 volume = Integer.parseInt(value);
1183 updateChannelState(CHANNEL_VOLUME);
1184 updateChannelState(CHANNEL_MAIN_VOLUME);
1185 updateChannelState(CHANNEL_MAIN_VOLUME_UP_DOWN);
1187 case RotelConnector.KEY_MUTE:
1188 if (RotelConnector.MSG_VALUE_ON.equalsIgnoreCase(value)) {
1190 updateChannelState(CHANNEL_MUTE);
1191 updateChannelState(CHANNEL_MAIN_MUTE);
1192 } else if (RotelConnector.MSG_VALUE_OFF.equalsIgnoreCase(value)) {
1194 updateChannelState(CHANNEL_MUTE);
1195 updateChannelState(CHANNEL_MAIN_MUTE);
1197 throw new RotelException("Invalid value");
1200 case RotelConnector.KEY_VOLUME_ZONE2:
1201 fixedVolumeZone2 = false;
1202 if (RotelConnector.MSG_VALUE_FIX.equalsIgnoreCase(value)) {
1203 fixedVolumeZone2 = true;
1204 } else if (RotelConnector.MSG_VALUE_MIN.equalsIgnoreCase(value)) {
1205 volumeZone2 = minVolume;
1206 } else if (RotelConnector.MSG_VALUE_MAX.equalsIgnoreCase(value)) {
1207 volumeZone2 = maxVolume;
1209 volumeZone2 = Integer.parseInt(value);
1211 updateChannelState(CHANNEL_ZONE2_VOLUME);
1212 updateChannelState(CHANNEL_ZONE2_VOLUME_UP_DOWN);
1214 case RotelConnector.KEY_VOLUME_ZONE3:
1215 fixedVolumeZone3 = false;
1216 if (RotelConnector.MSG_VALUE_FIX.equalsIgnoreCase(value)) {
1217 fixedVolumeZone3 = true;
1218 } else if (RotelConnector.MSG_VALUE_MIN.equalsIgnoreCase(value)) {
1219 volumeZone3 = minVolume;
1220 } else if (RotelConnector.MSG_VALUE_MAX.equalsIgnoreCase(value)) {
1221 volumeZone3 = maxVolume;
1223 volumeZone3 = Integer.parseInt(value);
1225 updateChannelState(CHANNEL_ZONE3_VOLUME);
1227 case RotelConnector.KEY_VOLUME_ZONE4:
1228 fixedVolumeZone4 = false;
1229 if (RotelConnector.MSG_VALUE_FIX.equalsIgnoreCase(value)) {
1230 fixedVolumeZone4 = true;
1231 } else if (RotelConnector.MSG_VALUE_MIN.equalsIgnoreCase(value)) {
1232 volumeZone4 = minVolume;
1233 } else if (RotelConnector.MSG_VALUE_MAX.equalsIgnoreCase(value)) {
1234 volumeZone4 = maxVolume;
1236 volumeZone4 = Integer.parseInt(value);
1238 updateChannelState(CHANNEL_ZONE4_VOLUME);
1240 case RotelConnector.KEY_MUTE_ZONE2:
1241 if (RotelConnector.MSG_VALUE_ON.equalsIgnoreCase(value)) {
1243 updateChannelState(CHANNEL_ZONE2_MUTE);
1244 } else if (RotelConnector.MSG_VALUE_OFF.equalsIgnoreCase(value)) {
1246 updateChannelState(CHANNEL_ZONE2_MUTE);
1248 throw new RotelException("Invalid value");
1251 case RotelConnector.KEY_MUTE_ZONE3:
1252 if (RotelConnector.MSG_VALUE_ON.equalsIgnoreCase(value)) {
1254 updateChannelState(CHANNEL_ZONE3_MUTE);
1255 } else if (RotelConnector.MSG_VALUE_OFF.equalsIgnoreCase(value)) {
1257 updateChannelState(CHANNEL_ZONE3_MUTE);
1259 throw new RotelException("Invalid value");
1262 case RotelConnector.KEY_MUTE_ZONE4:
1263 if (RotelConnector.MSG_VALUE_ON.equalsIgnoreCase(value)) {
1265 updateChannelState(CHANNEL_ZONE4_MUTE);
1266 } else if (RotelConnector.MSG_VALUE_OFF.equalsIgnoreCase(value)) {
1268 updateChannelState(CHANNEL_ZONE4_MUTE);
1270 throw new RotelException("Invalid value");
1273 case RotelConnector.KEY_TONE_MAX:
1274 maxToneLevel = Integer.parseInt(value);
1275 minToneLevel = -maxToneLevel;
1277 "Set minValue to {} and maxValue to {} for your sitemap widget attached to your bass or treble item.",
1278 minToneLevel, maxToneLevel);
1280 case RotelConnector.KEY_BASS:
1281 if (RotelConnector.MSG_VALUE_MIN.equalsIgnoreCase(value)) {
1282 bass = minToneLevel;
1283 } else if (RotelConnector.MSG_VALUE_MAX.equalsIgnoreCase(value)) {
1284 bass = maxToneLevel;
1286 bass = Integer.parseInt(value);
1288 updateChannelState(CHANNEL_BASS);
1289 updateChannelState(CHANNEL_MAIN_BASS);
1291 case RotelConnector.KEY_TREBLE:
1292 if (RotelConnector.MSG_VALUE_MIN.equalsIgnoreCase(value)) {
1293 treble = minToneLevel;
1294 } else if (RotelConnector.MSG_VALUE_MAX.equalsIgnoreCase(value)) {
1295 treble = maxToneLevel;
1297 treble = Integer.parseInt(value);
1299 updateChannelState(CHANNEL_TREBLE);
1300 updateChannelState(CHANNEL_MAIN_TREBLE);
1302 case RotelConnector.KEY_SOURCE:
1303 source = connector.getModel().getSourceFromCommand(RotelCommand.getFromAsciiCommand(value));
1304 updateChannelState(CHANNEL_SOURCE);
1305 updateChannelState(CHANNEL_MAIN_SOURCE);
1307 case RotelConnector.KEY_RECORD:
1308 recordSource = connector.getModel()
1309 .getRecordSourceFromCommand(RotelCommand.getFromAsciiCommand(value));
1310 updateChannelState(CHANNEL_MAIN_RECORD_SOURCE);
1312 case RotelConnector.KEY_SOURCE_ZONE2:
1313 sourceZone2 = connector.getModel()
1314 .getZone2SourceFromCommand(RotelCommand.getFromAsciiCommand(value));
1315 updateChannelState(CHANNEL_ZONE2_SOURCE);
1317 case RotelConnector.KEY_SOURCE_ZONE3:
1318 sourceZone3 = connector.getModel()
1319 .getZone3SourceFromCommand(RotelCommand.getFromAsciiCommand(value));
1320 updateChannelState(CHANNEL_ZONE3_SOURCE);
1322 case RotelConnector.KEY_SOURCE_ZONE4:
1323 sourceZone4 = connector.getModel()
1324 .getZone4SourceFromCommand(RotelCommand.getFromAsciiCommand(value));
1325 updateChannelState(CHANNEL_ZONE4_SOURCE);
1327 case RotelConnector.KEY_DSP_MODE:
1328 if ("dolby_pliix_movie".equals(value)) {
1329 value = "dolby_plii_movie";
1330 } else if ("dolby_pliix_music".equals(value)) {
1331 value = "dolby_plii_music";
1332 } else if ("dolby_pliix_game".equals(value)) {
1333 value = "dolby_plii_game";
1335 dsp = connector.getModel().getDspFromFeedback(value);
1336 logger.debug("DSP {}", dsp.getName());
1337 updateChannelState(CHANNEL_DSP);
1338 updateChannelState(CHANNEL_MAIN_DSP);
1340 case RotelConnector.KEY1_PLAY_STATUS:
1341 case RotelConnector.KEY2_PLAY_STATUS:
1342 if (RotelConnector.PLAY.equalsIgnoreCase(value)) {
1343 playStatus = RotelPlayStatus.PLAYING;
1344 updateChannelState(CHANNEL_PLAY_CONTROL);
1345 } else if (RotelConnector.PAUSE.equalsIgnoreCase(value)) {
1346 playStatus = RotelPlayStatus.PAUSED;
1347 updateChannelState(CHANNEL_PLAY_CONTROL);
1348 } else if (RotelConnector.STOP.equalsIgnoreCase(value)) {
1349 playStatus = RotelPlayStatus.STOPPED;
1350 updateChannelState(CHANNEL_PLAY_CONTROL);
1352 throw new RotelException("Invalid value");
1355 case RotelConnector.KEY_TRACK:
1356 if (source.getName().equals("CD") && !connector.getModel().hasSourceControl()) {
1357 track = Integer.parseInt(value);
1358 updateChannelState(CHANNEL_TRACK);
1361 case RotelConnector.KEY_FREQ:
1362 if (RotelConnector.MSG_VALUE_OFF.equalsIgnoreCase(value)) {
1365 // Suppress a potential ending "k" or "K"
1366 if (value.toUpperCase().endsWith("K")) {
1367 value = value.substring(0, value.length() - 1);
1369 frequency = Double.parseDouble(value);
1371 updateChannelState(CHANNEL_FREQUENCY);
1373 case RotelConnector.KEY_DIMMER:
1374 brightness = Integer.parseInt(value);
1375 updateChannelState(CHANNEL_BRIGHTNESS);
1377 case RotelConnector.KEY_UPDATE_MODE:
1378 case RotelConnector.KEY_DISPLAY_UPDATE:
1381 logger.debug("onNewMessageEvent: unhandled key {}", key);
1384 } catch (NumberFormatException | RotelException e) {
1385 logger.debug("Invalid value {} for key {}", value, key);
1390 * Handle the received information that device power (main zone) is ON
1392 private void handlePowerOn() {
1393 Boolean prev = power;
1395 updateChannelState(CHANNEL_POWER);
1396 updateChannelState(CHANNEL_MAIN_POWER);
1397 if ((prev == null) || !prev) {
1398 schedulePowerOnJob();
1403 * Handle the received information that device power (main zone) is OFF
1405 private void handlePowerOff() {
1408 updateChannelState(CHANNEL_POWER);
1409 updateChannelState(CHANNEL_MAIN_POWER);
1410 updateChannelState(CHANNEL_SOURCE);
1411 updateChannelState(CHANNEL_MAIN_SOURCE);
1412 updateChannelState(CHANNEL_MAIN_RECORD_SOURCE);
1413 updateChannelState(CHANNEL_DSP);
1414 updateChannelState(CHANNEL_MAIN_DSP);
1415 updateChannelState(CHANNEL_VOLUME);
1416 updateChannelState(CHANNEL_MAIN_VOLUME);
1417 updateChannelState(CHANNEL_MAIN_VOLUME_UP_DOWN);
1418 updateChannelState(CHANNEL_MUTE);
1419 updateChannelState(CHANNEL_MAIN_MUTE);
1420 updateChannelState(CHANNEL_BASS);
1421 updateChannelState(CHANNEL_MAIN_BASS);
1422 updateChannelState(CHANNEL_TREBLE);
1423 updateChannelState(CHANNEL_MAIN_TREBLE);
1424 updateChannelState(CHANNEL_PLAY_CONTROL);
1425 updateChannelState(CHANNEL_TRACK);
1426 updateChannelState(CHANNEL_FREQUENCY);
1427 updateChannelState(CHANNEL_BRIGHTNESS);
1431 * Handle the received information that zone 2 power is ON
1433 private void handlePowerOnZone2() {
1434 boolean prev = powerZone2;
1436 updateChannelState(CHANNEL_ZONE2_POWER);
1438 schedulePowerOnZone2Job();
1443 * Handle the received information that zone 2 power is OFF
1445 private void handlePowerOffZone2() {
1446 cancelPowerOnZone2Job();
1448 updateChannelState(CHANNEL_ZONE2_POWER);
1449 updateChannelState(CHANNEL_ZONE2_SOURCE);
1450 updateChannelState(CHANNEL_ZONE2_VOLUME);
1451 updateChannelState(CHANNEL_ZONE2_VOLUME_UP_DOWN);
1452 updateChannelState(CHANNEL_ZONE2_MUTE);
1456 * Handle the received information that zone 3 power is ON
1458 private void handlePowerOnZone3() {
1459 boolean prev = powerZone3;
1461 updateChannelState(CHANNEL_ZONE3_POWER);
1463 schedulePowerOnZone3Job();
1468 * Handle the received information that zone 3 power is OFF
1470 private void handlePowerOffZone3() {
1471 cancelPowerOnZone3Job();
1473 updateChannelState(CHANNEL_ZONE3_POWER);
1474 updateChannelState(CHANNEL_ZONE3_SOURCE);
1475 updateChannelState(CHANNEL_ZONE3_VOLUME);
1476 updateChannelState(CHANNEL_ZONE3_MUTE);
1480 * Handle the received information that zone 4 power is ON
1482 private void handlePowerOnZone4() {
1483 boolean prev = powerZone4;
1485 updateChannelState(CHANNEL_ZONE4_POWER);
1487 schedulePowerOnZone4Job();
1492 * Handle the received information that zone 4 power is OFF
1494 private void handlePowerOffZone4() {
1495 cancelPowerOnZone4Job();
1497 updateChannelState(CHANNEL_ZONE4_POWER);
1498 updateChannelState(CHANNEL_ZONE4_SOURCE);
1499 updateChannelState(CHANNEL_ZONE4_VOLUME);
1500 updateChannelState(CHANNEL_ZONE4_MUTE);
1504 * Schedule the job that will consider the device as OFF if no new event is received before its running
1506 * @param switchOffAllZones true if all zones have to be considered as OFF
1508 private void schedulePowerOffJob(boolean switchOffAllZones) {
1509 logger.debug("Schedule power OFF job");
1510 cancelPowerOffJob();
1511 powerOffJob = scheduler.schedule(() -> {
1512 logger.debug("Power OFF job");
1514 if (switchOffAllZones) {
1515 handlePowerOffZone2();
1516 handlePowerOffZone3();
1517 handlePowerOffZone4();
1519 }, 2000, TimeUnit.MILLISECONDS);
1523 * Cancel the job that will consider the device as OFF
1525 private void cancelPowerOffJob() {
1526 ScheduledFuture<?> powerOffJob = this.powerOffJob;
1527 if (powerOffJob != null && !powerOffJob.isCancelled()) {
1528 powerOffJob.cancel(true);
1529 this.powerOffJob = null;
1534 * Schedule the job to run with a few seconds delay when the device power (main zone) switched ON
1536 private void schedulePowerOnJob() {
1537 logger.debug("Schedule power ON job");
1539 powerOnJob = scheduler.schedule(() -> {
1540 synchronized (sequenceLock) {
1541 logger.debug("Power ON job");
1543 switch (connector.getProtocol()) {
1545 if (connector.getModel().getRespNbChars() <= 13
1546 && connector.getModel().hasVolumeControl()) {
1547 connector.sendCommand(getVolumeDownCommand());
1549 connector.sendCommand(getVolumeUpCommand());
1552 if (connector.getModel().getNbAdditionalZones() >= 1) {
1553 if (currentZone != 1 && connector.getModel()
1554 .getZoneSelectCmd() == RotelCommand.RECORD_FONCTION_SELECT) {
1555 selectZone(1, connector.getModel().getZoneSelectCmd());
1556 } else if (!selectingRecord) {
1557 connector.sendCommand(RotelCommand.RECORD_FONCTION_SELECT);
1561 connector.sendCommand(RotelCommand.RECORD_FONCTION_SELECT);
1564 if (connector.getModel().hasToneControl()) {
1565 if (connector.getModel() == RotelModel.RSX1065) {
1566 // No tone control select command
1567 connector.sendCommand(RotelCommand.TREBLE_DOWN);
1569 connector.sendCommand(RotelCommand.TREBLE_UP);
1571 connector.sendCommand(RotelCommand.BASS_DOWN);
1573 connector.sendCommand(RotelCommand.BASS_UP);
1576 selectFeature(2, null, RotelCommand.TONE_CONTROL_SELECT);
1581 if (connector.getModel() != RotelModel.RAP1580 && connector.getModel() != RotelModel.RDD1580
1582 && connector.getModel() != RotelModel.RSP1576
1583 && connector.getModel() != RotelModel.RSP1582) {
1584 connector.sendCommand(RotelCommand.UPDATE_AUTO);
1587 if (connector.getModel().hasSourceControl()) {
1588 connector.sendCommand(RotelCommand.SOURCE);
1591 if (connector.getModel().hasVolumeControl() || connector.getModel().hasToneControl()) {
1592 if (connector.getModel().hasVolumeControl()
1593 && connector.getModel() != RotelModel.RAP1580
1594 && connector.getModel() != RotelModel.RSP1576
1595 && connector.getModel() != RotelModel.RSP1582) {
1596 connector.sendCommand(RotelCommand.VOLUME_GET_MIN);
1598 connector.sendCommand(RotelCommand.VOLUME_GET_MAX);
1601 if (connector.getModel().hasToneControl()) {
1602 connector.sendCommand(RotelCommand.TONE_MAX);
1605 // Wait enough to be sure to get the min/max values requested just before
1607 if (connector.getModel().hasVolumeControl()) {
1608 connector.sendCommand(RotelCommand.VOLUME_GET);
1610 if (connector.getModel() != RotelModel.RA11
1611 && connector.getModel() != RotelModel.RA12
1612 && connector.getModel() != RotelModel.RCX1500) {
1613 connector.sendCommand(RotelCommand.MUTE);
1617 if (connector.getModel().hasToneControl()) {
1618 connector.sendCommand(RotelCommand.BASS);
1620 connector.sendCommand(RotelCommand.TREBLE);
1624 if (connector.getModel().hasPlayControl()) {
1625 if (connector.getModel() != RotelModel.RCD1570
1626 && connector.getModel() != RotelModel.RCD1572
1627 && (connector.getModel() != RotelModel.RCX1500
1628 || !source.getName().equals("CD"))) {
1629 connector.sendCommand(RotelCommand.PLAY_STATUS);
1632 connector.sendCommand(RotelCommand.CD_PLAY_STATUS);
1636 if (connector.getModel().hasDspControl()) {
1637 connector.sendCommand(RotelCommand.DSP_MODE);
1640 if (connector.getModel().canGetFrequency()) {
1641 connector.sendCommand(RotelCommand.FREQUENCY);
1644 if (connector.getModel().hasDimmerControl() && connector.getModel().canGetDimmerLevel()) {
1645 connector.sendCommand(RotelCommand.DIMMER_LEVEL_GET);
1650 connector.sendCommand(RotelCommand.UPDATE_AUTO);
1652 if (connector.getModel().hasSourceControl()) {
1653 connector.sendCommand(RotelCommand.SOURCE);
1656 if (connector.getModel().hasVolumeControl()) {
1657 connector.sendCommand(RotelCommand.VOLUME_GET);
1659 connector.sendCommand(RotelCommand.MUTE);
1662 if (connector.getModel().hasToneControl()) {
1663 connector.sendCommand(RotelCommand.BASS);
1665 connector.sendCommand(RotelCommand.TREBLE);
1668 if (connector.getModel().hasPlayControl()) {
1669 connector.sendCommand(RotelCommand.PLAY_STATUS);
1671 if (source.getName().equals("CD") && !connector.getModel().hasSourceControl()) {
1672 connector.sendCommand(RotelCommand.TRACK);
1676 if (connector.getModel().hasDspControl()) {
1677 connector.sendCommand(RotelCommand.DSP_MODE);
1680 if (connector.getModel().canGetFrequency()) {
1681 connector.sendCommand(RotelCommand.FREQUENCY);
1684 if (connector.getModel().hasDimmerControl() && connector.getModel().canGetDimmerLevel()) {
1685 connector.sendCommand(RotelCommand.DIMMER_LEVEL_GET);
1690 } catch (RotelException e) {
1691 logger.debug("Init sequence failed: {}", e.getMessage());
1692 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
1693 "@text/offline.comm-error-init-sequence");
1695 } catch (InterruptedException e) {
1696 logger.debug("Init sequence interrupted: {}", e.getMessage());
1697 Thread.currentThread().interrupt();
1700 }, 2500, TimeUnit.MILLISECONDS);
1704 * Cancel the job scheduled when the device power (main zone) switched ON
1706 private void cancelPowerOnJob() {
1707 ScheduledFuture<?> powerOnJob = this.powerOnJob;
1708 if (powerOnJob != null && !powerOnJob.isCancelled()) {
1709 powerOnJob.cancel(true);
1710 this.powerOnJob = null;
1715 * Schedule the job to run with a few seconds delay when the zone 2 power switched ON
1717 private void schedulePowerOnZone2Job() {
1718 logger.debug("Schedule power ON zone 2 job");
1719 cancelPowerOnZone2Job();
1720 powerOnZone2Job = scheduler.schedule(() -> {
1721 synchronized (sequenceLock) {
1722 logger.debug("Power ON zone 2 job");
1724 if (connector.getProtocol() == RotelProtocol.HEX
1725 && connector.getModel().getNbAdditionalZones() >= 1) {
1726 selectZone(2, connector.getModel().getZoneSelectCmd());
1727 connector.sendCommand(connector.getModel().hasZone2Commands() ? RotelCommand.ZONE2_VOLUME_DOWN
1728 : RotelCommand.VOLUME_DOWN);
1730 connector.sendCommand(connector.getModel().hasZone2Commands() ? RotelCommand.ZONE2_VOLUME_UP
1731 : RotelCommand.VOLUME_UP);
1734 } catch (RotelException e) {
1735 logger.debug("Init sequence zone 2 failed: {}", e.getMessage());
1736 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
1737 "@text/offline.comm-error-init-sequence-zone [\"2\"]");
1739 } catch (InterruptedException e) {
1740 logger.debug("Init sequence zone 2 interrupted: {}", e.getMessage());
1741 Thread.currentThread().interrupt();
1744 }, 2500, TimeUnit.MILLISECONDS);
1748 * Cancel the job scheduled when the zone 2 power switched ON
1750 private void cancelPowerOnZone2Job() {
1751 ScheduledFuture<?> powerOnZone2Job = this.powerOnZone2Job;
1752 if (powerOnZone2Job != null && !powerOnZone2Job.isCancelled()) {
1753 powerOnZone2Job.cancel(true);
1754 this.powerOnZone2Job = null;
1759 * Schedule the job to run with a few seconds delay when the zone 3 power switched ON
1761 private void schedulePowerOnZone3Job() {
1762 logger.debug("Schedule power ON zone 3 job");
1763 cancelPowerOnZone3Job();
1764 powerOnZone3Job = scheduler.schedule(() -> {
1765 synchronized (sequenceLock) {
1766 logger.debug("Power ON zone 3 job");
1768 if (connector.getProtocol() == RotelProtocol.HEX
1769 && connector.getModel().getNbAdditionalZones() >= 2) {
1770 selectZone(3, connector.getModel().getZoneSelectCmd());
1771 connector.sendCommand(connector.getModel().hasZone3Commands() ? RotelCommand.ZONE3_VOLUME_DOWN
1772 : RotelCommand.VOLUME_DOWN);
1774 connector.sendCommand(connector.getModel().hasZone3Commands() ? RotelCommand.ZONE3_VOLUME_UP
1775 : RotelCommand.VOLUME_UP);
1778 } catch (RotelException e) {
1779 logger.debug("Init sequence zone 3 failed: {}", e.getMessage());
1780 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
1781 "@text/offline.comm-error-init-sequence-zone [\"3\"]");
1783 } catch (InterruptedException e) {
1784 logger.debug("Init sequence zone 3 interrupted: {}", e.getMessage());
1785 Thread.currentThread().interrupt();
1788 }, 2500, TimeUnit.MILLISECONDS);
1792 * Cancel the job scheduled when the zone 3 power switched ON
1794 private void cancelPowerOnZone3Job() {
1795 ScheduledFuture<?> powerOnZone3Job = this.powerOnZone3Job;
1796 if (powerOnZone3Job != null && !powerOnZone3Job.isCancelled()) {
1797 powerOnZone3Job.cancel(true);
1798 this.powerOnZone3Job = null;
1803 * Schedule the job to run with a few seconds delay when the zone 4 power switched ON
1805 private void schedulePowerOnZone4Job() {
1806 logger.debug("Schedule power ON zone 4 job");
1807 cancelPowerOnZone4Job();
1808 powerOnZone4Job = scheduler.schedule(() -> {
1809 synchronized (sequenceLock) {
1810 logger.debug("Power ON zone 4 job");
1812 if (connector.getProtocol() == RotelProtocol.HEX
1813 && connector.getModel().getNbAdditionalZones() >= 3) {
1814 selectZone(4, connector.getModel().getZoneSelectCmd());
1815 connector.sendCommand(connector.getModel().hasZone4Commands() ? RotelCommand.ZONE4_VOLUME_DOWN
1816 : RotelCommand.VOLUME_DOWN);
1818 connector.sendCommand(connector.getModel().hasZone4Commands() ? RotelCommand.ZONE4_VOLUME_UP
1819 : RotelCommand.VOLUME_UP);
1822 } catch (RotelException e) {
1823 logger.debug("Init sequence zone 4 failed: {}", e.getMessage());
1824 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
1825 "@text/offline.comm-error-init-sequence-zone [\"4\"]");
1827 } catch (InterruptedException e) {
1828 logger.debug("Init sequence zone 4 interrupted: {}", e.getMessage());
1829 Thread.currentThread().interrupt();
1832 }, 2500, TimeUnit.MILLISECONDS);
1836 * Cancel the job scheduled when the zone 4 power switched ON
1838 private void cancelPowerOnZone4Job() {
1839 ScheduledFuture<?> powerOnZone4Job = this.powerOnZone4Job;
1840 if (powerOnZone4Job != null && !powerOnZone4Job.isCancelled()) {
1841 powerOnZone4Job.cancel(true);
1842 this.powerOnZone4Job = null;
1847 * Schedule the reconnection job
1849 private void scheduleReconnectJob() {
1850 logger.debug("Schedule reconnect job");
1851 cancelReconnectJob();
1852 reconnectJob = scheduler.scheduleWithFixedDelay(() -> {
1853 if (!connector.isConnected()) {
1854 logger.debug("Trying to reconnect...");
1857 String error = null;
1858 if (openConnection()) {
1859 synchronized (sequenceLock) {
1860 schedulePowerOffJob(true);
1862 connector.sendCommand(connector.getModel().getPowerStateCmd());
1863 } catch (RotelException e) {
1864 error = "@text/offline.comm-error-first-command-after-reconnection";
1865 logger.debug("First command after connection failed", e);
1866 cancelPowerOffJob();
1871 error = "@text/offline.comm-error-reconnection";
1873 if (error != null) {
1875 handlePowerOffZone2();
1876 handlePowerOffZone3();
1877 handlePowerOffZone4();
1878 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, error);
1880 updateStatus(ThingStatus.ONLINE);
1883 }, 1, POLLING_INTERVAL, TimeUnit.SECONDS);
1887 * Cancel the reconnection job
1889 private void cancelReconnectJob() {
1890 ScheduledFuture<?> reconnectJob = this.reconnectJob;
1891 if (reconnectJob != null && !reconnectJob.isCancelled()) {
1892 reconnectJob.cancel(true);
1893 this.reconnectJob = null;
1898 * Update the state of a channel
1900 * @param channel the channel
1902 private void updateChannelState(String channel) {
1903 if (!isLinked(channel)) {
1906 State state = UnDefType.UNDEF;
1909 case CHANNEL_MAIN_POWER:
1910 if (power != null) {
1911 state = power ? OnOffType.ON : OnOffType.OFF;
1914 case CHANNEL_ZONE2_POWER:
1915 state = powerZone2 ? OnOffType.ON : OnOffType.OFF;
1917 case CHANNEL_ZONE3_POWER:
1918 state = powerZone3 ? OnOffType.ON : OnOffType.OFF;
1920 case CHANNEL_ZONE4_POWER:
1921 state = powerZone4 ? OnOffType.ON : OnOffType.OFF;
1923 case CHANNEL_SOURCE:
1924 case CHANNEL_MAIN_SOURCE:
1926 state = new StringType(source.getName());
1929 case CHANNEL_MAIN_RECORD_SOURCE:
1930 RotelSource recordSource = this.recordSource;
1931 if (isPowerOn() && recordSource != null) {
1932 state = new StringType(recordSource.getName());
1935 case CHANNEL_ZONE2_SOURCE:
1936 RotelSource sourceZone2 = this.sourceZone2;
1937 if (powerZone2 && sourceZone2 != null) {
1938 state = new StringType(sourceZone2.getName());
1941 case CHANNEL_ZONE3_SOURCE:
1942 RotelSource sourceZone3 = this.sourceZone3;
1943 if (powerZone3 && sourceZone3 != null) {
1944 state = new StringType(sourceZone3.getName());
1947 case CHANNEL_ZONE4_SOURCE:
1948 RotelSource sourceZone4 = this.sourceZone4;
1949 if (powerZone4 && sourceZone4 != null) {
1950 state = new StringType(sourceZone4.getName());
1954 case CHANNEL_MAIN_DSP:
1956 state = new StringType(dsp.getName());
1959 case CHANNEL_VOLUME:
1960 case CHANNEL_MAIN_VOLUME:
1962 long volumePct = Math
1963 .round((double) (volume - minVolume) / (double) (maxVolume - minVolume) * 100.0);
1964 state = new PercentType(BigDecimal.valueOf(volumePct));
1967 case CHANNEL_MAIN_VOLUME_UP_DOWN:
1969 state = new DecimalType(volume);
1972 case CHANNEL_ZONE2_VOLUME:
1973 if (powerZone2 && !fixedVolumeZone2) {
1974 long volumePct = Math
1975 .round((double) (volumeZone2 - minVolume) / (double) (maxVolume - minVolume) * 100.0);
1976 state = new PercentType(BigDecimal.valueOf(volumePct));
1979 case CHANNEL_ZONE2_VOLUME_UP_DOWN:
1980 if (powerZone2 && !fixedVolumeZone2) {
1981 state = new DecimalType(volumeZone2);
1984 case CHANNEL_ZONE3_VOLUME:
1985 if (powerZone3 && !fixedVolumeZone3) {
1986 long volumePct = Math
1987 .round((double) (volumeZone3 - minVolume) / (double) (maxVolume - minVolume) * 100.0);
1988 state = new PercentType(BigDecimal.valueOf(volumePct));
1991 case CHANNEL_ZONE4_VOLUME:
1992 if (powerZone4 && !fixedVolumeZone4) {
1993 long volumePct = Math
1994 .round((double) (volumeZone4 - minVolume) / (double) (maxVolume - minVolume) * 100.0);
1995 state = new PercentType(BigDecimal.valueOf(volumePct));
1999 case CHANNEL_MAIN_MUTE:
2001 state = mute ? OnOffType.ON : OnOffType.OFF;
2004 case CHANNEL_ZONE2_MUTE:
2006 state = muteZone2 ? OnOffType.ON : OnOffType.OFF;
2009 case CHANNEL_ZONE3_MUTE:
2011 state = muteZone3 ? OnOffType.ON : OnOffType.OFF;
2014 case CHANNEL_ZONE4_MUTE:
2016 state = muteZone4 ? OnOffType.ON : OnOffType.OFF;
2020 case CHANNEL_MAIN_BASS:
2022 state = new DecimalType(bass);
2025 case CHANNEL_TREBLE:
2026 case CHANNEL_MAIN_TREBLE:
2028 state = new DecimalType(treble);
2032 if (track > 0 && isPowerOn()) {
2033 state = new DecimalType(track);
2036 case CHANNEL_PLAY_CONTROL:
2038 switch (playStatus) {
2040 state = PlayPauseType.PLAY;
2044 state = PlayPauseType.PAUSE;
2049 case CHANNEL_FREQUENCY:
2050 if (frequency > 0.0 && isPowerOn()) {
2051 state = new DecimalType(frequency);
2055 state = new StringType(frontPanelLine1);
2058 state = new StringType(frontPanelLine2);
2060 case CHANNEL_BRIGHTNESS:
2061 if (isPowerOn() && connector.getModel().hasDimmerControl()) {
2062 long dimmerPct = Math.round((double) (brightness - connector.getModel().getDimmerLevelMin())
2063 / (double) (connector.getModel().getDimmerLevelMax()
2064 - connector.getModel().getDimmerLevelMin())
2066 state = new PercentType(BigDecimal.valueOf(dimmerPct));
2072 updateState(channel, state);
2076 * Inform about the main zone power state
2078 * @return true if main zone power state is known and known as ON
2080 private boolean isPowerOn() {
2081 Boolean power = this.power;
2082 return power != null && power.booleanValue();
2086 * Get the command to be used for main zone POWER ON
2088 * @return the command
2090 private RotelCommand getPowerOnCommand() {
2091 return connector.getModel().hasOtherThanPrimaryCommands() ? RotelCommand.MAIN_ZONE_POWER_ON
2092 : RotelCommand.POWER_ON;
2096 * Get the command to be used for main zone POWER OFF
2098 * @return the command
2100 private RotelCommand getPowerOffCommand() {
2101 return connector.getModel().hasOtherThanPrimaryCommands() ? RotelCommand.MAIN_ZONE_POWER_OFF
2102 : RotelCommand.POWER_OFF;
2106 * Get the command to be used for main zone VOLUME UP
2108 * @return the command
2110 private RotelCommand getVolumeUpCommand() {
2111 return connector.getModel().hasOtherThanPrimaryCommands() ? RotelCommand.MAIN_ZONE_VOLUME_UP
2112 : RotelCommand.VOLUME_UP;
2116 * Get the command to be used for main zone VOLUME DOWN
2118 * @return the command
2120 private RotelCommand getVolumeDownCommand() {
2121 return connector.getModel().hasOtherThanPrimaryCommands() ? RotelCommand.MAIN_ZONE_VOLUME_DOWN
2122 : RotelCommand.VOLUME_DOWN;
2126 * Get the command to be used for main zone MUTE ON
2128 * @return the command
2130 private RotelCommand getMuteOnCommand() {
2131 return connector.getModel().hasOtherThanPrimaryCommands() ? RotelCommand.MAIN_ZONE_MUTE_ON
2132 : RotelCommand.MUTE_ON;
2136 * Get the command to be used for main zone MUTE OFF
2138 * @return the command
2140 private RotelCommand getMuteOffCommand() {
2141 return connector.getModel().hasOtherThanPrimaryCommands() ? RotelCommand.MAIN_ZONE_MUTE_OFF
2142 : RotelCommand.MUTE_OFF;
2146 * Get the command to be used for main zone MUTE TOGGLE
2148 * @return the command
2150 private RotelCommand getMuteToggleCommand() {
2151 return connector.getModel().hasOtherThanPrimaryCommands() ? RotelCommand.MAIN_ZONE_MUTE_TOGGLE
2152 : RotelCommand.MUTE_TOGGLE;