2 * Copyright (c) 2010-2020 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.communication;
15 import java.io.InterruptedIOException;
16 import java.nio.charset.StandardCharsets;
17 import java.util.Arrays;
20 import org.eclipse.jdt.annotation.NonNullByDefault;
21 import org.eclipse.jdt.annotation.Nullable;
22 import org.openhab.binding.rotel.internal.RotelException;
23 import org.openhab.binding.rotel.internal.RotelModel;
24 import org.openhab.binding.rotel.internal.RotelPlayStatus;
25 import org.slf4j.Logger;
26 import org.slf4j.LoggerFactory;
29 * Class for simulating the communication with the Rotel device
31 * @author Laurent Garnier - Initial contribution
34 public class RotelSimuConnector extends RotelConnector {
36 private final Logger logger = LoggerFactory.getLogger(RotelSimuConnector.class);
38 private static final int STEP_TONE_LEVEL = 1;
40 private Object lock = new Object();
42 private byte[] feedbackMsg = new byte[1];
43 private int idxInFeedbackMsg = feedbackMsg.length;
45 private boolean power;
46 private boolean powerZone2;
47 private boolean powerZone3;
48 private boolean powerZone4;
49 private RotelSource source = RotelSource.CAT0_CD;
50 private RotelSource recordSource = RotelSource.CAT1_CD;
51 private RotelSource sourceZone2 = RotelSource.CAT1_CD;
52 private RotelSource sourceZone3 = RotelSource.CAT1_CD;
53 private RotelSource sourceZone4 = RotelSource.CAT1_CD;
54 private boolean multiinput;
55 private RotelDsp dsp = RotelDsp.CAT4_NONE;
56 private int volume = 50;
58 private int volumeZone2 = 20;
59 private boolean muteZone2;
60 private int volumeZone3 = 30;
61 private boolean muteZone3;
62 private int volumeZone4 = 40;
63 private boolean muteZone4;
66 private boolean showTreble;
67 private RotelPlayStatus playStatus = RotelPlayStatus.STOPPED;
68 private int track = 1;
69 private boolean selectingRecord;
73 private int minVolume;
74 private int maxVolume;
75 private int minToneLevel;
76 private int maxToneLevel;
81 * @param model the projector model in use
82 * @param protocol the protocol to be used
83 * @param readerThreadName the name of thread to be created
85 public RotelSimuConnector(RotelModel model, RotelProtocol protocol, Map<RotelSource, String> sourcesLabels,
86 String readerThreadName) {
87 super(model, protocol, sourcesLabels, true, readerThreadName);
89 this.maxVolume = model.hasVolumeControl() ? model.getVolumeMax() : 0;
90 this.maxToneLevel = model.hasToneControl() ? model.getToneLevelMax() : 0;
91 this.minToneLevel = -this.maxToneLevel;
95 public synchronized void open() throws RotelException {
96 logger.debug("Opening simulated connection");
97 Thread thread = new RotelReaderThread(this, readerThreadName);
98 setReaderThread(thread);
101 logger.debug("Simulated connection opened");
105 public synchronized void close() {
106 logger.debug("Closing simulated connection");
109 logger.debug("Simulated connection closed");
113 protected int readInput(byte[] dataBuffer) throws RotelException, InterruptedIOException {
114 synchronized (lock) {
115 int len = feedbackMsg.length - idxInFeedbackMsg;
117 if (len > dataBuffer.length) {
118 len = dataBuffer.length;
120 System.arraycopy(feedbackMsg, idxInFeedbackMsg, dataBuffer, 0, len);
121 idxInFeedbackMsg += len;
125 // Give more chance to someone else than the reader thread to get the lock
128 } catch (InterruptedException e) {
129 Thread.currentThread().interrupt();
135 public void sendCommand(RotelCommand cmd, @Nullable Integer value) throws RotelException {
136 super.sendCommand(cmd, value);
137 if ((getProtocol() == RotelProtocol.HEX && cmd.getHexType() != 0)
138 || (getProtocol() == RotelProtocol.ASCII_V1 && cmd.getAsciiCommandV1() != null)
139 || (getProtocol() == RotelProtocol.ASCII_V2 && cmd.getAsciiCommandV2() != null)) {
140 buildFeedbackMessage(cmd, value);
145 * Built the simulated feedback message for a sent command
147 * @param cmd the sent command
148 * @param value the integer value considered in the sent command for volume, bass or treble adjustment
150 private void buildFeedbackMessage(RotelCommand cmd, @Nullable Integer value) {
151 String text = buildSourceLine1Response();
152 String textLine1Left = buildSourceLine1LeftResponse();
153 String textLine1Right = buildVolumeLine1RightResponse();
154 String textLine2 = "";
155 String textAscii = "";
156 boolean accepted = true;
157 boolean resetZone = true;
159 case DISPLAY_REFRESH:
162 case MAIN_ZONE_POWER_OFF:
164 text = buildSourceLine1Response();
165 textLine1Left = buildSourceLine1LeftResponse();
166 textLine1Right = buildVolumeLine1RightResponse();
167 textAscii = buildPowerAsciiResponse();
170 case MAIN_ZONE_POWER_ON:
172 text = buildSourceLine1Response();
173 textLine1Left = buildSourceLine1LeftResponse();
174 textLine1Right = buildVolumeLine1RightResponse();
175 textAscii = buildPowerAsciiResponse();
178 textAscii = buildPowerAsciiResponse();
180 case ZONE2_POWER_OFF:
182 text = textLine2 = buildZonePowerResponse(getModel().getNbAdditionalZones() > 1 ? "ZONE2" : "ZONE",
183 powerZone2, sourceZone2);
189 text = textLine2 = buildZonePowerResponse(getModel().getNbAdditionalZones() > 1 ? "ZONE2" : "ZONE",
190 powerZone2, sourceZone2);
194 case ZONE3_POWER_OFF:
196 text = textLine2 = buildZonePowerResponse("ZONE3", powerZone3, sourceZone3);
202 text = textLine2 = buildZonePowerResponse("ZONE3", powerZone3, sourceZone3);
206 case ZONE4_POWER_OFF:
208 text = textLine2 = buildZonePowerResponse("ZONE4", powerZone4, sourceZone4);
214 text = textLine2 = buildZonePowerResponse("ZONE4", powerZone4, sourceZone4);
218 case RECORD_FONCTION_SELECT:
219 if (getModel().getNbAdditionalZones() >= 1 && getModel().getZoneSelectCmd() == cmd) {
221 if (showZone > getModel().getNbAdditionalZones()) {
231 selectingRecord = power;
233 textLine2 = buildRecordResponse();
234 } else if (showZone == 2) {
235 selectingRecord = false;
236 text = textLine2 = buildZonePowerResponse(getModel().getNbAdditionalZones() > 1 ? "ZONE2" : "ZONE",
237 powerZone2, sourceZone2);
238 } else if (showZone == 3) {
239 selectingRecord = false;
240 text = textLine2 = buildZonePowerResponse("ZONE3", powerZone3, sourceZone3);
241 } else if (showZone == 4) {
242 selectingRecord = false;
243 text = textLine2 = buildZonePowerResponse("ZONE4", powerZone4, sourceZone4);
248 if (getModel().getNbAdditionalZones() == 0
249 || (getModel().getNbAdditionalZones() > 1 && getModel().getZoneSelectCmd() == cmd)
250 || (showZone == 1 && getModel().getZoneSelectCmd() != cmd)) {
253 if (getModel().getZoneSelectCmd() == cmd) {
254 if (!power && !powerZone2) {
257 } else if (showZone == 2) {
258 powerZone2 = !powerZone2;
264 powerZone2 = !powerZone2;
265 } else if (showZone == 3) {
266 powerZone3 = !powerZone3;
267 } else if (showZone == 4) {
268 powerZone4 = !powerZone4;
272 text = textLine2 = buildZonePowerResponse(
273 getModel().getNbAdditionalZones() > 1 ? "ZONE2" : "ZONE", powerZone2, sourceZone2);
274 } else if (showZone == 3) {
275 text = textLine2 = buildZonePowerResponse("ZONE3", powerZone3, sourceZone3);
276 } else if (showZone == 4) {
277 text = textLine2 = buildZonePowerResponse("ZONE4", powerZone4, sourceZone4);
286 if (!accepted && powerZone2) {
289 case ZONE2_VOLUME_UP:
290 if (volumeZone2 < maxVolume) {
293 text = textLine2 = buildZoneVolumeResponse(getModel().getNbAdditionalZones() > 1 ? "ZONE2" : "ZONE",
294 muteZone2, volumeZone2);
296 case ZONE2_VOLUME_DOWN:
297 if (volumeZone2 > minVolume) {
300 text = textLine2 = buildZoneVolumeResponse(getModel().getNbAdditionalZones() > 1 ? "ZONE2" : "ZONE",
301 muteZone2, volumeZone2);
303 case ZONE2_VOLUME_SET:
307 text = textLine2 = buildZoneVolumeResponse(getModel().getNbAdditionalZones() > 1 ? "ZONE2" : "ZONE",
308 muteZone2, volumeZone2);
311 if (!getModel().hasZone2Commands() && getModel().getNbAdditionalZones() >= 1 && showZone == 2) {
312 if (volumeZone2 < maxVolume) {
315 text = textLine2 = buildZoneVolumeResponse(
316 getModel().getNbAdditionalZones() > 1 ? "ZONE2" : "ZONE", muteZone2, volumeZone2);
323 if (!getModel().hasZone2Commands() && getModel().getNbAdditionalZones() >= 1 && showZone == 2) {
324 if (volumeZone2 > minVolume) {
327 text = textLine2 = buildZoneVolumeResponse(
328 getModel().getNbAdditionalZones() > 1 ? "ZONE2" : "ZONE", muteZone2, volumeZone2);
335 if (!getModel().hasZone2Commands() && getModel().getNbAdditionalZones() >= 1 && showZone == 2) {
339 text = textLine2 = buildZoneVolumeResponse(
340 getModel().getNbAdditionalZones() > 1 ? "ZONE2" : "ZONE", muteZone2, volumeZone2);
346 case ZONE2_MUTE_TOGGLE:
347 muteZone2 = !muteZone2;
348 text = textLine2 = buildZoneVolumeResponse(getModel().getNbAdditionalZones() > 1 ? "ZONE2" : "ZONE",
349 muteZone2, volumeZone2);
353 text = textLine2 = buildZoneVolumeResponse(getModel().getNbAdditionalZones() > 1 ? "ZONE2" : "ZONE",
354 muteZone2, volumeZone2);
358 text = textLine2 = buildZoneVolumeResponse(getModel().getNbAdditionalZones() > 1 ? "ZONE2" : "ZONE",
359 muteZone2, volumeZone2);
367 sourceZone2 = getModel().getZone2SourceFromCommand(cmd);
369 text = textLine2 = buildZonePowerResponse(getModel().getNbAdditionalZones() > 1 ? "ZONE2" : "ZONE",
370 powerZone2, sourceZone2);
375 } catch (RotelException e) {
378 if (!accepted && !getModel().hasZone2Commands() && getModel().getNbAdditionalZones() >= 1
381 sourceZone2 = getModel().getSourceFromCommand(cmd);
383 text = textLine2 = buildZonePowerResponse(getModel().getNbAdditionalZones() > 1 ? "ZONE2" : "ZONE",
384 powerZone2, sourceZone2);
388 } catch (RotelException e) {
392 if (!accepted && powerZone3) {
395 case ZONE3_VOLUME_UP:
396 if (volumeZone3 < maxVolume) {
399 text = textLine2 = buildZoneVolumeResponse("ZONE3", muteZone3, volumeZone3);
401 case ZONE3_VOLUME_DOWN:
402 if (volumeZone3 > minVolume) {
405 text = textLine2 = buildZoneVolumeResponse("ZONE3", muteZone3, volumeZone3);
407 case ZONE3_VOLUME_SET:
411 text = textLine2 = buildZoneVolumeResponse("ZONE3", muteZone3, volumeZone3);
413 case ZONE3_MUTE_TOGGLE:
414 muteZone3 = !muteZone3;
415 text = textLine2 = buildZoneVolumeResponse("ZONE3", muteZone3, volumeZone3);
419 text = textLine2 = buildZoneVolumeResponse("ZONE3", muteZone3, volumeZone3);
423 text = textLine2 = buildZoneVolumeResponse("ZONE3", muteZone3, volumeZone3);
431 sourceZone3 = getModel().getZone3SourceFromCommand(cmd);
433 text = textLine2 = buildZonePowerResponse("ZONE3", powerZone3, sourceZone3);
438 } catch (RotelException e) {
442 if (!accepted && powerZone4) {
445 case ZONE4_VOLUME_UP:
446 if (volumeZone4 < maxVolume) {
449 text = textLine2 = buildZoneVolumeResponse("ZONE4", muteZone4, volumeZone4);
451 case ZONE4_VOLUME_DOWN:
452 if (volumeZone4 > minVolume) {
455 text = textLine2 = buildZoneVolumeResponse("ZONE4", muteZone4, volumeZone4);
457 case ZONE4_VOLUME_SET:
461 text = textLine2 = buildZoneVolumeResponse("ZONE4", muteZone4, volumeZone4);
463 case ZONE4_MUTE_TOGGLE:
464 muteZone4 = !muteZone4;
465 text = textLine2 = buildZoneVolumeResponse("ZONE4", muteZone4, volumeZone4);
469 text = textLine2 = buildZoneVolumeResponse("ZONE4", muteZone4, volumeZone4);
473 text = textLine2 = buildZoneVolumeResponse("ZONE4", muteZone4, volumeZone4);
481 sourceZone4 = getModel().getZone4SourceFromCommand(cmd);
483 text = textLine2 = buildZonePowerResponse("ZONE4", powerZone4, sourceZone4);
488 } catch (RotelException e) {
492 if (!accepted && power) {
496 textAscii = buildAsciiResponse(
497 getProtocol() == RotelProtocol.ASCII_V1 ? KEY_DISPLAY_UPDATE : KEY_UPDATE_MODE, "AUTO");
500 textAscii = buildAsciiResponse(
501 getProtocol() == RotelProtocol.ASCII_V1 ? KEY_DISPLAY_UPDATE : KEY_UPDATE_MODE, "MANUAL");
504 textAscii = buildAsciiResponse(KEY_VOLUME_MIN, minVolume);
507 textAscii = buildAsciiResponse(KEY_VOLUME_MAX, maxVolume);
510 case MAIN_ZONE_VOLUME_UP:
511 if (volume < maxVolume) {
514 text = buildVolumeLine1Response();
515 textLine1Right = buildVolumeLine1RightResponse();
516 textAscii = buildVolumeAsciiResponse();
519 case MAIN_ZONE_VOLUME_DOWN:
520 if (volume > minVolume) {
523 text = buildVolumeLine1Response();
524 textLine1Right = buildVolumeLine1RightResponse();
525 textAscii = buildVolumeAsciiResponse();
531 text = buildVolumeLine1Response();
532 textLine1Right = buildVolumeLine1RightResponse();
533 textAscii = buildVolumeAsciiResponse();
536 textAscii = buildVolumeAsciiResponse();
539 case MAIN_ZONE_MUTE_TOGGLE:
541 text = buildSourceLine1Response();
542 textLine1Right = buildVolumeLine1RightResponse();
543 textAscii = buildMuteAsciiResponse();
546 case MAIN_ZONE_MUTE_ON:
548 text = buildSourceLine1Response();
549 textLine1Right = buildVolumeLine1RightResponse();
550 textAscii = buildMuteAsciiResponse();
553 case MAIN_ZONE_MUTE_OFF:
555 text = buildSourceLine1Response();
556 textLine1Right = buildVolumeLine1RightResponse();
557 textAscii = buildMuteAsciiResponse();
560 textAscii = buildMuteAsciiResponse();
563 textAscii = buildAsciiResponse(KEY_TONE_MAX, "%02d", maxToneLevel);
566 if (bass < maxToneLevel) {
567 bass += STEP_TONE_LEVEL;
569 text = buildBassLine1Response();
570 textLine1Right = buildBassLine1RightResponse();
571 textAscii = buildBassAsciiResponse();
574 if (bass > minToneLevel) {
575 bass -= STEP_TONE_LEVEL;
577 text = buildBassLine1Response();
578 textLine1Right = buildBassLine1RightResponse();
579 textAscii = buildBassAsciiResponse();
585 text = buildBassLine1Response();
586 textLine1Right = buildBassLine1RightResponse();
587 textAscii = buildBassAsciiResponse();
590 textAscii = buildBassAsciiResponse();
593 if (treble < maxToneLevel) {
594 treble += STEP_TONE_LEVEL;
596 text = buildTrebleLine1Response();
597 textLine1Right = buildTrebleLine1RightResponse();
598 textAscii = buildTrebleAsciiResponse();
601 if (treble > minToneLevel) {
602 treble -= STEP_TONE_LEVEL;
604 text = buildTrebleLine1Response();
605 textLine1Right = buildTrebleLine1RightResponse();
606 textAscii = buildTrebleAsciiResponse();
612 text = buildTrebleLine1Response();
613 textLine1Right = buildTrebleLine1RightResponse();
614 textAscii = buildTrebleAsciiResponse();
617 textAscii = buildTrebleAsciiResponse();
619 case TONE_CONTROL_SELECT:
620 showTreble = !showTreble;
622 text = buildTrebleLine1Response();
623 textLine1Right = buildTrebleLine1RightResponse();
625 text = buildBassLine1Response();
626 textLine1Right = buildBassLine1RightResponse();
630 playStatus = RotelPlayStatus.PLAYING;
631 textAscii = buildPlayStatusAsciiResponse();
634 playStatus = RotelPlayStatus.STOPPED;
635 textAscii = buildPlayStatusAsciiResponse();
638 switch (playStatus) {
640 playStatus = RotelPlayStatus.PAUSED;
644 playStatus = RotelPlayStatus.PLAYING;
647 textAscii = buildPlayStatusAsciiResponse();
651 textAscii = buildPlayStatusAsciiResponse();
655 textAscii = buildTrackAsciiResponse();
661 textAscii = buildTrackAsciiResponse();
664 textAscii = buildTrackAsciiResponse();
666 case SOURCE_MULTI_INPUT:
667 multiinput = !multiinput;
668 text = "MULTI IN " + (multiinput ? "ON" : "OFF");
670 source = getModel().getSourceFromCommand(cmd);
671 textLine1Left = buildSourceLine1LeftResponse();
672 textAscii = buildSourceAsciiResponse();
674 } catch (RotelException e) {
678 textAscii = buildSourceAsciiResponse();
681 dsp = RotelDsp.CAT4_NONE;
682 textLine2 = "STEREO";
683 textAscii = buildDspAsciiResponse();
686 dsp = RotelDsp.CAT4_STEREO3;
687 textLine2 = "DOLBY 3 STEREO";
688 textAscii = buildDspAsciiResponse();
691 dsp = RotelDsp.CAT4_STEREO5;
692 textLine2 = "5CH STEREO";
693 textAscii = buildDspAsciiResponse();
696 dsp = RotelDsp.CAT4_STEREO7;
697 textLine2 = "7CH STEREO";
698 textAscii = buildDspAsciiResponse();
701 dsp = RotelDsp.CAT5_STEREO9;
702 textAscii = buildDspAsciiResponse();
705 dsp = RotelDsp.CAT5_STEREO11;
706 textAscii = buildDspAsciiResponse();
709 dsp = RotelDsp.CAT4_DSP1;
711 textAscii = buildDspAsciiResponse();
714 dsp = RotelDsp.CAT4_DSP2;
716 textAscii = buildDspAsciiResponse();
719 dsp = RotelDsp.CAT4_DSP3;
721 textAscii = buildDspAsciiResponse();
724 dsp = RotelDsp.CAT4_DSP4;
726 textAscii = buildDspAsciiResponse();
729 dsp = RotelDsp.CAT4_PROLOGIC;
730 textLine2 = "DOLBY PRO LOGIC";
731 textAscii = buildDspAsciiResponse();
734 dsp = RotelDsp.CAT4_PLII_CINEMA;
735 textLine2 = "DOLBY PL C";
736 textAscii = buildDspAsciiResponse();
739 dsp = RotelDsp.CAT4_PLII_MUSIC;
740 textLine2 = "DOLBY PL M";
741 textAscii = buildDspAsciiResponse();
744 dsp = RotelDsp.CAT4_PLII_GAME;
745 textLine2 = "DOLBY PL G";
746 textAscii = buildDspAsciiResponse();
749 dsp = RotelDsp.CAT4_PLIIZ;
750 textLine2 = "DOLBY PL z";
751 textAscii = buildDspAsciiResponse();
754 dsp = RotelDsp.CAT4_NEO6_MUSIC;
755 textLine2 = "DTS Neo:6 M";
756 textAscii = buildDspAsciiResponse();
759 dsp = RotelDsp.CAT4_NEO6_CINEMA;
760 textLine2 = "DTS Neo:6 C";
761 textAscii = buildDspAsciiResponse();
764 dsp = RotelDsp.CAT5_ATMOS;
765 textAscii = buildDspAsciiResponse();
768 dsp = RotelDsp.CAT5_NEURAL_X;
769 textAscii = buildDspAsciiResponse();
772 dsp = RotelDsp.CAT4_BYPASS;
773 textLine2 = "BYPASS";
774 textAscii = buildDspAsciiResponse();
777 textAscii = buildDspAsciiResponse();
780 textAscii = buildAsciiResponse(KEY_FREQ, "44.1");
782 case DIMMER_LEVEL_SET:
786 textAscii = buildAsciiResponse(KEY_DIMMER, dimmer);
788 case DIMMER_LEVEL_GET:
789 textAscii = buildAsciiResponse(KEY_DIMMER, dimmer);
797 source = getModel().getMainZoneSourceFromCommand(cmd);
798 text = buildSourceLine1Response();
799 textLine1Left = buildSourceLine1LeftResponse();
800 textAscii = buildSourceAsciiResponse();
802 } catch (RotelException e) {
807 if (selectingRecord && !getModel().hasOtherThanPrimaryCommands()) {
808 recordSource = getModel().getSourceFromCommand(cmd);
810 source = getModel().getSourceFromCommand(cmd);
812 text = buildSourceLine1Response();
813 textLine1Left = buildSourceLine1LeftResponse();
814 textAscii = buildSourceAsciiResponse();
817 } catch (RotelException e) {
822 recordSource = getModel().getRecordSourceFromCommand(cmd);
823 text = buildSourceLine1Response();
824 textLine2 = buildRecordResponse();
826 } catch (RotelException e) {
835 if (cmd != RotelCommand.RECORD_FONCTION_SELECT) {
836 selectingRecord = false;
842 if (getModel().getRespNbChars() == 42) {
843 while (textLine1Left.length() < 14) {
844 textLine1Left += " ";
846 while (textLine1Right.length() < 7) {
847 textLine1Right += " ";
849 while (textLine2.length() < 21) {
852 text = textLine1Left + textLine1Right + textLine2;
855 if (getProtocol() == RotelProtocol.HEX) {
856 byte[] chars = Arrays.copyOf(text.getBytes(StandardCharsets.US_ASCII), getModel().getRespNbChars());
857 byte[] flags = new byte[getModel().getRespNbFlags()];
859 getModel().setMultiInput(flags, multiinput);
860 } catch (RotelException e) {
863 getModel().setZone2(flags, powerZone2);
864 } catch (RotelException e) {
867 getModel().setZone3(flags, powerZone3);
868 } catch (RotelException e) {
871 getModel().setZone4(flags, powerZone4);
872 } catch (RotelException e) {
874 int size = 6 + getModel().getRespNbChars() + getModel().getRespNbFlags();
875 byte[] dataBuffer = new byte[size];
877 dataBuffer[idx++] = START;
878 dataBuffer[idx++] = (byte) (size - 4);
879 dataBuffer[idx++] = getModel().getDeviceId();
880 dataBuffer[idx++] = STANDARD_RESPONSE;
881 if (getModel().isCharsBeforeFlags()) {
882 System.arraycopy(chars, 0, dataBuffer, idx, getModel().getRespNbChars());
883 idx += getModel().getRespNbChars();
884 System.arraycopy(flags, 0, dataBuffer, idx, getModel().getRespNbFlags());
885 idx += getModel().getRespNbFlags();
887 System.arraycopy(flags, 0, dataBuffer, idx, getModel().getRespNbFlags());
888 idx += getModel().getRespNbFlags();
889 System.arraycopy(chars, 0, dataBuffer, idx, getModel().getRespNbChars());
890 idx += getModel().getRespNbChars();
892 byte checksum = computeCheckSum(dataBuffer, idx - 1);
893 if ((checksum & 0x000000FF) == 0x000000FD) {
894 dataBuffer[idx++] = (byte) 0xFD;
895 dataBuffer[idx++] = 0;
896 } else if ((checksum & 0x000000FF) == 0x000000FE) {
897 dataBuffer[idx++] = (byte) 0xFD;
898 dataBuffer[idx++] = 1;
900 dataBuffer[idx++] = checksum;
902 synchronized (lock) {
903 feedbackMsg = Arrays.copyOf(dataBuffer, idx);
904 idxInFeedbackMsg = 0;
907 String command = textAscii + (getProtocol() == RotelProtocol.ASCII_V1 ? "!" : "$");
908 synchronized (lock) {
909 feedbackMsg = command.getBytes(StandardCharsets.US_ASCII);
910 idxInFeedbackMsg = 0;
915 private String buildAsciiResponse(String key, String value) {
916 return String.format("%s=%s", key, value);
919 private String buildAsciiResponse(String key, int value) {
920 return buildAsciiResponse(key, "%d", value);
923 private String buildAsciiResponse(String key, String format, int value) {
924 return String.format("%s=" + format, key, value);
927 private String buildAsciiResponse(String key, boolean value) {
928 return buildAsciiResponse(key, value ? MSG_VALUE_ON : MSG_VALUE_OFF);
931 private String buildPowerAsciiResponse() {
932 return buildAsciiResponse(KEY_POWER, power ? POWER_ON : STANDBY);
935 private String buildVolumeAsciiResponse() {
936 return buildAsciiResponse(KEY_VOLUME, "%02d", volume);
939 private String buildMuteAsciiResponse() {
940 return buildAsciiResponse(KEY_MUTE, mute);
943 private String buildBassAsciiResponse() {
946 result = buildAsciiResponse(KEY_BASS, "000");
947 } else if (bass > 0) {
948 result = buildAsciiResponse(KEY_BASS, "+%02d", bass);
950 result = buildAsciiResponse(KEY_BASS, "-%02d", -bass);
955 private String buildTrebleAsciiResponse() {
958 result = buildAsciiResponse(KEY_TREBLE, "000");
959 } else if (treble > 0) {
960 result = buildAsciiResponse(KEY_TREBLE, "+%02d", treble);
962 result = buildAsciiResponse(KEY_TREBLE, "-%02d", -treble);
967 private String buildPlayStatusAsciiResponse() {
969 switch (playStatus) {
980 return buildAsciiResponse(getProtocol() == RotelProtocol.ASCII_V1 ? KEY1_PLAY_STATUS : KEY2_PLAY_STATUS,
984 private String buildTrackAsciiResponse() {
985 return buildAsciiResponse(KEY_TRACK, "%03d", track);
988 private String buildSourceAsciiResponse() {
990 RotelCommand command = source.getCommand();
991 if (command != null) {
992 str = command.getAsciiCommandV2();
994 return buildAsciiResponse(KEY_SOURCE, (str == null) ? "" : str);
997 private String buildDspAsciiResponse() {
998 return buildAsciiResponse(KEY_DSP_MODE, dsp.getFeedback());
1001 private String buildSourceLine1Response() {
1008 text = getSourceLabel(source, false) + " " + getSourceLabel(recordSource, true);
1013 private String buildSourceLine1LeftResponse() {
1018 text = getSourceLabel(source, false);
1023 private String buildRecordResponse() {
1028 text = "REC " + getSourceLabel(recordSource, true);
1033 private String buildZonePowerResponse(String zone, boolean powerZone, RotelSource sourceZone) {
1034 String state = powerZone ? getSourceLabel(sourceZone, true) : "OFF";
1035 return zone + " " + state;
1038 private String buildVolumeLine1Response() {
1040 if (volume == minVolume) {
1041 text = " VOLUME MIN ";
1042 } else if (volume == maxVolume) {
1043 text = " VOLUME MAX ";
1045 text = String.format(" VOLUME %02d ", volume);
1050 private String buildVolumeLine1RightResponse() {
1056 } else if (volume == minVolume) {
1058 } else if (volume == maxVolume) {
1061 text = String.format("VOL %02d", volume);
1066 private String buildZoneVolumeResponse(String zone, boolean muted, int vol) {
1069 text = zone + " MUTE ON";
1070 } else if (vol == minVolume) {
1071 text = zone + " VOL MIN";
1072 } else if (vol == maxVolume) {
1073 text = zone + " VOL MAX";
1075 text = String.format("%s VOL %02d", zone, vol);
1080 private String buildBassLine1Response() {
1082 if (bass == minToneLevel) {
1083 text = " BASS MIN ";
1084 } else if (bass == maxToneLevel) {
1085 text = " BASS MAX ";
1086 } else if (bass == 0) {
1088 } else if (bass > 0) {
1089 text = String.format(" BASS +%02d ", bass);
1091 text = String.format(" BASS -%02d ", -bass);
1096 private String buildBassLine1RightResponse() {
1098 if (bass == minToneLevel) {
1100 } else if (bass == maxToneLevel) {
1102 } else if (bass == 0) {
1104 } else if (bass > 0) {
1105 text = String.format("LF + %02d", bass);
1107 text = String.format("LF - %02d", -bass);
1112 private String buildTrebleLine1Response() {
1114 if (treble == minToneLevel) {
1115 text = " TREBLE MIN ";
1116 } else if (treble == maxToneLevel) {
1117 text = " TREBLE MAX ";
1118 } else if (treble == 0) {
1119 text = " TREBLE 0 ";
1120 } else if (treble > 0) {
1121 text = String.format(" TREBLE +%02d ", treble);
1123 text = String.format(" TREBLE -%02d ", -treble);
1128 private String buildTrebleLine1RightResponse() {
1130 if (treble == minToneLevel) {
1132 } else if (treble == maxToneLevel) {
1134 } else if (treble == 0) {
1136 } else if (treble > 0) {
1137 text = String.format("HF + %02d", treble);
1139 text = String.format("HF - %02d", -treble);
1144 private String getSourceLabel(RotelSource source, boolean considerFollowMain) {
1146 if (considerFollowMain && source.getName().equals(RotelSource.CAT1_FOLLOW_MAIN.getName())) {
1148 } else if (sourcesLabels.get(source) != null) {
1149 label = sourcesLabels.get(source);
1151 label = source.getLabel();