2 * Copyright (c) 2010-2022 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;
78 private static final int SLEEP_INTV = 30;
80 private @Nullable ScheduledFuture<?> reconnectJob;
81 private @Nullable ScheduledFuture<?> powerOnJob;
82 private @Nullable ScheduledFuture<?> powerOffJob;
83 private @Nullable ScheduledFuture<?> powerOnZone2Job;
84 private @Nullable ScheduledFuture<?> powerOnZone3Job;
85 private @Nullable ScheduledFuture<?> powerOnZone4Job;
87 private RotelStateDescriptionOptionProvider stateDescriptionProvider;
88 private SerialPortManager serialPortManager;
90 private RotelConnector connector = new RotelSimuConnector(DEFAULT_MODEL, RotelProtocol.HEX, new HashMap<>(),
93 private int minVolume;
94 private int maxVolume;
95 private int minToneLevel;
96 private int maxToneLevel;
98 private int currentZone = 1;
99 private boolean selectingRecord;
100 private @Nullable Boolean power;
101 private boolean powerZone2;
102 private boolean powerZone3;
103 private boolean powerZone4;
104 private RotelSource source = RotelSource.CAT0_CD;
105 private @Nullable RotelSource recordSource;
106 private @Nullable RotelSource sourceZone2;
107 private @Nullable RotelSource sourceZone3;
108 private @Nullable RotelSource sourceZone4;
109 private RotelDsp dsp = RotelDsp.CAT1_NONE;
111 private boolean mute;
112 private boolean fixedVolumeZone2;
113 private int volumeZone2;
114 private boolean muteZone2;
115 private boolean fixedVolumeZone3;
116 private int volumeZone3;
117 private boolean muteZone3;
118 private boolean fixedVolumeZone4;
119 private int volumeZone4;
120 private boolean muteZone4;
123 private RotelPlayStatus playStatus = RotelPlayStatus.STOPPED;
125 private double frequency;
126 private String frontPanelLine1 = "";
127 private String frontPanelLine2 = "";
128 private int brightness;
129 private boolean tcbypass;
131 private int minBalanceLevel;
132 private int maxBalanceLevel;
133 private boolean speakera;
134 private boolean speakerb;
135 private boolean useFixedBalanceCmd;
137 private Object sequenceLock = new Object();
142 public RotelHandler(Thing thing, RotelStateDescriptionOptionProvider stateDescriptionProvider,
143 SerialPortManager serialPortManager) {
145 this.stateDescriptionProvider = stateDescriptionProvider;
146 this.serialPortManager = serialPortManager;
150 public void initialize() {
151 logger.debug("Start initializing handler for thing {}", getThing().getUID());
153 RotelModel rotelModel;
154 switch (getThing().getThingTypeUID().getId()) {
155 case THING_TYPE_ID_RSP1066:
156 rotelModel = RotelModel.RSP1066;
158 case THING_TYPE_ID_RSP1068:
159 rotelModel = RotelModel.RSP1068;
161 case THING_TYPE_ID_RSP1069:
162 rotelModel = RotelModel.RSP1069;
164 case THING_TYPE_ID_RSP1098:
165 rotelModel = RotelModel.RSP1098;
167 case THING_TYPE_ID_RSP1570:
168 rotelModel = RotelModel.RSP1570;
170 case THING_TYPE_ID_RSP1572:
171 rotelModel = RotelModel.RSP1572;
173 case THING_TYPE_ID_RSX1055:
174 rotelModel = RotelModel.RSX1055;
176 case THING_TYPE_ID_RSX1056:
177 rotelModel = RotelModel.RSX1056;
179 case THING_TYPE_ID_RSX1057:
180 rotelModel = RotelModel.RSX1057;
182 case THING_TYPE_ID_RSX1058:
183 rotelModel = RotelModel.RSX1058;
185 case THING_TYPE_ID_RSX1065:
186 rotelModel = RotelModel.RSX1065;
188 case THING_TYPE_ID_RSX1067:
189 rotelModel = RotelModel.RSX1067;
191 case THING_TYPE_ID_RSX1550:
192 rotelModel = RotelModel.RSX1550;
194 case THING_TYPE_ID_RSX1560:
195 rotelModel = RotelModel.RSX1560;
197 case THING_TYPE_ID_RSX1562:
198 rotelModel = RotelModel.RSX1562;
200 case THING_TYPE_ID_A11:
201 rotelModel = RotelModel.A11;
202 useFixedBalanceCmd = true;
204 case THING_TYPE_ID_A12:
205 rotelModel = RotelModel.A12;
206 useFixedBalanceCmd = true;
208 case THING_TYPE_ID_A14:
209 rotelModel = RotelModel.A14;
210 useFixedBalanceCmd = true;
212 case THING_TYPE_ID_CD11:
213 rotelModel = RotelModel.CD11;
215 case THING_TYPE_ID_CD14:
216 rotelModel = RotelModel.CD14;
218 case THING_TYPE_ID_RA11:
219 rotelModel = RotelModel.RA11;
221 case THING_TYPE_ID_RA12:
222 rotelModel = RotelModel.RA12;
224 case THING_TYPE_ID_RA1570:
225 rotelModel = RotelModel.RA1570;
227 case THING_TYPE_ID_RA1572:
228 rotelModel = RotelModel.RA1572;
230 case THING_TYPE_ID_RA1592:
231 rotelModel = RotelModel.RA1592;
233 case THING_TYPE_ID_RAP1580:
234 rotelModel = RotelModel.RAP1580;
236 case THING_TYPE_ID_RC1570:
237 rotelModel = RotelModel.RC1570;
239 case THING_TYPE_ID_RC1572:
240 rotelModel = RotelModel.RC1572;
242 case THING_TYPE_ID_RC1590:
243 rotelModel = RotelModel.RC1590;
245 case THING_TYPE_ID_RCD1570:
246 rotelModel = RotelModel.RCD1570;
248 case THING_TYPE_ID_RCD1572:
249 rotelModel = RotelModel.RCD1572;
251 case THING_TYPE_ID_RCX1500:
252 rotelModel = RotelModel.RCX1500;
254 case THING_TYPE_ID_RDD1580:
255 rotelModel = RotelModel.RDD1580;
257 case THING_TYPE_ID_RDG1520:
258 case THING_TYPE_ID_RT09:
259 rotelModel = RotelModel.RDG1520;
261 case THING_TYPE_ID_RSP1576:
262 rotelModel = RotelModel.RSP1576;
264 case THING_TYPE_ID_RSP1582:
265 rotelModel = RotelModel.RSP1582;
267 case THING_TYPE_ID_RT11:
268 rotelModel = RotelModel.RT11;
270 case THING_TYPE_ID_RT1570:
271 rotelModel = RotelModel.RT1570;
273 case THING_TYPE_ID_T11:
274 rotelModel = RotelModel.T11;
276 case THING_TYPE_ID_T14:
277 rotelModel = RotelModel.T14;
280 rotelModel = DEFAULT_MODEL;
284 RotelThingConfiguration config = getConfigAs(RotelThingConfiguration.class);
286 RotelProtocol rotelProtocol = RotelProtocol.HEX;
287 if (config.protocol != null && !config.protocol.isEmpty()) {
289 rotelProtocol = RotelProtocol.getFromName(config.protocol);
290 } catch (RotelException e) {
293 Map<String, String> properties = editProperties();
294 String property = properties.get(RotelBindingConstants.PROPERTY_PROTOCOL);
295 if (property != null && !property.isEmpty()) {
297 rotelProtocol = RotelProtocol.getFromName(property);
298 } catch (RotelException e) {
302 logger.debug("rotelProtocol {}", rotelProtocol.getName());
304 Map<RotelSource, String> sourcesCustomLabels = new HashMap<>();
305 Map<RotelSource, String> sourcesLabels = new HashMap<>();
307 String readerThreadName = "OH-binding-" + getThing().getUID().getAsString();
309 connector = new RotelSimuConnector(rotelModel, rotelProtocol, sourcesLabels, readerThreadName);
311 if (rotelModel.hasVolumeControl()) {
312 maxVolume = rotelModel.getVolumeMax();
313 if (!rotelModel.hasDirectVolumeControl()) {
315 "Set minValue to {} and maxValue to {} for your sitemap widget attached to your volume item.",
316 minVolume, maxVolume);
319 if (rotelModel.hasToneControl()) {
320 maxToneLevel = rotelModel.getToneLevelMax();
321 minToneLevel = -maxToneLevel;
323 "Set minValue to {} and maxValue to {} for your sitemap widget attached to your bass or treble item.",
324 minToneLevel, maxToneLevel);
326 if (rotelModel.hasBalanceControl()) {
327 maxBalanceLevel = rotelModel.getBalanceLevelMax();
328 minBalanceLevel = -maxBalanceLevel;
329 logger.info("Set minValue to {} and maxValue to {} for your sitemap widget attached to your balance item.",
330 minBalanceLevel, maxBalanceLevel);
333 // Check configuration settings
334 String configError = null;
335 if ((config.serialPort == null || config.serialPort.isEmpty())
336 && (config.host == null || config.host.isEmpty())) {
337 configError = "@text/offline.config-error-unknown-serialport-and-host";
338 } else if (config.host == null || config.host.isEmpty()) {
339 if (config.serialPort.toLowerCase().startsWith("rfc2217")) {
340 configError = "@text/offline.config-error-invalid-serial-over-ip";
343 if (config.port == null) {
344 configError = "@text/offline.config-error-unknown-port";
345 } else if (config.port <= 0) {
346 configError = "@text/offline.config-error-invalid-port";
350 if (configError != null) {
351 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, configError);
353 for (RotelSource src : rotelModel.getSources()) {
354 // Consider custom input labels
356 switch (src.getName()) {
358 label = config.inputLabelCd;
361 label = config.inputLabelTuner;
364 label = config.inputLabelTape;
367 label = config.inputLabelPhono;
370 label = config.inputLabelVideo1;
373 label = config.inputLabelVideo2;
376 label = config.inputLabelVideo3;
379 label = config.inputLabelVideo4;
382 label = config.inputLabelVideo5;
385 label = config.inputLabelVideo6;
388 label = config.inputLabelUsb;
391 label = config.inputLabelMulti;
396 if (label != null && !label.isEmpty()) {
397 sourcesCustomLabels.put(src, label);
399 sourcesLabels.put(src, (label == null || label.isEmpty()) ? src.getLabel() : label);
402 if (USE_SIMULATED_DEVICE) {
403 connector = new RotelSimuConnector(rotelModel, rotelProtocol, sourcesLabels, readerThreadName);
404 } else if (config.serialPort != null) {
405 connector = new RotelSerialConnector(serialPortManager, config.serialPort, rotelModel, rotelProtocol,
406 sourcesLabels, readerThreadName);
408 connector = new RotelIpConnector(config.host, config.port, rotelModel, rotelProtocol, sourcesLabels,
412 if (rotelModel.hasSourceControl()) {
413 stateDescriptionProvider.setStateOptions(new ChannelUID(getThing().getUID(), CHANNEL_SOURCE),
414 getStateOptions(rotelModel.getSources(), sourcesCustomLabels));
415 stateDescriptionProvider.setStateOptions(new ChannelUID(getThing().getUID(), CHANNEL_MAIN_SOURCE),
416 getStateOptions(rotelModel.getSources(), sourcesCustomLabels));
417 stateDescriptionProvider.setStateOptions(
418 new ChannelUID(getThing().getUID(), CHANNEL_MAIN_RECORD_SOURCE),
419 getStateOptions(rotelModel.getRecordSources(), sourcesCustomLabels));
421 if (rotelModel.hasZone2SourceControl()) {
422 stateDescriptionProvider.setStateOptions(new ChannelUID(getThing().getUID(), CHANNEL_ZONE2_SOURCE),
423 getStateOptions(rotelModel.getZone2Sources(), sourcesCustomLabels));
425 if (rotelModel.hasZone3SourceControl()) {
426 stateDescriptionProvider.setStateOptions(new ChannelUID(getThing().getUID(), CHANNEL_ZONE3_SOURCE),
427 getStateOptions(rotelModel.getZone3Sources(), sourcesCustomLabels));
429 if (rotelModel.hasZone4SourceControl()) {
430 stateDescriptionProvider.setStateOptions(new ChannelUID(getThing().getUID(), CHANNEL_ZONE4_SOURCE),
431 getStateOptions(rotelModel.getZone4Sources(), sourcesCustomLabels));
433 if (rotelModel.hasDspControl()) {
434 stateDescriptionProvider.setStateOptions(new ChannelUID(getThing().getUID(), CHANNEL_DSP),
435 rotelModel.getDspStateOptions());
436 stateDescriptionProvider.setStateOptions(new ChannelUID(getThing().getUID(), CHANNEL_MAIN_DSP),
437 rotelModel.getDspStateOptions());
440 updateStatus(ThingStatus.UNKNOWN);
442 scheduleReconnectJob();
445 logger.debug("Finished initializing!");
449 public void dispose() {
450 logger.debug("Disposing handler for thing {}", getThing().getUID());
453 cancelPowerOnZone2Job();
454 cancelPowerOnZone3Job();
455 cancelPowerOnZone4Job();
456 cancelReconnectJob();
461 public List<StateOption> getStateOptions(List<RotelSource> list, Map<RotelSource, String> sourcesLabels) {
462 List<StateOption> options = new ArrayList<>();
463 for (RotelSource item : list) {
464 String label = sourcesLabels.get(item);
465 options.add(new StateOption(item.getName(), label == null ? ("@text/source." + item.getName()) : label));
471 public void handleCommand(ChannelUID channelUID, Command command) {
472 String channel = channelUID.getId();
474 if (getThing().getStatus() != ThingStatus.ONLINE) {
475 logger.debug("Thing is not ONLINE; command {} from channel {} is ignored", command, channel);
479 if (command instanceof RefreshType) {
480 updateChannelState(channel);
484 if (!connector.isConnected()) {
485 logger.debug("Command {} from channel {} is ignored: connection not established", command, channel);
491 boolean success = true;
492 synchronized (sequenceLock) {
496 case CHANNEL_MAIN_POWER:
497 handlePowerCmd(channel, command, getPowerOnCommand(), getPowerOffCommand());
499 case CHANNEL_ZONE2_POWER:
500 if (connector.getModel().hasZone2Commands()) {
501 handlePowerCmd(channel, command, RotelCommand.ZONE2_POWER_ON, RotelCommand.ZONE2_POWER_OFF);
502 } else if (connector.getModel().getNbAdditionalZones() == 1) {
503 if (isPowerOn() || powerZone2) {
504 selectZone(2, connector.getModel().getZoneSelectCmd());
506 connector.sendCommand(RotelCommand.ZONE_SELECT);
509 logger.debug("Command {} from channel {} failed: unavailable feature", command, channel);
512 case CHANNEL_ZONE3_POWER:
513 if (connector.getModel().hasZone3Commands()) {
514 handlePowerCmd(channel, command, RotelCommand.ZONE3_POWER_ON, RotelCommand.ZONE3_POWER_OFF);
517 logger.debug("Command {} from channel {} failed: unavailable feature", command, channel);
520 case CHANNEL_ZONE4_POWER:
521 if (connector.getModel().hasZone4Commands()) {
522 handlePowerCmd(channel, command, RotelCommand.ZONE4_POWER_ON, RotelCommand.ZONE4_POWER_OFF);
525 logger.debug("Command {} from channel {} failed: unavailable feature", command, channel);
529 case CHANNEL_MAIN_SOURCE:
532 logger.debug("Command {} from channel {} ignored: device in standby", command, channel);
534 src = connector.getModel().getSourceFromName(command.toString());
535 cmd = connector.getModel().hasOtherThanPrimaryCommands() ? src.getMainZoneCommand()
538 connector.sendCommand(cmd);
539 if (connector.getModel().canGetFrequency()) {
540 // send <new-source> returns
541 // 1.) the selected <new-source>
542 // 2.) the used frequency
544 // at response-time the frequency has the value of <old-source>
545 // so we must wait a short moment to get the frequency of <new-source>
547 connector.sendCommand(RotelCommand.FREQUENCY);
549 updateChannelState(CHANNEL_FREQUENCY);
553 logger.debug("Command {} from channel {} failed: undefined source command", command,
558 case CHANNEL_MAIN_RECORD_SOURCE:
561 logger.debug("Command {} from channel {} ignored: device in standby", command, channel);
562 } else if (connector.getModel().hasOtherThanPrimaryCommands()) {
563 src = connector.getModel().getSourceFromName(command.toString());
564 cmd = src.getRecordCommand();
566 connector.sendCommand(cmd);
569 logger.debug("Command {} from channel {} failed: undefined record source command",
573 src = connector.getModel().getSourceFromName(command.toString());
574 cmd = src.getCommand();
576 connector.sendCommand(RotelCommand.RECORD_FONCTION_SELECT);
578 connector.sendCommand(cmd);
581 logger.debug("Command {} from channel {} failed: undefined source command", command,
586 case CHANNEL_ZONE2_SOURCE:
589 logger.debug("Command {} from channel {} ignored: zone 2 in standby", command, channel);
590 } else if (connector.getModel().hasZone2Commands()) {
591 src = connector.getModel().getSourceFromName(command.toString());
592 cmd = src.getZone2Command();
594 connector.sendCommand(cmd);
597 logger.debug("Command {} from channel {} failed: undefined zone 2 source command",
600 } else if (connector.getModel().getNbAdditionalZones() >= 1) {
601 src = connector.getModel().getSourceFromName(command.toString());
602 cmd = src.getCommand();
604 selectZone(2, connector.getModel().getZoneSelectCmd());
605 connector.sendCommand(cmd);
608 logger.debug("Command {} from channel {} failed: undefined source command", command,
613 logger.debug("Command {} from channel {} failed: unavailable feature", command, channel);
616 case CHANNEL_ZONE3_SOURCE:
619 logger.debug("Command {} from channel {} ignored: zone 3 in standby", command, channel);
620 } else if (connector.getModel().hasZone3Commands()) {
621 src = connector.getModel().getSourceFromName(command.toString());
622 cmd = src.getZone3Command();
624 connector.sendCommand(cmd);
627 logger.debug("Command {} from channel {} failed: undefined zone 3 source command",
632 logger.debug("Command {} from channel {} failed: unavailable feature", command, channel);
635 case CHANNEL_ZONE4_SOURCE:
638 logger.debug("Command {} from channel {} ignored: zone 4 in standby", command, channel);
639 } else if (connector.getModel().hasZone4Commands()) {
640 src = connector.getModel().getSourceFromName(command.toString());
641 cmd = src.getZone4Command();
643 connector.sendCommand(cmd);
646 logger.debug("Command {} from channel {} failed: undefined zone 4 source command",
651 logger.debug("Command {} from channel {} failed: unavailable feature", command, channel);
655 case CHANNEL_MAIN_DSP:
658 logger.debug("Command {} from channel {} ignored: device in standby", command, channel);
660 connector.sendCommand(connector.getModel().getCommandFromDspName(command.toString()));
664 case CHANNEL_MAIN_VOLUME:
667 logger.debug("Command {} from channel {} ignored: device in standby", command, channel);
668 } else if (connector.getModel().hasVolumeControl()) {
669 handleVolumeCmd(volume, channel, command, getVolumeUpCommand(), getVolumeDownCommand(),
670 RotelCommand.VOLUME_SET);
673 logger.debug("Command {} from channel {} failed: unavailable feature", command, channel);
676 case CHANNEL_MAIN_VOLUME_UP_DOWN:
679 logger.debug("Command {} from channel {} ignored: device in standby", command, channel);
680 } else if (connector.getModel().hasVolumeControl()) {
681 handleVolumeCmd(volume, channel, command, getVolumeUpCommand(), getVolumeDownCommand(),
685 logger.debug("Command {} from channel {} failed: unavailable feature", command, channel);
688 case CHANNEL_ZONE2_VOLUME:
691 logger.debug("Command {} from channel {} ignored: zone 2 in standby", command, channel);
692 } else if (fixedVolumeZone2) {
694 logger.debug("Command {} from channel {} ignored: fixed volume in zone 2", command,
696 } else if (connector.getModel().hasVolumeControl()
697 && connector.getModel().getNbAdditionalZones() >= 1) {
698 if (connector.getModel().hasZone2Commands()) {
699 handleVolumeCmd(volumeZone2, channel, command, RotelCommand.ZONE2_VOLUME_UP,
700 RotelCommand.ZONE2_VOLUME_DOWN, RotelCommand.ZONE2_VOLUME_SET);
702 selectZone(2, connector.getModel().getZoneSelectCmd());
703 handleVolumeCmd(volumeZone2, channel, command, RotelCommand.VOLUME_UP,
704 RotelCommand.VOLUME_DOWN, RotelCommand.VOLUME_SET);
708 logger.debug("Command {} from channel {} failed: unavailable feature", command, channel);
711 case CHANNEL_ZONE2_VOLUME_UP_DOWN:
714 logger.debug("Command {} from channel {} ignored: zone 2 in standby", command, channel);
715 } else if (fixedVolumeZone2) {
717 logger.debug("Command {} from channel {} ignored: fixed volume in zone 2", command,
719 } else if (connector.getModel().hasVolumeControl()
720 && connector.getModel().getNbAdditionalZones() >= 1) {
721 if (connector.getModel().hasZone2Commands()) {
722 handleVolumeCmd(volumeZone2, channel, command, RotelCommand.ZONE2_VOLUME_UP,
723 RotelCommand.ZONE2_VOLUME_DOWN, null);
725 selectZone(2, connector.getModel().getZoneSelectCmd());
726 handleVolumeCmd(volumeZone2, channel, command, RotelCommand.VOLUME_UP,
727 RotelCommand.VOLUME_DOWN, null);
731 logger.debug("Command {} from channel {} failed: unavailable feature", command, channel);
734 case CHANNEL_ZONE3_VOLUME:
737 logger.debug("Command {} from channel {} ignored: zone 3 in standby", command, channel);
738 } else if (fixedVolumeZone3) {
740 logger.debug("Command {} from channel {} ignored: fixed volume in zone 3", command,
742 } else if (connector.getModel().hasVolumeControl() && connector.getModel().hasZone3Commands()) {
743 handleVolumeCmd(volumeZone3, channel, command, RotelCommand.ZONE3_VOLUME_UP,
744 RotelCommand.ZONE3_VOLUME_DOWN, RotelCommand.ZONE3_VOLUME_SET);
747 logger.debug("Command {} from channel {} failed: unavailable feature", command, channel);
750 case CHANNEL_ZONE4_VOLUME:
753 logger.debug("Command {} from channel {} ignored: zone 4 in standby", command, channel);
754 } else if (fixedVolumeZone4) {
756 logger.debug("Command {} from channel {} ignored: fixed volume in zone 4", command,
758 } else if (connector.getModel().hasVolumeControl() && connector.getModel().hasZone4Commands()) {
759 handleVolumeCmd(volumeZone4, channel, command, RotelCommand.ZONE4_VOLUME_UP,
760 RotelCommand.ZONE4_VOLUME_DOWN, RotelCommand.ZONE4_VOLUME_SET);
763 logger.debug("Command {} from channel {} failed: unavailable feature", command, channel);
767 case CHANNEL_MAIN_MUTE:
770 logger.debug("Command {} from channel {} ignored: device in standby", command, channel);
771 } else if (connector.getModel().hasVolumeControl()) {
772 handleMuteCmd(connector.getProtocol() == RotelProtocol.HEX, channel, command,
773 getMuteOnCommand(), getMuteOffCommand(), getMuteToggleCommand());
776 logger.debug("Command {} from channel {} failed: unavailable feature", command, channel);
779 case CHANNEL_ZONE2_MUTE:
782 logger.debug("Command {} from channel {} ignored: zone 2 in standby", command, channel);
783 } else if (connector.getModel().hasVolumeControl() && connector.getModel().hasZone2Commands()) {
784 handleMuteCmd(false, channel, command, RotelCommand.ZONE2_MUTE_ON,
785 RotelCommand.ZONE2_MUTE_OFF, RotelCommand.ZONE2_MUTE_TOGGLE);
788 logger.debug("Command {} from channel {} failed: unavailable feature", command, channel);
791 case CHANNEL_ZONE3_MUTE:
794 logger.debug("Command {} from channel {} ignored: zone 3 in standby", command, channel);
795 } else if (connector.getModel().hasVolumeControl() && connector.getModel().hasZone3Commands()) {
796 handleMuteCmd(false, channel, command, RotelCommand.ZONE3_MUTE_ON,
797 RotelCommand.ZONE3_MUTE_OFF, RotelCommand.ZONE3_MUTE_TOGGLE);
800 logger.debug("Command {} from channel {} failed: unavailable feature", command, channel);
803 case CHANNEL_ZONE4_MUTE:
806 logger.debug("Command {} from channel {} ignored: zone 4 in standby", command, channel);
807 } else if (connector.getModel().hasVolumeControl() && connector.getModel().hasZone4Commands()) {
808 handleMuteCmd(false, channel, command, RotelCommand.ZONE4_MUTE_ON,
809 RotelCommand.ZONE4_MUTE_OFF, RotelCommand.ZONE4_MUTE_TOGGLE);
812 logger.debug("Command {} from channel {} failed: unavailable feature", command, channel);
816 case CHANNEL_MAIN_BASS:
819 logger.debug("Command {} from channel {} ignored: device in standby", command, channel);
820 } else if (tcbypass) {
821 logger.debug("Command {} from channel {} ignored: tone control bypass is ON", command,
823 updateChannelState(CHANNEL_BASS);
825 handleToneCmd(bass, channel, command, 2, RotelCommand.BASS_UP, RotelCommand.BASS_DOWN,
826 RotelCommand.BASS_SET);
830 case CHANNEL_MAIN_TREBLE:
833 logger.debug("Command {} from channel {} ignored: device in standby", command, channel);
834 } else if (tcbypass) {
835 logger.debug("Command {} from channel {} ignored: tone control bypass is ON", command,
837 updateChannelState(CHANNEL_TREBLE);
839 handleToneCmd(treble, channel, command, 1, RotelCommand.TREBLE_UP, RotelCommand.TREBLE_DOWN,
840 RotelCommand.TREBLE_SET);
843 case CHANNEL_PLAY_CONTROL:
846 logger.debug("Command {} from channel {} ignored: device in standby", command, channel);
847 } else if (command instanceof PlayPauseType && command == PlayPauseType.PLAY) {
848 connector.sendCommand(RotelCommand.PLAY);
849 } else if (command instanceof PlayPauseType && command == PlayPauseType.PAUSE) {
850 connector.sendCommand(RotelCommand.PAUSE);
851 if (connector.getProtocol() == RotelProtocol.ASCII_V1
852 && connector.getModel() != RotelModel.RCD1570
853 && connector.getModel() != RotelModel.RCD1572
854 && connector.getModel() != RotelModel.RCX1500) {
855 Thread.sleep(SLEEP_INTV);
856 connector.sendCommand(RotelCommand.PLAY_STATUS);
858 } else if (command instanceof NextPreviousType && command == NextPreviousType.NEXT) {
859 connector.sendCommand(RotelCommand.TRACK_FORWARD);
860 } else if (command instanceof NextPreviousType && command == NextPreviousType.PREVIOUS) {
861 connector.sendCommand(RotelCommand.TRACK_BACKWORD);
864 logger.debug("Command {} from channel {} failed: invalid command value", command, channel);
867 case CHANNEL_BRIGHTNESS:
870 logger.debug("Command {} from channel {} ignored: device in standby", command, channel);
871 } else if (!connector.getModel().hasDimmerControl()) {
873 logger.debug("Command {} from channel {} failed: unavailable feature", command, channel);
874 } else if (command instanceof PercentType) {
875 int dimmer = (int) Math.round(((PercentType) command).doubleValue() / 100.0
876 * (connector.getModel().getDimmerLevelMax()
877 - connector.getModel().getDimmerLevelMin()))
878 + connector.getModel().getDimmerLevelMin();
879 connector.sendCommand(RotelCommand.DIMMER_LEVEL_SET, dimmer);
882 logger.debug("Command {} from channel {} failed: invalid command value", command, channel);
885 case CHANNEL_TCBYPASS:
888 logger.debug("Command {} from channel {} ignored: device in standby", command, channel);
889 } else if (!connector.getModel().hasToneControl()
890 || connector.getProtocol() == RotelProtocol.HEX) {
892 logger.debug("Command {} from channel {} failed: unavailable feature", command, channel);
894 handleTcbypassCmd(channel, command,
895 connector.getProtocol() == RotelProtocol.ASCII_V1 ? RotelCommand.TONE_CONTROLS_OFF
896 : RotelCommand.TCBYPASS_ON,
897 connector.getProtocol() == RotelProtocol.ASCII_V1 ? RotelCommand.TONE_CONTROLS_ON
898 : RotelCommand.TCBYPASS_OFF);
901 case CHANNEL_BALANCE:
904 logger.debug("Command {} from channel {} ignored: device in standby", command, channel);
905 } else if (!connector.getModel().hasBalanceControl()
906 || connector.getProtocol() == RotelProtocol.HEX) {
908 logger.debug("Command {} from channel {} failed: unavailable feature", command, channel);
910 handleBalanceCmd(channel, command, RotelCommand.BALANCE_LEFT, RotelCommand.BALANCE_RIGHT,
911 useFixedBalanceCmd ? RotelCommand.BALANCE_SET_FIX : RotelCommand.BALANCE_SET);
914 case CHANNEL_SPEAKER_A:
917 logger.debug("Command {} from channel {} ignored: device in standby", command, channel);
919 handleSpeakerCmd(connector.getProtocol() == RotelProtocol.HEX, channel, command,
920 RotelCommand.SPEAKER_A_ON, RotelCommand.SPEAKER_A_OFF,
921 RotelCommand.SPEAKER_A_TOGGLE);
924 case CHANNEL_SPEAKER_B:
927 logger.debug("Command {} from channel {} ignored: device in standby", command, channel);
929 handleSpeakerCmd(connector.getProtocol() == RotelProtocol.HEX, channel, command,
930 RotelCommand.SPEAKER_B_ON, RotelCommand.SPEAKER_B_OFF,
931 RotelCommand.SPEAKER_B_TOGGLE);
936 logger.debug("Command {} from channel {} failed: nnexpected command", command, channel);
940 logger.debug("Command {} from channel {} succeeded", command, channel);
942 updateChannelState(channel);
944 } catch (RotelException e) {
945 logger.debug("Command {} from channel {} failed: {}", command, channel, e.getMessage());
946 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
947 "@text/offline.comm-error-sending-command");
949 scheduleReconnectJob();
950 } catch (InterruptedException e) {
951 logger.debug("Command {} from channel {} interrupted: {}", command, channel, e.getMessage());
952 Thread.currentThread().interrupt();
958 * Handle a power ON/OFF command
960 * @param channel the channel
961 * @param command the received channel command (OnOffType)
962 * @param onCmd the command to be sent to the device to power it ON
963 * @param offCmd the command to be sent to the device to power it OFF
965 * @throws RotelException in case of communication error with the device
967 private void handlePowerCmd(String channel, Command command, RotelCommand onCmd, RotelCommand offCmd)
968 throws RotelException {
969 if (command instanceof OnOffType && command == OnOffType.ON) {
970 connector.sendCommand(onCmd);
971 } else if (command instanceof OnOffType && command == OnOffType.OFF) {
972 connector.sendCommand(offCmd);
974 logger.debug("Command {} from channel {} failed: invalid command value", command, channel);
979 * Handle a volume command
981 * @param current the current volume
982 * @param channel the channel
983 * @param command the received channel command (IncreaseDecreaseType or DecimalType)
984 * @param upCmd the command to be sent to the device to increase the volume
985 * @param downCmd the command to be sent to the device to decrease the volume
986 * @param setCmd the command to be sent to the device to set the volume at a value
988 * @throws RotelException in case of communication error with the device
990 private void handleVolumeCmd(int current, String channel, Command command, RotelCommand upCmd, RotelCommand downCmd,
991 @Nullable RotelCommand setCmd) throws RotelException {
992 if (command instanceof IncreaseDecreaseType && command == IncreaseDecreaseType.INCREASE) {
993 connector.sendCommand(upCmd);
994 } else if (command instanceof IncreaseDecreaseType && command == IncreaseDecreaseType.DECREASE) {
995 connector.sendCommand(downCmd);
996 } else if (command instanceof DecimalType && setCmd == null) {
997 int value = ((DecimalType) command).intValue();
998 if (value >= minVolume && value <= maxVolume) {
999 if (value > current) {
1000 connector.sendCommand(upCmd);
1001 } else if (value < current) {
1002 connector.sendCommand(downCmd);
1005 } else if (command instanceof PercentType && setCmd != null) {
1006 int value = (int) Math.round(((PercentType) command).doubleValue() / 100.0 * (maxVolume - minVolume))
1008 connector.sendCommand(setCmd, value);
1010 logger.debug("Command {} from channel {} failed: invalid command value", command, channel);
1015 * Handle a mute command
1017 * @param onlyToggle true if only the toggle command must be used
1018 * @param channel the channel
1019 * @param command the received channel command (OnOffType)
1020 * @param onCmd the command to be sent to the device to mute
1021 * @param offCmd the command to be sent to the device to unmute
1022 * @param toggleCmd the command to be sent to the device to toggle the mute state
1024 * @throws RotelException in case of communication error with the device
1026 private void handleMuteCmd(boolean onlyToggle, String channel, Command command, RotelCommand onCmd,
1027 RotelCommand offCmd, RotelCommand toggleCmd) throws RotelException {
1028 if (command instanceof OnOffType) {
1030 connector.sendCommand(toggleCmd);
1031 } else if (command == OnOffType.ON) {
1032 connector.sendCommand(onCmd);
1033 } else if (command == OnOffType.OFF) {
1034 connector.sendCommand(offCmd);
1037 logger.debug("Command {} from channel {} failed: invalid command value", command, channel);
1042 * Handle a tone level adjustment command (bass or treble)
1044 * @param current the current tone level
1045 * @param channel the channel
1046 * @param command the received channel command (IncreaseDecreaseType or DecimalType)
1047 * @param nbSelect the number of TONE_CONTROL_SELECT commands to be run to display the right tone (bass or treble)
1048 * @param upCmd the command to be sent to the device to increase the tone level
1049 * @param downCmd the command to be sent to the device to decrease the tone level
1050 * @param setCmd the command to be sent to the device to set the tone level at a value
1052 * @throws RotelException in case of communication error with the device
1053 * @throws InterruptedException in case of interruption during a thread sleep
1055 private void handleToneCmd(int current, String channel, Command command, int nbSelect, RotelCommand upCmd,
1056 RotelCommand downCmd, RotelCommand setCmd) throws RotelException, InterruptedException {
1057 if (command instanceof IncreaseDecreaseType && command == IncreaseDecreaseType.INCREASE) {
1058 selectToneControl(nbSelect);
1059 connector.sendCommand(upCmd);
1060 } else if (command instanceof IncreaseDecreaseType && command == IncreaseDecreaseType.DECREASE) {
1061 selectToneControl(nbSelect);
1062 connector.sendCommand(downCmd);
1063 } else if (command instanceof DecimalType) {
1064 int value = ((DecimalType) command).intValue();
1065 if (value >= minToneLevel && value <= maxToneLevel) {
1066 if (connector.getProtocol() != RotelProtocol.HEX) {
1067 connector.sendCommand(setCmd, value);
1068 } else if (value > current) {
1069 selectToneControl(nbSelect);
1070 connector.sendCommand(upCmd);
1071 } else if (value < current) {
1072 selectToneControl(nbSelect);
1073 connector.sendCommand(downCmd);
1077 logger.debug("Command {} from channel {} failed: invalid command value", command, channel);
1082 * Handle a tcbypass command (only for ASCII protocol)
1084 * @param channel the channel
1085 * @param command the received channel command (OnOffType)
1086 * @param onCmd the command to be sent to the device to bypass_on
1087 * @param offCmd the command to be sent to the device to bypass_off
1089 * @throws RotelException in case of communication error with the device
1091 private void handleTcbypassCmd(String channel, Command command, RotelCommand onCmd, RotelCommand offCmd)
1092 throws RotelException, InterruptedException {
1093 if (command instanceof OnOffType) {
1094 if (command == OnOffType.ON) {
1095 connector.sendCommand(onCmd);
1098 updateChannelState(CHANNEL_BASS);
1099 updateChannelState(CHANNEL_TREBLE);
1100 } else if (command == OnOffType.OFF) {
1101 connector.sendCommand(offCmd);
1103 connector.sendCommand(RotelCommand.BASS);
1105 connector.sendCommand(RotelCommand.TREBLE);
1108 logger.debug("Command {} from channel {} failed: invalid command value", command, channel);
1113 * Handle a speaker command
1115 * @param onlyToggle true if only the toggle command must be used
1116 * @param channel the channel
1117 * @param command the received channel command (OnOffType)
1118 * @param onCmd the command to be sent to the device to speaker_x_on
1119 * @param offCmd the command to be sent to the device to speaker_x_off
1120 * @param toggleCmd the command to be sent to the device to toggle the speaker_x state
1122 * @throws RotelException in case of communication error with the device
1124 private void handleSpeakerCmd(boolean onlyToggle, String channel, Command command, RotelCommand onCmd,
1125 RotelCommand offCmd, RotelCommand toggleCmd) throws RotelException {
1126 if (command instanceof OnOffType) {
1128 connector.sendCommand(toggleCmd);
1129 } else if (command == OnOffType.ON) {
1130 connector.sendCommand(onCmd);
1131 } else if (command == OnOffType.OFF) {
1132 connector.sendCommand(offCmd);
1135 logger.debug("Command {} from channel {} failed: invalid command value", command, channel);
1140 * Handle a tone balance adjustment command (left or right) (only for ASCII protocol)
1142 * @param channel the channel
1143 * @param command the received channel command (IncreaseDecreaseType or DecimalType)
1144 * @param rightCmd the command to be sent to the device to "increase" balance (shift to the right side)
1145 * @param leftCmd the command to be sent to the device to "decrease" balance (shift to the left side)
1146 * @param setCmd the command to be sent to the device to set the balance at a value
1148 * @throws RotelException in case of communication error with the device
1149 * @throws InterruptedException in case of interruption during a thread sleep
1151 private void handleBalanceCmd(String channel, Command command, RotelCommand leftCmd, RotelCommand rightCmd,
1152 RotelCommand setCmd) throws RotelException, InterruptedException {
1153 if (command instanceof IncreaseDecreaseType && command == IncreaseDecreaseType.INCREASE) {
1154 connector.sendCommand(rightCmd);
1155 } else if (command instanceof IncreaseDecreaseType && command == IncreaseDecreaseType.DECREASE) {
1156 connector.sendCommand(leftCmd);
1157 } else if (command instanceof DecimalType) {
1158 int value = ((DecimalType) command).intValue();
1159 if (value >= minBalanceLevel && value <= maxBalanceLevel) {
1160 connector.sendCommand(setCmd, value);
1163 logger.debug("Command {} from channel {} failed: invalid command value", command, channel);
1168 * Run a sequence of commands to display the current tone level (bass or treble) on the device front panel
1170 * @param nbSelect the number of TONE_CONTROL_SELECT commands to be run to display the right tone (bass or treble)
1172 * @throws RotelException in case of communication error with the device
1173 * @throws InterruptedException in case of interruption during a thread sleep
1175 private void selectToneControl(int nbSelect) throws RotelException, InterruptedException {
1176 // No tone control select command for RSX-1065
1177 if (connector.getProtocol() == RotelProtocol.HEX && connector.getModel() != RotelModel.RSX1065) {
1178 selectFeature(nbSelect, RotelCommand.RECORD_FONCTION_SELECT, RotelCommand.TONE_CONTROL_SELECT);
1183 * Run a sequence of commands to display a particular zone on the device front panel
1185 * @param zone the zone to be displayed (1 for main zone)
1186 * @param selectCommand the command to be sent to the device to switch the display between zones
1188 * @throws RotelException in case of communication error with the device
1189 * @throws InterruptedException in case of interruption during a thread sleep
1191 private void selectZone(int zone, @Nullable RotelCommand selectCommand)
1192 throws RotelException, InterruptedException {
1193 if (connector.getProtocol() == RotelProtocol.HEX && connector.getModel().getNbAdditionalZones() >= 1
1194 && zone >= 1 && zone != currentZone && selectCommand != null) {
1196 if (zone < currentZone) {
1197 nbSelect = zone + connector.getModel().getNbAdditionalZones() - currentZone;
1198 if (isPowerOn() && selectCommand == RotelCommand.RECORD_FONCTION_SELECT) {
1202 nbSelect = zone - currentZone;
1203 if (isPowerOn() && currentZone == 1 && selectCommand == RotelCommand.RECORD_FONCTION_SELECT
1204 && !selectingRecord) {
1208 selectFeature(nbSelect, null, selectCommand);
1213 * Run a sequence of commands to display a particular feature on the device front panel
1215 * @param nbSelect the number of select commands to be run
1216 * @param preCmd the initial command to be sent to the device (before the select commands)
1217 * @param selectCmd the select command to be sent to the device
1219 * @throws RotelException in case of communication error with the device
1220 * @throws InterruptedException in case of interruption during a thread sleep
1222 private void selectFeature(int nbSelect, @Nullable RotelCommand preCmd, RotelCommand selectCmd)
1223 throws RotelException, InterruptedException {
1224 if (connector.getProtocol() == RotelProtocol.HEX) {
1225 if (preCmd != null) {
1226 connector.sendCommand(preCmd);
1229 for (int i = 1; i <= nbSelect; i++) {
1230 connector.sendCommand(selectCmd);
1237 * Open the connection with the Rotel device
1239 * @return true if the connection is opened successfully or flase if not
1241 private synchronized boolean openConnection() {
1242 connector.addEventListener(this);
1245 } catch (RotelException e) {
1246 logger.debug("openConnection() failed", e);
1248 logger.debug("openConnection(): {}", connector.isConnected() ? "connected" : "disconnected");
1249 return connector.isConnected();
1253 * Close the connection with the Rotel device
1255 private synchronized void closeConnection() {
1257 connector.removeEventListener(this);
1258 logger.debug("closeConnection(): disconnected");
1262 public void onNewMessageEvent(EventObject event) {
1263 cancelPowerOffJob();
1265 RotelMessageEvent evt = (RotelMessageEvent) event;
1266 logger.debug("onNewMessageEvent: key {} = {}", evt.getKey(), evt.getValue());
1268 String key = evt.getKey();
1269 String value = evt.getValue().trim();
1270 if (!RotelConnector.KEY_ERROR.equals(key)) {
1271 updateStatus(ThingStatus.ONLINE);
1275 case RotelConnector.KEY_ERROR:
1276 logger.debug("Reading feedback message failed");
1277 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
1278 "@text/offline.comm-error-reading-thread");
1281 case RotelConnector.KEY_LINE1:
1282 frontPanelLine1 = value;
1283 updateChannelState(CHANNEL_LINE1);
1285 case RotelConnector.KEY_LINE2:
1286 frontPanelLine2 = value;
1287 updateChannelState(CHANNEL_LINE2);
1289 case RotelConnector.KEY_ZONE:
1290 currentZone = Integer.parseInt(value);
1292 case RotelConnector.KEY_RECORD_SEL:
1293 selectingRecord = RotelConnector.MSG_VALUE_ON.equalsIgnoreCase(value);
1295 case RotelConnector.KEY_POWER:
1296 if (RotelConnector.POWER_ON.equalsIgnoreCase(value)) {
1298 } else if (RotelConnector.STANDBY.equalsIgnoreCase(value)) {
1300 } else if (RotelConnector.POWER_OFF_DELAYED.equalsIgnoreCase(value)) {
1301 schedulePowerOffJob(false);
1303 throw new RotelException("Invalid value");
1306 case RotelConnector.KEY_POWER_ZONE2:
1307 if (RotelConnector.POWER_ON.equalsIgnoreCase(value)) {
1308 handlePowerOnZone2();
1309 } else if (RotelConnector.STANDBY.equalsIgnoreCase(value)) {
1310 handlePowerOffZone2();
1312 throw new RotelException("Invalid value");
1315 case RotelConnector.KEY_POWER_ZONE3:
1316 if (RotelConnector.POWER_ON.equalsIgnoreCase(value)) {
1317 handlePowerOnZone3();
1318 } else if (RotelConnector.STANDBY.equalsIgnoreCase(value)) {
1319 handlePowerOffZone3();
1321 throw new RotelException("Invalid value");
1324 case RotelConnector.KEY_POWER_ZONE4:
1325 if (RotelConnector.POWER_ON.equalsIgnoreCase(value)) {
1326 handlePowerOnZone4();
1327 } else if (RotelConnector.STANDBY.equalsIgnoreCase(value)) {
1328 handlePowerOffZone4();
1330 throw new RotelException("Invalid value");
1333 case RotelConnector.KEY_VOLUME_MIN:
1334 minVolume = Integer.parseInt(value);
1335 if (!connector.getModel().hasDirectVolumeControl()) {
1336 logger.info("Set minValue to {} for your sitemap widget attached to your volume item.",
1340 case RotelConnector.KEY_VOLUME_MAX:
1341 maxVolume = Integer.parseInt(value);
1342 if (!connector.getModel().hasDirectVolumeControl()) {
1343 logger.info("Set maxValue to {} for your sitemap widget attached to your volume item.",
1347 case RotelConnector.KEY_VOLUME:
1348 if (RotelConnector.MSG_VALUE_MIN.equalsIgnoreCase(value)) {
1350 } else if (RotelConnector.MSG_VALUE_MAX.equalsIgnoreCase(value)) {
1353 volume = Integer.parseInt(value);
1355 updateChannelState(CHANNEL_VOLUME);
1356 updateChannelState(CHANNEL_MAIN_VOLUME);
1357 updateChannelState(CHANNEL_MAIN_VOLUME_UP_DOWN);
1359 case RotelConnector.KEY_MUTE:
1360 if (RotelConnector.MSG_VALUE_ON.equalsIgnoreCase(value)) {
1362 updateChannelState(CHANNEL_MUTE);
1363 updateChannelState(CHANNEL_MAIN_MUTE);
1364 } else if (RotelConnector.MSG_VALUE_OFF.equalsIgnoreCase(value)) {
1366 updateChannelState(CHANNEL_MUTE);
1367 updateChannelState(CHANNEL_MAIN_MUTE);
1369 throw new RotelException("Invalid value");
1372 case RotelConnector.KEY_VOLUME_ZONE2:
1373 fixedVolumeZone2 = false;
1374 if (RotelConnector.MSG_VALUE_FIX.equalsIgnoreCase(value)) {
1375 fixedVolumeZone2 = true;
1376 } else if (RotelConnector.MSG_VALUE_MIN.equalsIgnoreCase(value)) {
1377 volumeZone2 = minVolume;
1378 } else if (RotelConnector.MSG_VALUE_MAX.equalsIgnoreCase(value)) {
1379 volumeZone2 = maxVolume;
1381 volumeZone2 = Integer.parseInt(value);
1383 updateChannelState(CHANNEL_ZONE2_VOLUME);
1384 updateChannelState(CHANNEL_ZONE2_VOLUME_UP_DOWN);
1386 case RotelConnector.KEY_VOLUME_ZONE3:
1387 fixedVolumeZone3 = false;
1388 if (RotelConnector.MSG_VALUE_FIX.equalsIgnoreCase(value)) {
1389 fixedVolumeZone3 = true;
1390 } else if (RotelConnector.MSG_VALUE_MIN.equalsIgnoreCase(value)) {
1391 volumeZone3 = minVolume;
1392 } else if (RotelConnector.MSG_VALUE_MAX.equalsIgnoreCase(value)) {
1393 volumeZone3 = maxVolume;
1395 volumeZone3 = Integer.parseInt(value);
1397 updateChannelState(CHANNEL_ZONE3_VOLUME);
1399 case RotelConnector.KEY_VOLUME_ZONE4:
1400 fixedVolumeZone4 = false;
1401 if (RotelConnector.MSG_VALUE_FIX.equalsIgnoreCase(value)) {
1402 fixedVolumeZone4 = true;
1403 } else if (RotelConnector.MSG_VALUE_MIN.equalsIgnoreCase(value)) {
1404 volumeZone4 = minVolume;
1405 } else if (RotelConnector.MSG_VALUE_MAX.equalsIgnoreCase(value)) {
1406 volumeZone4 = maxVolume;
1408 volumeZone4 = Integer.parseInt(value);
1410 updateChannelState(CHANNEL_ZONE4_VOLUME);
1412 case RotelConnector.KEY_MUTE_ZONE2:
1413 if (RotelConnector.MSG_VALUE_ON.equalsIgnoreCase(value)) {
1415 updateChannelState(CHANNEL_ZONE2_MUTE);
1416 } else if (RotelConnector.MSG_VALUE_OFF.equalsIgnoreCase(value)) {
1418 updateChannelState(CHANNEL_ZONE2_MUTE);
1420 throw new RotelException("Invalid value");
1423 case RotelConnector.KEY_MUTE_ZONE3:
1424 if (RotelConnector.MSG_VALUE_ON.equalsIgnoreCase(value)) {
1426 updateChannelState(CHANNEL_ZONE3_MUTE);
1427 } else if (RotelConnector.MSG_VALUE_OFF.equalsIgnoreCase(value)) {
1429 updateChannelState(CHANNEL_ZONE3_MUTE);
1431 throw new RotelException("Invalid value");
1434 case RotelConnector.KEY_MUTE_ZONE4:
1435 if (RotelConnector.MSG_VALUE_ON.equalsIgnoreCase(value)) {
1437 updateChannelState(CHANNEL_ZONE4_MUTE);
1438 } else if (RotelConnector.MSG_VALUE_OFF.equalsIgnoreCase(value)) {
1440 updateChannelState(CHANNEL_ZONE4_MUTE);
1442 throw new RotelException("Invalid value");
1445 case RotelConnector.KEY_TONE_MAX:
1446 maxToneLevel = Integer.parseInt(value);
1447 minToneLevel = -maxToneLevel;
1449 "Set minValue to {} and maxValue to {} for your sitemap widget attached to your bass or treble item.",
1450 minToneLevel, maxToneLevel);
1452 case RotelConnector.KEY_BASS:
1453 if (RotelConnector.MSG_VALUE_MIN.equalsIgnoreCase(value)) {
1454 bass = minToneLevel;
1455 } else if (RotelConnector.MSG_VALUE_MAX.equalsIgnoreCase(value)) {
1456 bass = maxToneLevel;
1458 bass = Integer.parseInt(value);
1460 updateChannelState(CHANNEL_BASS);
1461 updateChannelState(CHANNEL_MAIN_BASS);
1463 case RotelConnector.KEY_TREBLE:
1464 if (RotelConnector.MSG_VALUE_MIN.equalsIgnoreCase(value)) {
1465 treble = minToneLevel;
1466 } else if (RotelConnector.MSG_VALUE_MAX.equalsIgnoreCase(value)) {
1467 treble = maxToneLevel;
1469 treble = Integer.parseInt(value);
1471 updateChannelState(CHANNEL_TREBLE);
1472 updateChannelState(CHANNEL_MAIN_TREBLE);
1474 case RotelConnector.KEY_SOURCE:
1475 source = connector.getModel().getSourceFromCommand(RotelCommand.getFromAsciiCommand(value));
1476 updateChannelState(CHANNEL_SOURCE);
1477 updateChannelState(CHANNEL_MAIN_SOURCE);
1479 case RotelConnector.KEY_RECORD:
1480 recordSource = connector.getModel()
1481 .getRecordSourceFromCommand(RotelCommand.getFromAsciiCommand(value));
1482 updateChannelState(CHANNEL_MAIN_RECORD_SOURCE);
1484 case RotelConnector.KEY_SOURCE_ZONE2:
1485 sourceZone2 = connector.getModel()
1486 .getZone2SourceFromCommand(RotelCommand.getFromAsciiCommand(value));
1487 updateChannelState(CHANNEL_ZONE2_SOURCE);
1489 case RotelConnector.KEY_SOURCE_ZONE3:
1490 sourceZone3 = connector.getModel()
1491 .getZone3SourceFromCommand(RotelCommand.getFromAsciiCommand(value));
1492 updateChannelState(CHANNEL_ZONE3_SOURCE);
1494 case RotelConnector.KEY_SOURCE_ZONE4:
1495 sourceZone4 = connector.getModel()
1496 .getZone4SourceFromCommand(RotelCommand.getFromAsciiCommand(value));
1497 updateChannelState(CHANNEL_ZONE4_SOURCE);
1499 case RotelConnector.KEY_DSP_MODE:
1500 if ("dolby_pliix_movie".equals(value)) {
1501 value = "dolby_plii_movie";
1502 } else if ("dolby_pliix_music".equals(value)) {
1503 value = "dolby_plii_music";
1504 } else if ("dolby_pliix_game".equals(value)) {
1505 value = "dolby_plii_game";
1507 dsp = connector.getModel().getDspFromFeedback(value);
1508 logger.debug("DSP {}", dsp.getName());
1509 updateChannelState(CHANNEL_DSP);
1510 updateChannelState(CHANNEL_MAIN_DSP);
1512 case RotelConnector.KEY1_PLAY_STATUS:
1513 case RotelConnector.KEY2_PLAY_STATUS:
1514 if (RotelConnector.PLAY.equalsIgnoreCase(value)) {
1515 playStatus = RotelPlayStatus.PLAYING;
1516 updateChannelState(CHANNEL_PLAY_CONTROL);
1517 } else if (RotelConnector.PAUSE.equalsIgnoreCase(value)) {
1518 playStatus = RotelPlayStatus.PAUSED;
1519 updateChannelState(CHANNEL_PLAY_CONTROL);
1520 } else if (RotelConnector.STOP.equalsIgnoreCase(value)) {
1521 playStatus = RotelPlayStatus.STOPPED;
1522 updateChannelState(CHANNEL_PLAY_CONTROL);
1524 throw new RotelException("Invalid value");
1527 case RotelConnector.KEY_TRACK:
1528 if (source.getName().equals("CD") && !connector.getModel().hasSourceControl()) {
1529 track = Integer.parseInt(value);
1530 updateChannelState(CHANNEL_TRACK);
1533 case RotelConnector.KEY_FREQ:
1534 if (RotelConnector.MSG_VALUE_OFF.equalsIgnoreCase(value)) {
1537 // Suppress a potential ending "k" or "K"
1538 if (value.toUpperCase().endsWith("K")) {
1539 value = value.substring(0, value.length() - 1);
1541 frequency = Double.parseDouble(value);
1543 updateChannelState(CHANNEL_FREQUENCY);
1545 case RotelConnector.KEY_DIMMER:
1546 brightness = Integer.parseInt(value);
1547 updateChannelState(CHANNEL_BRIGHTNESS);
1549 case RotelConnector.KEY_UPDATE_MODE:
1550 case RotelConnector.KEY_DISPLAY_UPDATE:
1552 case RotelConnector.KEY_TONE:
1553 if (RotelConnector.MSG_VALUE_ON.equalsIgnoreCase(value)) {
1555 updateChannelState(CHANNEL_TCBYPASS);
1556 } else if (RotelConnector.MSG_VALUE_OFF.equalsIgnoreCase(value)) {
1558 updateChannelState(CHANNEL_TCBYPASS);
1560 throw new RotelException("Invalid value");
1563 case RotelConnector.KEY_TCBYPASS:
1564 if (RotelConnector.MSG_VALUE_ON.equalsIgnoreCase(value)) {
1566 updateChannelState(CHANNEL_TCBYPASS);
1567 } else if (RotelConnector.MSG_VALUE_OFF.equalsIgnoreCase(value)) {
1569 updateChannelState(CHANNEL_TCBYPASS);
1571 throw new RotelException("Invalid value");
1574 case RotelConnector.KEY_BALANCE:
1575 if (RotelConnector.MSG_VALUE_MIN.equalsIgnoreCase(value)) {
1576 balance = minBalanceLevel;
1577 } else if (RotelConnector.MSG_VALUE_MAX.equalsIgnoreCase(value)) {
1578 balance = maxBalanceLevel;
1579 } else if (value.toUpperCase().startsWith("L")) {
1580 balance = -Integer.parseInt(value.substring(1));
1581 } else if (value.toLowerCase().startsWith("R")) {
1582 balance = Integer.parseInt(value.substring(1));
1584 balance = Integer.parseInt(value);
1586 updateChannelState(CHANNEL_BALANCE);
1588 case RotelConnector.KEY_SPEAKER:
1589 if (RotelConnector.MSG_VALUE_SPEAKER_A.equalsIgnoreCase(value)) {
1592 updateChannelState(CHANNEL_SPEAKER_A);
1593 updateChannelState(CHANNEL_SPEAKER_B);
1594 } else if (RotelConnector.MSG_VALUE_SPEAKER_B.equalsIgnoreCase(value)) {
1597 updateChannelState(CHANNEL_SPEAKER_A);
1598 updateChannelState(CHANNEL_SPEAKER_B);
1599 } else if (RotelConnector.MSG_VALUE_SPEAKER_AB.equalsIgnoreCase(value)) {
1602 updateChannelState(CHANNEL_SPEAKER_A);
1603 updateChannelState(CHANNEL_SPEAKER_B);
1604 } else if (RotelConnector.MSG_VALUE_OFF.equalsIgnoreCase(value)) {
1607 updateChannelState(CHANNEL_SPEAKER_A);
1608 updateChannelState(CHANNEL_SPEAKER_B);
1610 throw new RotelException("Invalid value");
1614 logger.debug("onNewMessageEvent: unhandled key {}", key);
1617 } catch (NumberFormatException | RotelException e) {
1618 logger.debug("Invalid value {} for key {}", value, key);
1623 * Handle the received information that device power (main zone) is ON
1625 private void handlePowerOn() {
1626 Boolean prev = power;
1628 updateChannelState(CHANNEL_POWER);
1629 updateChannelState(CHANNEL_MAIN_POWER);
1630 if ((prev == null) || !prev) {
1631 schedulePowerOnJob();
1636 * Handle the received information that device power (main zone) is OFF
1638 private void handlePowerOff() {
1641 updateChannelState(CHANNEL_POWER);
1642 updateChannelState(CHANNEL_MAIN_POWER);
1643 updateChannelState(CHANNEL_SOURCE);
1644 updateChannelState(CHANNEL_MAIN_SOURCE);
1645 updateChannelState(CHANNEL_MAIN_RECORD_SOURCE);
1646 updateChannelState(CHANNEL_DSP);
1647 updateChannelState(CHANNEL_MAIN_DSP);
1648 updateChannelState(CHANNEL_VOLUME);
1649 updateChannelState(CHANNEL_MAIN_VOLUME);
1650 updateChannelState(CHANNEL_MAIN_VOLUME_UP_DOWN);
1651 updateChannelState(CHANNEL_MUTE);
1652 updateChannelState(CHANNEL_MAIN_MUTE);
1653 updateChannelState(CHANNEL_BASS);
1654 updateChannelState(CHANNEL_MAIN_BASS);
1655 updateChannelState(CHANNEL_TREBLE);
1656 updateChannelState(CHANNEL_MAIN_TREBLE);
1657 updateChannelState(CHANNEL_PLAY_CONTROL);
1658 updateChannelState(CHANNEL_TRACK);
1659 updateChannelState(CHANNEL_FREQUENCY);
1660 updateChannelState(CHANNEL_BRIGHTNESS);
1661 updateChannelState(CHANNEL_TCBYPASS);
1662 updateChannelState(CHANNEL_BALANCE);
1663 updateChannelState(CHANNEL_SPEAKER_A);
1664 updateChannelState(CHANNEL_SPEAKER_B);
1668 * Handle the received information that zone 2 power is ON
1670 private void handlePowerOnZone2() {
1671 boolean prev = powerZone2;
1673 updateChannelState(CHANNEL_ZONE2_POWER);
1675 schedulePowerOnZone2Job();
1680 * Handle the received information that zone 2 power is OFF
1682 private void handlePowerOffZone2() {
1683 cancelPowerOnZone2Job();
1685 updateChannelState(CHANNEL_ZONE2_POWER);
1686 updateChannelState(CHANNEL_ZONE2_SOURCE);
1687 updateChannelState(CHANNEL_ZONE2_VOLUME);
1688 updateChannelState(CHANNEL_ZONE2_VOLUME_UP_DOWN);
1689 updateChannelState(CHANNEL_ZONE2_MUTE);
1693 * Handle the received information that zone 3 power is ON
1695 private void handlePowerOnZone3() {
1696 boolean prev = powerZone3;
1698 updateChannelState(CHANNEL_ZONE3_POWER);
1700 schedulePowerOnZone3Job();
1705 * Handle the received information that zone 3 power is OFF
1707 private void handlePowerOffZone3() {
1708 cancelPowerOnZone3Job();
1710 updateChannelState(CHANNEL_ZONE3_POWER);
1711 updateChannelState(CHANNEL_ZONE3_SOURCE);
1712 updateChannelState(CHANNEL_ZONE3_VOLUME);
1713 updateChannelState(CHANNEL_ZONE3_MUTE);
1717 * Handle the received information that zone 4 power is ON
1719 private void handlePowerOnZone4() {
1720 boolean prev = powerZone4;
1722 updateChannelState(CHANNEL_ZONE4_POWER);
1724 schedulePowerOnZone4Job();
1729 * Handle the received information that zone 4 power is OFF
1731 private void handlePowerOffZone4() {
1732 cancelPowerOnZone4Job();
1734 updateChannelState(CHANNEL_ZONE4_POWER);
1735 updateChannelState(CHANNEL_ZONE4_SOURCE);
1736 updateChannelState(CHANNEL_ZONE4_VOLUME);
1737 updateChannelState(CHANNEL_ZONE4_MUTE);
1741 * Schedule the job that will consider the device as OFF if no new event is received before its running
1743 * @param switchOffAllZones true if all zones have to be considered as OFF
1745 private void schedulePowerOffJob(boolean switchOffAllZones) {
1746 logger.debug("Schedule power OFF job");
1747 cancelPowerOffJob();
1748 powerOffJob = scheduler.schedule(() -> {
1749 logger.debug("Power OFF job");
1751 if (switchOffAllZones) {
1752 handlePowerOffZone2();
1753 handlePowerOffZone3();
1754 handlePowerOffZone4();
1756 }, 2000, TimeUnit.MILLISECONDS);
1760 * Cancel the job that will consider the device as OFF
1762 private void cancelPowerOffJob() {
1763 ScheduledFuture<?> powerOffJob = this.powerOffJob;
1764 if (powerOffJob != null && !powerOffJob.isCancelled()) {
1765 powerOffJob.cancel(true);
1766 this.powerOffJob = null;
1771 * Schedule the job to run with a few seconds delay when the device power (main zone) switched ON
1773 private void schedulePowerOnJob() {
1774 logger.debug("Schedule power ON job");
1776 powerOnJob = scheduler.schedule(() -> {
1777 synchronized (sequenceLock) {
1778 logger.debug("Power ON job");
1780 switch (connector.getProtocol()) {
1782 if (connector.getModel().getRespNbChars() <= 13
1783 && connector.getModel().hasVolumeControl()) {
1784 connector.sendCommand(getVolumeDownCommand());
1786 connector.sendCommand(getVolumeUpCommand());
1789 if (connector.getModel().getNbAdditionalZones() >= 1) {
1790 if (currentZone != 1 && connector.getModel()
1791 .getZoneSelectCmd() == RotelCommand.RECORD_FONCTION_SELECT) {
1792 selectZone(1, connector.getModel().getZoneSelectCmd());
1793 } else if (!selectingRecord) {
1794 connector.sendCommand(RotelCommand.RECORD_FONCTION_SELECT);
1798 connector.sendCommand(RotelCommand.RECORD_FONCTION_SELECT);
1801 if (connector.getModel().hasToneControl()) {
1802 if (connector.getModel() == RotelModel.RSX1065) {
1803 // No tone control select command
1804 connector.sendCommand(RotelCommand.TREBLE_DOWN);
1806 connector.sendCommand(RotelCommand.TREBLE_UP);
1808 connector.sendCommand(RotelCommand.BASS_DOWN);
1810 connector.sendCommand(RotelCommand.BASS_UP);
1813 selectFeature(2, null, RotelCommand.TONE_CONTROL_SELECT);
1818 if (connector.getModel() != RotelModel.RAP1580 && connector.getModel() != RotelModel.RDD1580
1819 && connector.getModel() != RotelModel.RSP1576
1820 && connector.getModel() != RotelModel.RSP1582) {
1821 connector.sendCommand(RotelCommand.UPDATE_AUTO);
1822 Thread.sleep(SLEEP_INTV);
1824 if (connector.getModel().hasSourceControl()) {
1825 connector.sendCommand(RotelCommand.SOURCE);
1826 Thread.sleep(SLEEP_INTV);
1828 if (connector.getModel().hasVolumeControl() || connector.getModel().hasToneControl()) {
1829 if (connector.getModel().hasVolumeControl()
1830 && connector.getModel() != RotelModel.RAP1580
1831 && connector.getModel() != RotelModel.RSP1576
1832 && connector.getModel() != RotelModel.RSP1582) {
1833 connector.sendCommand(RotelCommand.VOLUME_GET_MIN);
1834 Thread.sleep(SLEEP_INTV);
1835 connector.sendCommand(RotelCommand.VOLUME_GET_MAX);
1836 Thread.sleep(SLEEP_INTV);
1838 if (connector.getModel().hasToneControl()) {
1839 connector.sendCommand(RotelCommand.TONE_MAX);
1840 Thread.sleep(SLEEP_INTV);
1842 // Wait enough to be sure to get the min/max values requested just before
1844 if (connector.getModel().hasVolumeControl()) {
1845 connector.sendCommand(RotelCommand.VOLUME_GET);
1846 Thread.sleep(SLEEP_INTV);
1847 if (connector.getModel() != RotelModel.RA11
1848 && connector.getModel() != RotelModel.RA12
1849 && connector.getModel() != RotelModel.RCX1500) {
1850 connector.sendCommand(RotelCommand.MUTE);
1851 Thread.sleep(SLEEP_INTV);
1854 if (connector.getModel().hasToneControl()) {
1855 connector.sendCommand(RotelCommand.BASS);
1856 Thread.sleep(SLEEP_INTV);
1857 connector.sendCommand(RotelCommand.TREBLE);
1858 Thread.sleep(SLEEP_INTV);
1859 connector.sendCommand(RotelCommand.TONE_CONTROLS);
1860 Thread.sleep(SLEEP_INTV);
1863 if (connector.getModel().hasBalanceControl()) {
1864 connector.sendCommand(RotelCommand.BALANCE);
1865 Thread.sleep(SLEEP_INTV);
1867 if (connector.getModel().hasPlayControl()) {
1868 if (connector.getModel() != RotelModel.RCD1570
1869 && connector.getModel() != RotelModel.RCD1572
1870 && (connector.getModel() != RotelModel.RCX1500
1871 || !source.getName().equals("CD"))) {
1872 connector.sendCommand(RotelCommand.PLAY_STATUS);
1873 Thread.sleep(SLEEP_INTV);
1875 connector.sendCommand(RotelCommand.CD_PLAY_STATUS);
1876 Thread.sleep(SLEEP_INTV);
1879 if (connector.getModel().hasDspControl()) {
1880 connector.sendCommand(RotelCommand.DSP_MODE);
1881 Thread.sleep(SLEEP_INTV);
1883 if (connector.getModel().canGetFrequency()) {
1884 connector.sendCommand(RotelCommand.FREQUENCY);
1885 Thread.sleep(SLEEP_INTV);
1887 if (connector.getModel().hasDimmerControl() && connector.getModel().canGetDimmerLevel()) {
1888 connector.sendCommand(RotelCommand.DIMMER_LEVEL_GET);
1889 Thread.sleep(SLEEP_INTV);
1891 if (connector.getModel().hasSpeakerGroups()) {
1892 connector.sendCommand(RotelCommand.SPEAKER);
1893 Thread.sleep(SLEEP_INTV);
1897 connector.sendCommand(RotelCommand.UPDATE_AUTO);
1898 Thread.sleep(SLEEP_INTV);
1899 if (connector.getModel().hasSourceControl()) {
1900 connector.sendCommand(RotelCommand.SOURCE);
1901 Thread.sleep(SLEEP_INTV);
1903 if (connector.getModel().hasVolumeControl()) {
1904 connector.sendCommand(RotelCommand.VOLUME_GET);
1905 Thread.sleep(SLEEP_INTV);
1906 connector.sendCommand(RotelCommand.MUTE);
1907 Thread.sleep(SLEEP_INTV);
1909 if (connector.getModel().hasToneControl()) {
1910 connector.sendCommand(RotelCommand.BASS);
1911 Thread.sleep(SLEEP_INTV);
1912 connector.sendCommand(RotelCommand.TREBLE);
1913 Thread.sleep(SLEEP_INTV);
1914 connector.sendCommand(RotelCommand.TCBYPASS);
1915 Thread.sleep(SLEEP_INTV);
1917 if (connector.getModel().hasBalanceControl()) {
1918 connector.sendCommand(RotelCommand.BALANCE);
1919 Thread.sleep(SLEEP_INTV);
1921 if (connector.getModel().hasPlayControl()) {
1922 connector.sendCommand(RotelCommand.PLAY_STATUS);
1923 Thread.sleep(SLEEP_INTV);
1924 if (source.getName().equals("CD") && !connector.getModel().hasSourceControl()) {
1925 connector.sendCommand(RotelCommand.TRACK);
1926 Thread.sleep(SLEEP_INTV);
1929 if (connector.getModel().hasDspControl()) {
1930 connector.sendCommand(RotelCommand.DSP_MODE);
1931 Thread.sleep(SLEEP_INTV);
1933 if (connector.getModel().canGetFrequency()) {
1934 connector.sendCommand(RotelCommand.FREQUENCY);
1935 Thread.sleep(SLEEP_INTV);
1937 if (connector.getModel().hasDimmerControl() && connector.getModel().canGetDimmerLevel()) {
1938 connector.sendCommand(RotelCommand.DIMMER_LEVEL_GET);
1939 Thread.sleep(SLEEP_INTV);
1941 if (connector.getModel().hasSpeakerGroups()) {
1942 connector.sendCommand(RotelCommand.SPEAKER);
1943 Thread.sleep(SLEEP_INTV);
1947 } catch (RotelException e) {
1948 logger.debug("Init sequence failed: {}", e.getMessage());
1949 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
1950 "@text/offline.comm-error-init-sequence");
1952 } catch (InterruptedException e) {
1953 logger.debug("Init sequence interrupted: {}", e.getMessage());
1954 Thread.currentThread().interrupt();
1957 }, 2500, TimeUnit.MILLISECONDS);
1961 * Cancel the job scheduled when the device power (main zone) switched ON
1963 private void cancelPowerOnJob() {
1964 ScheduledFuture<?> powerOnJob = this.powerOnJob;
1965 if (powerOnJob != null && !powerOnJob.isCancelled()) {
1966 powerOnJob.cancel(true);
1967 this.powerOnJob = null;
1972 * Schedule the job to run with a few seconds delay when the zone 2 power switched ON
1974 private void schedulePowerOnZone2Job() {
1975 logger.debug("Schedule power ON zone 2 job");
1976 cancelPowerOnZone2Job();
1977 powerOnZone2Job = scheduler.schedule(() -> {
1978 synchronized (sequenceLock) {
1979 logger.debug("Power ON zone 2 job");
1981 if (connector.getProtocol() == RotelProtocol.HEX
1982 && connector.getModel().getNbAdditionalZones() >= 1) {
1983 selectZone(2, connector.getModel().getZoneSelectCmd());
1984 connector.sendCommand(connector.getModel().hasZone2Commands() ? RotelCommand.ZONE2_VOLUME_DOWN
1985 : RotelCommand.VOLUME_DOWN);
1987 connector.sendCommand(connector.getModel().hasZone2Commands() ? RotelCommand.ZONE2_VOLUME_UP
1988 : RotelCommand.VOLUME_UP);
1991 } catch (RotelException e) {
1992 logger.debug("Init sequence zone 2 failed: {}", e.getMessage());
1993 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
1994 "@text/offline.comm-error-init-sequence-zone [\"2\"]");
1996 } catch (InterruptedException e) {
1997 logger.debug("Init sequence zone 2 interrupted: {}", e.getMessage());
1998 Thread.currentThread().interrupt();
2001 }, 2500, TimeUnit.MILLISECONDS);
2005 * Cancel the job scheduled when the zone 2 power switched ON
2007 private void cancelPowerOnZone2Job() {
2008 ScheduledFuture<?> powerOnZone2Job = this.powerOnZone2Job;
2009 if (powerOnZone2Job != null && !powerOnZone2Job.isCancelled()) {
2010 powerOnZone2Job.cancel(true);
2011 this.powerOnZone2Job = null;
2016 * Schedule the job to run with a few seconds delay when the zone 3 power switched ON
2018 private void schedulePowerOnZone3Job() {
2019 logger.debug("Schedule power ON zone 3 job");
2020 cancelPowerOnZone3Job();
2021 powerOnZone3Job = scheduler.schedule(() -> {
2022 synchronized (sequenceLock) {
2023 logger.debug("Power ON zone 3 job");
2025 if (connector.getProtocol() == RotelProtocol.HEX
2026 && connector.getModel().getNbAdditionalZones() >= 2) {
2027 selectZone(3, connector.getModel().getZoneSelectCmd());
2028 connector.sendCommand(connector.getModel().hasZone3Commands() ? RotelCommand.ZONE3_VOLUME_DOWN
2029 : RotelCommand.VOLUME_DOWN);
2031 connector.sendCommand(connector.getModel().hasZone3Commands() ? RotelCommand.ZONE3_VOLUME_UP
2032 : RotelCommand.VOLUME_UP);
2035 } catch (RotelException e) {
2036 logger.debug("Init sequence zone 3 failed: {}", e.getMessage());
2037 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
2038 "@text/offline.comm-error-init-sequence-zone [\"3\"]");
2040 } catch (InterruptedException e) {
2041 logger.debug("Init sequence zone 3 interrupted: {}", e.getMessage());
2042 Thread.currentThread().interrupt();
2045 }, 2500, TimeUnit.MILLISECONDS);
2049 * Cancel the job scheduled when the zone 3 power switched ON
2051 private void cancelPowerOnZone3Job() {
2052 ScheduledFuture<?> powerOnZone3Job = this.powerOnZone3Job;
2053 if (powerOnZone3Job != null && !powerOnZone3Job.isCancelled()) {
2054 powerOnZone3Job.cancel(true);
2055 this.powerOnZone3Job = null;
2060 * Schedule the job to run with a few seconds delay when the zone 4 power switched ON
2062 private void schedulePowerOnZone4Job() {
2063 logger.debug("Schedule power ON zone 4 job");
2064 cancelPowerOnZone4Job();
2065 powerOnZone4Job = scheduler.schedule(() -> {
2066 synchronized (sequenceLock) {
2067 logger.debug("Power ON zone 4 job");
2069 if (connector.getProtocol() == RotelProtocol.HEX
2070 && connector.getModel().getNbAdditionalZones() >= 3) {
2071 selectZone(4, connector.getModel().getZoneSelectCmd());
2072 connector.sendCommand(connector.getModel().hasZone4Commands() ? RotelCommand.ZONE4_VOLUME_DOWN
2073 : RotelCommand.VOLUME_DOWN);
2075 connector.sendCommand(connector.getModel().hasZone4Commands() ? RotelCommand.ZONE4_VOLUME_UP
2076 : RotelCommand.VOLUME_UP);
2079 } catch (RotelException e) {
2080 logger.debug("Init sequence zone 4 failed: {}", e.getMessage());
2081 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
2082 "@text/offline.comm-error-init-sequence-zone [\"4\"]");
2084 } catch (InterruptedException e) {
2085 logger.debug("Init sequence zone 4 interrupted: {}", e.getMessage());
2086 Thread.currentThread().interrupt();
2089 }, 2500, TimeUnit.MILLISECONDS);
2093 * Cancel the job scheduled when the zone 4 power switched ON
2095 private void cancelPowerOnZone4Job() {
2096 ScheduledFuture<?> powerOnZone4Job = this.powerOnZone4Job;
2097 if (powerOnZone4Job != null && !powerOnZone4Job.isCancelled()) {
2098 powerOnZone4Job.cancel(true);
2099 this.powerOnZone4Job = null;
2104 * Schedule the reconnection job
2106 private void scheduleReconnectJob() {
2107 logger.debug("Schedule reconnect job");
2108 cancelReconnectJob();
2109 reconnectJob = scheduler.scheduleWithFixedDelay(() -> {
2110 if (!connector.isConnected()) {
2111 logger.debug("Trying to reconnect...");
2114 String error = null;
2115 if (openConnection()) {
2116 synchronized (sequenceLock) {
2117 schedulePowerOffJob(true);
2119 connector.sendCommand(connector.getModel().getPowerStateCmd());
2120 } catch (RotelException e) {
2121 error = "@text/offline.comm-error-first-command-after-reconnection";
2122 logger.debug("First command after connection failed", e);
2123 cancelPowerOffJob();
2128 error = "@text/offline.comm-error-reconnection";
2130 if (error != null) {
2132 handlePowerOffZone2();
2133 handlePowerOffZone3();
2134 handlePowerOffZone4();
2135 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, error);
2137 updateStatus(ThingStatus.ONLINE);
2140 }, 1, POLLING_INTERVAL, TimeUnit.SECONDS);
2144 * Cancel the reconnection job
2146 private void cancelReconnectJob() {
2147 ScheduledFuture<?> reconnectJob = this.reconnectJob;
2148 if (reconnectJob != null && !reconnectJob.isCancelled()) {
2149 reconnectJob.cancel(true);
2150 this.reconnectJob = null;
2155 * Update the state of a channel
2157 * @param channel the channel
2159 private void updateChannelState(String channel) {
2160 if (!isLinked(channel)) {
2163 State state = UnDefType.UNDEF;
2166 case CHANNEL_MAIN_POWER:
2169 state = OnOffType.from(po.booleanValue());
2172 case CHANNEL_ZONE2_POWER:
2173 state = OnOffType.from(powerZone2);
2175 case CHANNEL_ZONE3_POWER:
2176 state = OnOffType.from(powerZone3);
2178 case CHANNEL_ZONE4_POWER:
2179 state = OnOffType.from(powerZone4);
2181 case CHANNEL_SOURCE:
2182 case CHANNEL_MAIN_SOURCE:
2184 state = new StringType(source.getName());
2187 case CHANNEL_MAIN_RECORD_SOURCE:
2188 RotelSource recordSource = this.recordSource;
2189 if (isPowerOn() && recordSource != null) {
2190 state = new StringType(recordSource.getName());
2193 case CHANNEL_ZONE2_SOURCE:
2194 RotelSource sourceZone2 = this.sourceZone2;
2195 if (powerZone2 && sourceZone2 != null) {
2196 state = new StringType(sourceZone2.getName());
2199 case CHANNEL_ZONE3_SOURCE:
2200 RotelSource sourceZone3 = this.sourceZone3;
2201 if (powerZone3 && sourceZone3 != null) {
2202 state = new StringType(sourceZone3.getName());
2205 case CHANNEL_ZONE4_SOURCE:
2206 RotelSource sourceZone4 = this.sourceZone4;
2207 if (powerZone4 && sourceZone4 != null) {
2208 state = new StringType(sourceZone4.getName());
2212 case CHANNEL_MAIN_DSP:
2214 state = new StringType(dsp.getName());
2217 case CHANNEL_VOLUME:
2218 case CHANNEL_MAIN_VOLUME:
2220 long volumePct = Math
2221 .round((double) (volume - minVolume) / (double) (maxVolume - minVolume) * 100.0);
2222 state = new PercentType(BigDecimal.valueOf(volumePct));
2225 case CHANNEL_MAIN_VOLUME_UP_DOWN:
2227 state = new DecimalType(volume);
2230 case CHANNEL_ZONE2_VOLUME:
2231 if (powerZone2 && !fixedVolumeZone2) {
2232 long volumePct = Math
2233 .round((double) (volumeZone2 - minVolume) / (double) (maxVolume - minVolume) * 100.0);
2234 state = new PercentType(BigDecimal.valueOf(volumePct));
2237 case CHANNEL_ZONE2_VOLUME_UP_DOWN:
2238 if (powerZone2 && !fixedVolumeZone2) {
2239 state = new DecimalType(volumeZone2);
2242 case CHANNEL_ZONE3_VOLUME:
2243 if (powerZone3 && !fixedVolumeZone3) {
2244 long volumePct = Math
2245 .round((double) (volumeZone3 - minVolume) / (double) (maxVolume - minVolume) * 100.0);
2246 state = new PercentType(BigDecimal.valueOf(volumePct));
2249 case CHANNEL_ZONE4_VOLUME:
2250 if (powerZone4 && !fixedVolumeZone4) {
2251 long volumePct = Math
2252 .round((double) (volumeZone4 - minVolume) / (double) (maxVolume - minVolume) * 100.0);
2253 state = new PercentType(BigDecimal.valueOf(volumePct));
2257 case CHANNEL_MAIN_MUTE:
2259 state = OnOffType.from(mute);
2262 case CHANNEL_ZONE2_MUTE:
2264 state = OnOffType.from(muteZone2);
2267 case CHANNEL_ZONE3_MUTE:
2269 state = OnOffType.from(muteZone3);
2272 case CHANNEL_ZONE4_MUTE:
2274 state = OnOffType.from(muteZone4);
2278 case CHANNEL_MAIN_BASS:
2280 state = new DecimalType(bass);
2283 case CHANNEL_TREBLE:
2284 case CHANNEL_MAIN_TREBLE:
2286 state = new DecimalType(treble);
2290 if (track > 0 && isPowerOn()) {
2291 state = new DecimalType(track);
2294 case CHANNEL_PLAY_CONTROL:
2296 switch (playStatus) {
2298 state = PlayPauseType.PLAY;
2302 state = PlayPauseType.PAUSE;
2307 case CHANNEL_FREQUENCY:
2308 if (frequency > 0.0 && isPowerOn()) {
2309 state = new DecimalType(frequency);
2313 state = new StringType(frontPanelLine1);
2316 state = new StringType(frontPanelLine2);
2318 case CHANNEL_BRIGHTNESS:
2319 if (isPowerOn() && connector.getModel().hasDimmerControl()) {
2320 long dimmerPct = Math.round((double) (brightness - connector.getModel().getDimmerLevelMin())
2321 / (double) (connector.getModel().getDimmerLevelMax()
2322 - connector.getModel().getDimmerLevelMin())
2324 state = new PercentType(BigDecimal.valueOf(dimmerPct));
2327 case CHANNEL_TCBYPASS:
2329 state = OnOffType.from(tcbypass);
2332 case CHANNEL_BALANCE:
2334 state = new DecimalType(balance);
2337 case CHANNEL_SPEAKER_A:
2339 state = OnOffType.from(speakera);
2342 case CHANNEL_SPEAKER_B:
2344 state = OnOffType.from(speakerb);
2350 updateState(channel, state);
2354 * Inform about the main zone power state
2356 * @return true if main zone power state is known and known as ON
2358 private boolean isPowerOn() {
2359 Boolean power = this.power;
2360 return power != null && power.booleanValue();
2364 * Get the command to be used for main zone POWER ON
2366 * @return the command
2368 private RotelCommand getPowerOnCommand() {
2369 return connector.getModel().hasOtherThanPrimaryCommands() ? RotelCommand.MAIN_ZONE_POWER_ON
2370 : RotelCommand.POWER_ON;
2374 * Get the command to be used for main zone POWER OFF
2376 * @return the command
2378 private RotelCommand getPowerOffCommand() {
2379 return connector.getModel().hasOtherThanPrimaryCommands() ? RotelCommand.MAIN_ZONE_POWER_OFF
2380 : RotelCommand.POWER_OFF;
2384 * Get the command to be used for main zone VOLUME UP
2386 * @return the command
2388 private RotelCommand getVolumeUpCommand() {
2389 return connector.getModel().hasOtherThanPrimaryCommands() ? RotelCommand.MAIN_ZONE_VOLUME_UP
2390 : RotelCommand.VOLUME_UP;
2394 * Get the command to be used for main zone VOLUME DOWN
2396 * @return the command
2398 private RotelCommand getVolumeDownCommand() {
2399 return connector.getModel().hasOtherThanPrimaryCommands() ? RotelCommand.MAIN_ZONE_VOLUME_DOWN
2400 : RotelCommand.VOLUME_DOWN;
2404 * Get the command to be used for main zone MUTE ON
2406 * @return the command
2408 private RotelCommand getMuteOnCommand() {
2409 return connector.getModel().hasOtherThanPrimaryCommands() ? RotelCommand.MAIN_ZONE_MUTE_ON
2410 : RotelCommand.MUTE_ON;
2414 * Get the command to be used for main zone MUTE OFF
2416 * @return the command
2418 private RotelCommand getMuteOffCommand() {
2419 return connector.getModel().hasOtherThanPrimaryCommands() ? RotelCommand.MAIN_ZONE_MUTE_OFF
2420 : RotelCommand.MUTE_OFF;
2424 * Get the command to be used for main zone MUTE TOGGLE
2426 * @return the command
2428 private RotelCommand getMuteToggleCommand() {
2429 return connector.getModel().hasOtherThanPrimaryCommands() ? RotelCommand.MAIN_ZONE_MUTE_TOGGLE
2430 : RotelCommand.MUTE_TOGGLE;