]> git.basschouten.com Git - openhab-addons.git/blob
fe6df3c45050151e127f4335b8c40a2595389a42
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2020 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
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
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.rotel.internal.communication;
14
15 import java.io.InterruptedIOException;
16 import java.nio.charset.StandardCharsets;
17 import java.util.Arrays;
18 import java.util.Map;
19
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;
27
28 /**
29  * Class for simulating the communication with the Rotel device
30  *
31  * @author Laurent Garnier - Initial contribution
32  */
33 @NonNullByDefault
34 public class RotelSimuConnector extends RotelConnector {
35
36     private final Logger logger = LoggerFactory.getLogger(RotelSimuConnector.class);
37
38     private static final int STEP_TONE_LEVEL = 1;
39
40     private Object lock = new Object();
41
42     private byte[] feedbackMsg = new byte[1];
43     private int idxInFeedbackMsg = feedbackMsg.length;
44
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;
57     private boolean mute;
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;
64     private int bass;
65     private int treble;
66     private boolean showTreble;
67     private RotelPlayStatus playStatus = RotelPlayStatus.STOPPED;
68     private int track = 1;
69     private boolean selectingRecord;
70     private int showZone;
71     private int dimmer;
72
73     private int minVolume;
74     private int maxVolume;
75     private int minToneLevel;
76     private int maxToneLevel;
77
78     /**
79      * Constructor
80      *
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
84      */
85     public RotelSimuConnector(RotelModel model, RotelProtocol protocol, Map<RotelSource, String> sourcesLabels,
86             String readerThreadName) {
87         super(model, protocol, sourcesLabels, true, readerThreadName);
88         this.minVolume = 0;
89         this.maxVolume = model.hasVolumeControl() ? model.getVolumeMax() : 0;
90         this.maxToneLevel = model.hasToneControl() ? model.getToneLevelMax() : 0;
91         this.minToneLevel = -this.maxToneLevel;
92     }
93
94     @Override
95     public synchronized void open() throws RotelException {
96         logger.debug("Opening simulated connection");
97         Thread thread = new RotelReaderThread(this, readerThreadName);
98         setReaderThread(thread);
99         thread.start();
100         setConnected(true);
101         logger.debug("Simulated connection opened");
102     }
103
104     @Override
105     public synchronized void close() {
106         logger.debug("Closing simulated connection");
107         super.cleanup();
108         setConnected(false);
109         logger.debug("Simulated connection closed");
110     }
111
112     @Override
113     protected int readInput(byte[] dataBuffer) throws RotelException, InterruptedIOException {
114         synchronized (lock) {
115             int len = feedbackMsg.length - idxInFeedbackMsg;
116             if (len > 0) {
117                 if (len > dataBuffer.length) {
118                     len = dataBuffer.length;
119                 }
120                 System.arraycopy(feedbackMsg, idxInFeedbackMsg, dataBuffer, 0, len);
121                 idxInFeedbackMsg += len;
122                 return len;
123             }
124         }
125         // Give more chance to someone else than the reader thread to get the lock
126         try {
127             Thread.sleep(20);
128         } catch (InterruptedException e) {
129             Thread.currentThread().interrupt();
130         }
131         return 0;
132     }
133
134     @Override
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);
141         }
142     }
143
144     /**
145      * Built the simulated feedback message for a sent command
146      *
147      * @param cmd the sent command
148      * @param value the integer value considered in the sent command for volume, bass or treble adjustment
149      */
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;
158         switch (cmd) {
159             case DISPLAY_REFRESH:
160                 break;
161             case POWER_OFF:
162             case MAIN_ZONE_POWER_OFF:
163                 power = false;
164                 text = buildSourceLine1Response();
165                 textLine1Left = buildSourceLine1LeftResponse();
166                 textLine1Right = buildVolumeLine1RightResponse();
167                 textAscii = buildPowerAsciiResponse();
168                 break;
169             case POWER_ON:
170             case MAIN_ZONE_POWER_ON:
171                 power = true;
172                 text = buildSourceLine1Response();
173                 textLine1Left = buildSourceLine1LeftResponse();
174                 textLine1Right = buildVolumeLine1RightResponse();
175                 textAscii = buildPowerAsciiResponse();
176                 break;
177             case POWER:
178                 textAscii = buildPowerAsciiResponse();
179                 break;
180             case ZONE2_POWER_OFF:
181                 powerZone2 = false;
182                 text = textLine2 = buildZonePowerResponse(getModel().getNbAdditionalZones() > 1 ? "ZONE2" : "ZONE",
183                         powerZone2, sourceZone2);
184                 showZone = 2;
185                 resetZone = false;
186                 break;
187             case ZONE2_POWER_ON:
188                 powerZone2 = true;
189                 text = textLine2 = buildZonePowerResponse(getModel().getNbAdditionalZones() > 1 ? "ZONE2" : "ZONE",
190                         powerZone2, sourceZone2);
191                 showZone = 2;
192                 resetZone = false;
193                 break;
194             case ZONE3_POWER_OFF:
195                 powerZone3 = false;
196                 text = textLine2 = buildZonePowerResponse("ZONE3", powerZone3, sourceZone3);
197                 showZone = 3;
198                 resetZone = false;
199                 break;
200             case ZONE3_POWER_ON:
201                 powerZone3 = true;
202                 text = textLine2 = buildZonePowerResponse("ZONE3", powerZone3, sourceZone3);
203                 showZone = 3;
204                 resetZone = false;
205                 break;
206             case ZONE4_POWER_OFF:
207                 powerZone4 = false;
208                 text = textLine2 = buildZonePowerResponse("ZONE4", powerZone4, sourceZone4);
209                 showZone = 4;
210                 resetZone = false;
211                 break;
212             case ZONE4_POWER_ON:
213                 powerZone4 = true;
214                 text = textLine2 = buildZonePowerResponse("ZONE4", powerZone4, sourceZone4);
215                 showZone = 4;
216                 resetZone = false;
217                 break;
218             case RECORD_FONCTION_SELECT:
219                 if (getModel().getNbAdditionalZones() >= 1 && getModel().getZoneSelectCmd() == cmd) {
220                     showZone++;
221                     if (showZone > getModel().getNbAdditionalZones()) {
222                         showZone = 1;
223                         if (!power) {
224                             showZone++;
225                         }
226                     }
227                 } else {
228                     showZone = 1;
229                 }
230                 if (showZone == 1) {
231                     selectingRecord = power;
232                     showTreble = false;
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);
244                 }
245                 resetZone = false;
246                 break;
247             case ZONE_SELECT:
248                 if (getModel().getNbAdditionalZones() == 0
249                         || (getModel().getNbAdditionalZones() > 1 && getModel().getZoneSelectCmd() == cmd)
250                         || (showZone == 1 && getModel().getZoneSelectCmd() != cmd)) {
251                     accepted = false;
252                 } else {
253                     if (getModel().getZoneSelectCmd() == cmd) {
254                         if (!power && !powerZone2) {
255                             showZone = 2;
256                             powerZone2 = true;
257                         } else if (showZone == 2) {
258                             powerZone2 = !powerZone2;
259                         } else {
260                             showZone = 2;
261                         }
262                     } else {
263                         if (showZone == 2) {
264                             powerZone2 = !powerZone2;
265                         } else if (showZone == 3) {
266                             powerZone3 = !powerZone3;
267                         } else if (showZone == 4) {
268                             powerZone4 = !powerZone4;
269                         }
270                     }
271                     if (showZone == 2) {
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);
278                     }
279                     resetZone = false;
280                 }
281                 break;
282             default:
283                 accepted = false;
284                 break;
285         }
286         if (!accepted && powerZone2) {
287             accepted = true;
288             switch (cmd) {
289                 case ZONE2_VOLUME_UP:
290                     if (volumeZone2 < maxVolume) {
291                         volumeZone2++;
292                     }
293                     text = textLine2 = buildZoneVolumeResponse(getModel().getNbAdditionalZones() > 1 ? "ZONE2" : "ZONE",
294                             muteZone2, volumeZone2);
295                     break;
296                 case ZONE2_VOLUME_DOWN:
297                     if (volumeZone2 > minVolume) {
298                         volumeZone2--;
299                     }
300                     text = textLine2 = buildZoneVolumeResponse(getModel().getNbAdditionalZones() > 1 ? "ZONE2" : "ZONE",
301                             muteZone2, volumeZone2);
302                     break;
303                 case ZONE2_VOLUME_SET:
304                     if (value != null) {
305                         volumeZone2 = value;
306                     }
307                     text = textLine2 = buildZoneVolumeResponse(getModel().getNbAdditionalZones() > 1 ? "ZONE2" : "ZONE",
308                             muteZone2, volumeZone2);
309                     break;
310                 case VOLUME_UP:
311                     if (!getModel().hasZone2Commands() && getModel().getNbAdditionalZones() >= 1 && showZone == 2) {
312                         if (volumeZone2 < maxVolume) {
313                             volumeZone2++;
314                         }
315                         text = textLine2 = buildZoneVolumeResponse(
316                                 getModel().getNbAdditionalZones() > 1 ? "ZONE2" : "ZONE", muteZone2, volumeZone2);
317                         resetZone = false;
318                     } else {
319                         accepted = false;
320                     }
321                     break;
322                 case VOLUME_DOWN:
323                     if (!getModel().hasZone2Commands() && getModel().getNbAdditionalZones() >= 1 && showZone == 2) {
324                         if (volumeZone2 > minVolume) {
325                             volumeZone2--;
326                         }
327                         text = textLine2 = buildZoneVolumeResponse(
328                                 getModel().getNbAdditionalZones() > 1 ? "ZONE2" : "ZONE", muteZone2, volumeZone2);
329                         resetZone = false;
330                     } else {
331                         accepted = false;
332                     }
333                     break;
334                 case VOLUME_SET:
335                     if (!getModel().hasZone2Commands() && getModel().getNbAdditionalZones() >= 1 && showZone == 2) {
336                         if (value != null) {
337                             volumeZone2 = value;
338                         }
339                         text = textLine2 = buildZoneVolumeResponse(
340                                 getModel().getNbAdditionalZones() > 1 ? "ZONE2" : "ZONE", muteZone2, volumeZone2);
341                         resetZone = false;
342                     } else {
343                         accepted = false;
344                     }
345                     break;
346                 case ZONE2_MUTE_TOGGLE:
347                     muteZone2 = !muteZone2;
348                     text = textLine2 = buildZoneVolumeResponse(getModel().getNbAdditionalZones() > 1 ? "ZONE2" : "ZONE",
349                             muteZone2, volumeZone2);
350                     break;
351                 case ZONE2_MUTE_ON:
352                     muteZone2 = true;
353                     text = textLine2 = buildZoneVolumeResponse(getModel().getNbAdditionalZones() > 1 ? "ZONE2" : "ZONE",
354                             muteZone2, volumeZone2);
355                     break;
356                 case ZONE2_MUTE_OFF:
357                     muteZone2 = false;
358                     text = textLine2 = buildZoneVolumeResponse(getModel().getNbAdditionalZones() > 1 ? "ZONE2" : "ZONE",
359                             muteZone2, volumeZone2);
360                     break;
361                 default:
362                     accepted = false;
363                     break;
364             }
365             if (!accepted) {
366                 try {
367                     sourceZone2 = getModel().getZone2SourceFromCommand(cmd);
368                     powerZone2 = true;
369                     text = textLine2 = buildZonePowerResponse(getModel().getNbAdditionalZones() > 1 ? "ZONE2" : "ZONE",
370                             powerZone2, sourceZone2);
371                     muteZone2 = false;
372                     accepted = true;
373                     showZone = 2;
374                     resetZone = false;
375                 } catch (RotelException e) {
376                 }
377             }
378             if (!accepted && !getModel().hasZone2Commands() && getModel().getNbAdditionalZones() >= 1
379                     && showZone == 2) {
380                 try {
381                     sourceZone2 = getModel().getSourceFromCommand(cmd);
382                     powerZone2 = true;
383                     text = textLine2 = buildZonePowerResponse(getModel().getNbAdditionalZones() > 1 ? "ZONE2" : "ZONE",
384                             powerZone2, sourceZone2);
385                     muteZone2 = false;
386                     accepted = true;
387                     resetZone = false;
388                 } catch (RotelException e) {
389                 }
390             }
391         }
392         if (!accepted && powerZone3) {
393             accepted = true;
394             switch (cmd) {
395                 case ZONE3_VOLUME_UP:
396                     if (volumeZone3 < maxVolume) {
397                         volumeZone3++;
398                     }
399                     text = textLine2 = buildZoneVolumeResponse("ZONE3", muteZone3, volumeZone3);
400                     break;
401                 case ZONE3_VOLUME_DOWN:
402                     if (volumeZone3 > minVolume) {
403                         volumeZone3--;
404                     }
405                     text = textLine2 = buildZoneVolumeResponse("ZONE3", muteZone3, volumeZone3);
406                     break;
407                 case ZONE3_VOLUME_SET:
408                     if (value != null) {
409                         volumeZone3 = value;
410                     }
411                     text = textLine2 = buildZoneVolumeResponse("ZONE3", muteZone3, volumeZone3);
412                     break;
413                 case ZONE3_MUTE_TOGGLE:
414                     muteZone3 = !muteZone3;
415                     text = textLine2 = buildZoneVolumeResponse("ZONE3", muteZone3, volumeZone3);
416                     break;
417                 case ZONE3_MUTE_ON:
418                     muteZone3 = true;
419                     text = textLine2 = buildZoneVolumeResponse("ZONE3", muteZone3, volumeZone3);
420                     break;
421                 case ZONE3_MUTE_OFF:
422                     muteZone3 = false;
423                     text = textLine2 = buildZoneVolumeResponse("ZONE3", muteZone3, volumeZone3);
424                     break;
425                 default:
426                     accepted = false;
427                     break;
428             }
429             if (!accepted) {
430                 try {
431                     sourceZone3 = getModel().getZone3SourceFromCommand(cmd);
432                     powerZone3 = true;
433                     text = textLine2 = buildZonePowerResponse("ZONE3", powerZone3, sourceZone3);
434                     muteZone3 = false;
435                     accepted = true;
436                     showZone = 3;
437                     resetZone = false;
438                 } catch (RotelException e) {
439                 }
440             }
441         }
442         if (!accepted && powerZone4) {
443             accepted = true;
444             switch (cmd) {
445                 case ZONE4_VOLUME_UP:
446                     if (volumeZone4 < maxVolume) {
447                         volumeZone4++;
448                     }
449                     text = textLine2 = buildZoneVolumeResponse("ZONE4", muteZone4, volumeZone4);
450                     break;
451                 case ZONE4_VOLUME_DOWN:
452                     if (volumeZone4 > minVolume) {
453                         volumeZone4--;
454                     }
455                     text = textLine2 = buildZoneVolumeResponse("ZONE4", muteZone4, volumeZone4);
456                     break;
457                 case ZONE4_VOLUME_SET:
458                     if (value != null) {
459                         volumeZone4 = value;
460                     }
461                     text = textLine2 = buildZoneVolumeResponse("ZONE4", muteZone4, volumeZone4);
462                     break;
463                 case ZONE4_MUTE_TOGGLE:
464                     muteZone4 = !muteZone4;
465                     text = textLine2 = buildZoneVolumeResponse("ZONE4", muteZone4, volumeZone4);
466                     break;
467                 case ZONE4_MUTE_ON:
468                     muteZone4 = true;
469                     text = textLine2 = buildZoneVolumeResponse("ZONE4", muteZone4, volumeZone4);
470                     break;
471                 case ZONE4_MUTE_OFF:
472                     muteZone4 = false;
473                     text = textLine2 = buildZoneVolumeResponse("ZONE4", muteZone4, volumeZone4);
474                     break;
475                 default:
476                     accepted = false;
477                     break;
478             }
479             if (!accepted) {
480                 try {
481                     sourceZone4 = getModel().getZone4SourceFromCommand(cmd);
482                     powerZone4 = true;
483                     text = textLine2 = buildZonePowerResponse("ZONE4", powerZone4, sourceZone4);
484                     muteZone4 = false;
485                     accepted = true;
486                     showZone = 4;
487                     resetZone = false;
488                 } catch (RotelException e) {
489                 }
490             }
491         }
492         if (!accepted && power) {
493             accepted = true;
494             switch (cmd) {
495                 case UPDATE_AUTO:
496                     textAscii = buildAsciiResponse(
497                             getProtocol() == RotelProtocol.ASCII_V1 ? KEY_DISPLAY_UPDATE : KEY_UPDATE_MODE, "AUTO");
498                     break;
499                 case UPDATE_MANUAL:
500                     textAscii = buildAsciiResponse(
501                             getProtocol() == RotelProtocol.ASCII_V1 ? KEY_DISPLAY_UPDATE : KEY_UPDATE_MODE, "MANUAL");
502                     break;
503                 case VOLUME_GET_MIN:
504                     textAscii = buildAsciiResponse(KEY_VOLUME_MIN, minVolume);
505                     break;
506                 case VOLUME_GET_MAX:
507                     textAscii = buildAsciiResponse(KEY_VOLUME_MAX, maxVolume);
508                     break;
509                 case VOLUME_UP:
510                 case MAIN_ZONE_VOLUME_UP:
511                     if (volume < maxVolume) {
512                         volume++;
513                     }
514                     text = buildVolumeLine1Response();
515                     textLine1Right = buildVolumeLine1RightResponse();
516                     textAscii = buildVolumeAsciiResponse();
517                     break;
518                 case VOLUME_DOWN:
519                 case MAIN_ZONE_VOLUME_DOWN:
520                     if (volume > minVolume) {
521                         volume--;
522                     }
523                     text = buildVolumeLine1Response();
524                     textLine1Right = buildVolumeLine1RightResponse();
525                     textAscii = buildVolumeAsciiResponse();
526                     break;
527                 case VOLUME_SET:
528                     if (value != null) {
529                         volume = value;
530                     }
531                     text = buildVolumeLine1Response();
532                     textLine1Right = buildVolumeLine1RightResponse();
533                     textAscii = buildVolumeAsciiResponse();
534                     break;
535                 case VOLUME_GET:
536                     textAscii = buildVolumeAsciiResponse();
537                     break;
538                 case MUTE_TOGGLE:
539                 case MAIN_ZONE_MUTE_TOGGLE:
540                     mute = !mute;
541                     text = buildSourceLine1Response();
542                     textLine1Right = buildVolumeLine1RightResponse();
543                     textAscii = buildMuteAsciiResponse();
544                     break;
545                 case MUTE_ON:
546                 case MAIN_ZONE_MUTE_ON:
547                     mute = true;
548                     text = buildSourceLine1Response();
549                     textLine1Right = buildVolumeLine1RightResponse();
550                     textAscii = buildMuteAsciiResponse();
551                     break;
552                 case MUTE_OFF:
553                 case MAIN_ZONE_MUTE_OFF:
554                     mute = false;
555                     text = buildSourceLine1Response();
556                     textLine1Right = buildVolumeLine1RightResponse();
557                     textAscii = buildMuteAsciiResponse();
558                     break;
559                 case MUTE:
560                     textAscii = buildMuteAsciiResponse();
561                     break;
562                 case TONE_MAX:
563                     textAscii = buildAsciiResponse(KEY_TONE_MAX, "%02d", maxToneLevel);
564                     break;
565                 case BASS_UP:
566                     if (bass < maxToneLevel) {
567                         bass += STEP_TONE_LEVEL;
568                     }
569                     text = buildBassLine1Response();
570                     textLine1Right = buildBassLine1RightResponse();
571                     textAscii = buildBassAsciiResponse();
572                     break;
573                 case BASS_DOWN:
574                     if (bass > minToneLevel) {
575                         bass -= STEP_TONE_LEVEL;
576                     }
577                     text = buildBassLine1Response();
578                     textLine1Right = buildBassLine1RightResponse();
579                     textAscii = buildBassAsciiResponse();
580                     break;
581                 case BASS_SET:
582                     if (value != null) {
583                         bass = value;
584                     }
585                     text = buildBassLine1Response();
586                     textLine1Right = buildBassLine1RightResponse();
587                     textAscii = buildBassAsciiResponse();
588                     break;
589                 case BASS:
590                     textAscii = buildBassAsciiResponse();
591                     break;
592                 case TREBLE_UP:
593                     if (treble < maxToneLevel) {
594                         treble += STEP_TONE_LEVEL;
595                     }
596                     text = buildTrebleLine1Response();
597                     textLine1Right = buildTrebleLine1RightResponse();
598                     textAscii = buildTrebleAsciiResponse();
599                     break;
600                 case TREBLE_DOWN:
601                     if (treble > minToneLevel) {
602                         treble -= STEP_TONE_LEVEL;
603                     }
604                     text = buildTrebleLine1Response();
605                     textLine1Right = buildTrebleLine1RightResponse();
606                     textAscii = buildTrebleAsciiResponse();
607                     break;
608                 case TREBLE_SET:
609                     if (value != null) {
610                         treble = value;
611                     }
612                     text = buildTrebleLine1Response();
613                     textLine1Right = buildTrebleLine1RightResponse();
614                     textAscii = buildTrebleAsciiResponse();
615                     break;
616                 case TREBLE:
617                     textAscii = buildTrebleAsciiResponse();
618                     break;
619                 case TONE_CONTROL_SELECT:
620                     showTreble = !showTreble;
621                     if (showTreble) {
622                         text = buildTrebleLine1Response();
623                         textLine1Right = buildTrebleLine1RightResponse();
624                     } else {
625                         text = buildBassLine1Response();
626                         textLine1Right = buildBassLine1RightResponse();
627                     }
628                     break;
629                 case PLAY:
630                     playStatus = RotelPlayStatus.PLAYING;
631                     textAscii = buildPlayStatusAsciiResponse();
632                     break;
633                 case STOP:
634                     playStatus = RotelPlayStatus.STOPPED;
635                     textAscii = buildPlayStatusAsciiResponse();
636                     break;
637                 case PAUSE:
638                     switch (playStatus) {
639                         case PLAYING:
640                             playStatus = RotelPlayStatus.PAUSED;
641                             break;
642                         case PAUSED:
643                         case STOPPED:
644                             playStatus = RotelPlayStatus.PLAYING;
645                             break;
646                     }
647                     textAscii = buildPlayStatusAsciiResponse();
648                     break;
649                 case CD_PLAY_STATUS:
650                 case PLAY_STATUS:
651                     textAscii = buildPlayStatusAsciiResponse();
652                     break;
653                 case TRACK_FORWARD:
654                     track++;
655                     textAscii = buildTrackAsciiResponse();
656                     break;
657                 case TRACK_BACKWORD:
658                     if (track > 1) {
659                         track--;
660                     }
661                     textAscii = buildTrackAsciiResponse();
662                     break;
663                 case TRACK:
664                     textAscii = buildTrackAsciiResponse();
665                     break;
666                 case SOURCE_MULTI_INPUT:
667                     multiinput = !multiinput;
668                     text = "MULTI IN " + (multiinput ? "ON" : "OFF");
669                     try {
670                         source = getModel().getSourceFromCommand(cmd);
671                         textLine1Left = buildSourceLine1LeftResponse();
672                         textAscii = buildSourceAsciiResponse();
673                         mute = false;
674                     } catch (RotelException e) {
675                     }
676                     break;
677                 case SOURCE:
678                     textAscii = buildSourceAsciiResponse();
679                     break;
680                 case STEREO:
681                     dsp = RotelDsp.CAT4_NONE;
682                     textLine2 = "STEREO";
683                     textAscii = buildDspAsciiResponse();
684                     break;
685                 case STEREO3:
686                     dsp = RotelDsp.CAT4_STEREO3;
687                     textLine2 = "DOLBY 3 STEREO";
688                     textAscii = buildDspAsciiResponse();
689                     break;
690                 case STEREO5:
691                     dsp = RotelDsp.CAT4_STEREO5;
692                     textLine2 = "5CH STEREO";
693                     textAscii = buildDspAsciiResponse();
694                     break;
695                 case STEREO7:
696                     dsp = RotelDsp.CAT4_STEREO7;
697                     textLine2 = "7CH STEREO";
698                     textAscii = buildDspAsciiResponse();
699                     break;
700                 case STEREO9:
701                     dsp = RotelDsp.CAT5_STEREO9;
702                     textAscii = buildDspAsciiResponse();
703                     break;
704                 case STEREO11:
705                     dsp = RotelDsp.CAT5_STEREO11;
706                     textAscii = buildDspAsciiResponse();
707                     break;
708                 case DSP1:
709                     dsp = RotelDsp.CAT4_DSP1;
710                     textLine2 = "DSP 1";
711                     textAscii = buildDspAsciiResponse();
712                     break;
713                 case DSP2:
714                     dsp = RotelDsp.CAT4_DSP2;
715                     textLine2 = "DSP 2";
716                     textAscii = buildDspAsciiResponse();
717                     break;
718                 case DSP3:
719                     dsp = RotelDsp.CAT4_DSP3;
720                     textLine2 = "DSP 3";
721                     textAscii = buildDspAsciiResponse();
722                     break;
723                 case DSP4:
724                     dsp = RotelDsp.CAT4_DSP4;
725                     textLine2 = "DSP 4";
726                     textAscii = buildDspAsciiResponse();
727                     break;
728                 case PROLOGIC:
729                     dsp = RotelDsp.CAT4_PROLOGIC;
730                     textLine2 = "DOLBY PRO LOGIC";
731                     textAscii = buildDspAsciiResponse();
732                     break;
733                 case PLII_CINEMA:
734                     dsp = RotelDsp.CAT4_PLII_CINEMA;
735                     textLine2 = "DOLBY PL  C";
736                     textAscii = buildDspAsciiResponse();
737                     break;
738                 case PLII_MUSIC:
739                     dsp = RotelDsp.CAT4_PLII_MUSIC;
740                     textLine2 = "DOLBY PL  M";
741                     textAscii = buildDspAsciiResponse();
742                     break;
743                 case PLII_GAME:
744                     dsp = RotelDsp.CAT4_PLII_GAME;
745                     textLine2 = "DOLBY PL  G";
746                     textAscii = buildDspAsciiResponse();
747                     break;
748                 case PLIIZ:
749                     dsp = RotelDsp.CAT4_PLIIZ;
750                     textLine2 = "DOLBY PL z";
751                     textAscii = buildDspAsciiResponse();
752                     break;
753                 case NEO6_MUSIC:
754                     dsp = RotelDsp.CAT4_NEO6_MUSIC;
755                     textLine2 = "DTS Neo:6 M";
756                     textAscii = buildDspAsciiResponse();
757                     break;
758                 case NEO6_CINEMA:
759                     dsp = RotelDsp.CAT4_NEO6_CINEMA;
760                     textLine2 = "DTS Neo:6 C";
761                     textAscii = buildDspAsciiResponse();
762                     break;
763                 case ATMOS:
764                     dsp = RotelDsp.CAT5_ATMOS;
765                     textAscii = buildDspAsciiResponse();
766                     break;
767                 case NEURAL_X:
768                     dsp = RotelDsp.CAT5_NEURAL_X;
769                     textAscii = buildDspAsciiResponse();
770                     break;
771                 case BYPASS:
772                     dsp = RotelDsp.CAT4_BYPASS;
773                     textLine2 = "BYPASS";
774                     textAscii = buildDspAsciiResponse();
775                     break;
776                 case DSP_MODE:
777                     textAscii = buildDspAsciiResponse();
778                     break;
779                 case FREQUENCY:
780                     textAscii = buildAsciiResponse(KEY_FREQ, "44.1");
781                     break;
782                 case DIMMER_LEVEL_SET:
783                     if (value != null) {
784                         dimmer = value;
785                     }
786                     textAscii = buildAsciiResponse(KEY_DIMMER, dimmer);
787                     break;
788                 case DIMMER_LEVEL_GET:
789                     textAscii = buildAsciiResponse(KEY_DIMMER, dimmer);
790                     break;
791                 default:
792                     accepted = false;
793                     break;
794             }
795             if (!accepted) {
796                 try {
797                     source = getModel().getMainZoneSourceFromCommand(cmd);
798                     text = buildSourceLine1Response();
799                     textLine1Left = buildSourceLine1LeftResponse();
800                     textAscii = buildSourceAsciiResponse();
801                     accepted = true;
802                 } catch (RotelException e) {
803                 }
804             }
805             if (!accepted) {
806                 try {
807                     if (selectingRecord && !getModel().hasOtherThanPrimaryCommands()) {
808                         recordSource = getModel().getSourceFromCommand(cmd);
809                     } else {
810                         source = getModel().getSourceFromCommand(cmd);
811                     }
812                     text = buildSourceLine1Response();
813                     textLine1Left = buildSourceLine1LeftResponse();
814                     textAscii = buildSourceAsciiResponse();
815                     mute = false;
816                     accepted = true;
817                 } catch (RotelException e) {
818                 }
819             }
820             if (!accepted) {
821                 try {
822                     recordSource = getModel().getRecordSourceFromCommand(cmd);
823                     text = buildSourceLine1Response();
824                     textLine2 = buildRecordResponse();
825                     accepted = true;
826                 } catch (RotelException e) {
827                 }
828             }
829         }
830
831         if (!accepted) {
832             return;
833         }
834
835         if (cmd != RotelCommand.RECORD_FONCTION_SELECT) {
836             selectingRecord = false;
837         }
838         if (resetZone) {
839             showZone = 0;
840         }
841
842         if (getModel().getRespNbChars() == 42) {
843             while (textLine1Left.length() < 14) {
844                 textLine1Left += " ";
845             }
846             while (textLine1Right.length() < 7) {
847                 textLine1Right += " ";
848             }
849             while (textLine2.length() < 21) {
850                 textLine2 += " ";
851             }
852             text = textLine1Left + textLine1Right + textLine2;
853         }
854
855         if (getProtocol() == RotelProtocol.HEX) {
856             byte[] chars = Arrays.copyOf(text.getBytes(StandardCharsets.US_ASCII), getModel().getRespNbChars());
857             byte[] flags = new byte[getModel().getRespNbFlags()];
858             try {
859                 getModel().setMultiInput(flags, multiinput);
860             } catch (RotelException e) {
861             }
862             try {
863                 getModel().setZone2(flags, powerZone2);
864             } catch (RotelException e) {
865             }
866             try {
867                 getModel().setZone3(flags, powerZone3);
868             } catch (RotelException e) {
869             }
870             try {
871                 getModel().setZone4(flags, powerZone4);
872             } catch (RotelException e) {
873             }
874             int size = 6 + getModel().getRespNbChars() + getModel().getRespNbFlags();
875             byte[] dataBuffer = new byte[size];
876             int idx = 0;
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();
886             } else {
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();
891             }
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;
899             } else {
900                 dataBuffer[idx++] = checksum;
901             }
902             synchronized (lock) {
903                 feedbackMsg = Arrays.copyOf(dataBuffer, idx);
904                 idxInFeedbackMsg = 0;
905             }
906         } else {
907             String command = textAscii + (getProtocol() == RotelProtocol.ASCII_V1 ? "!" : "$");
908             synchronized (lock) {
909                 feedbackMsg = command.getBytes(StandardCharsets.US_ASCII);
910                 idxInFeedbackMsg = 0;
911             }
912         }
913     }
914
915     private String buildAsciiResponse(String key, String value) {
916         return String.format("%s=%s", key, value);
917     }
918
919     private String buildAsciiResponse(String key, int value) {
920         return buildAsciiResponse(key, "%d", value);
921     }
922
923     private String buildAsciiResponse(String key, String format, int value) {
924         return String.format("%s=" + format, key, value);
925     }
926
927     private String buildAsciiResponse(String key, boolean value) {
928         return buildAsciiResponse(key, value ? MSG_VALUE_ON : MSG_VALUE_OFF);
929     }
930
931     private String buildPowerAsciiResponse() {
932         return buildAsciiResponse(KEY_POWER, power ? POWER_ON : STANDBY);
933     }
934
935     private String buildVolumeAsciiResponse() {
936         return buildAsciiResponse(KEY_VOLUME, "%02d", volume);
937     }
938
939     private String buildMuteAsciiResponse() {
940         return buildAsciiResponse(KEY_MUTE, mute);
941     }
942
943     private String buildBassAsciiResponse() {
944         String result;
945         if (bass == 0) {
946             result = buildAsciiResponse(KEY_BASS, "000");
947         } else if (bass > 0) {
948             result = buildAsciiResponse(KEY_BASS, "+%02d", bass);
949         } else {
950             result = buildAsciiResponse(KEY_BASS, "-%02d", -bass);
951         }
952         return result;
953     }
954
955     private String buildTrebleAsciiResponse() {
956         String result;
957         if (treble == 0) {
958             result = buildAsciiResponse(KEY_TREBLE, "000");
959         } else if (treble > 0) {
960             result = buildAsciiResponse(KEY_TREBLE, "+%02d", treble);
961         } else {
962             result = buildAsciiResponse(KEY_TREBLE, "-%02d", -treble);
963         }
964         return result;
965     }
966
967     private String buildPlayStatusAsciiResponse() {
968         String status = "";
969         switch (playStatus) {
970             case PLAYING:
971                 status = PLAY;
972                 break;
973             case PAUSED:
974                 status = PAUSE;
975                 break;
976             case STOPPED:
977                 status = STOP;
978                 break;
979         }
980         return buildAsciiResponse(getProtocol() == RotelProtocol.ASCII_V1 ? KEY1_PLAY_STATUS : KEY2_PLAY_STATUS,
981                 status);
982     }
983
984     private String buildTrackAsciiResponse() {
985         return buildAsciiResponse(KEY_TRACK, "%03d", track);
986     }
987
988     private String buildSourceAsciiResponse() {
989         String str = null;
990         RotelCommand command = source.getCommand();
991         if (command != null) {
992             str = command.getAsciiCommandV2();
993         }
994         return buildAsciiResponse(KEY_SOURCE, (str == null) ? "" : str);
995     }
996
997     private String buildDspAsciiResponse() {
998         return buildAsciiResponse(KEY_DSP_MODE, dsp.getFeedback());
999     }
1000
1001     private String buildSourceLine1Response() {
1002         String text;
1003         if (!power) {
1004             text = "";
1005         } else if (mute) {
1006             text = "MUTE ON";
1007         } else {
1008             text = getSourceLabel(source, false) + " " + getSourceLabel(recordSource, true);
1009         }
1010         return text;
1011     }
1012
1013     private String buildSourceLine1LeftResponse() {
1014         String text;
1015         if (!power) {
1016             text = "";
1017         } else {
1018             text = getSourceLabel(source, false);
1019         }
1020         return text;
1021     }
1022
1023     private String buildRecordResponse() {
1024         String text;
1025         if (!power) {
1026             text = "";
1027         } else {
1028             text = "REC " + getSourceLabel(recordSource, true);
1029         }
1030         return text;
1031     }
1032
1033     private String buildZonePowerResponse(String zone, boolean powerZone, RotelSource sourceZone) {
1034         String state = powerZone ? getSourceLabel(sourceZone, true) : "OFF";
1035         return zone + " " + state;
1036     }
1037
1038     private String buildVolumeLine1Response() {
1039         String text;
1040         if (volume == minVolume) {
1041             text = " VOLUME  MIN ";
1042         } else if (volume == maxVolume) {
1043             text = " VOLUME  MAX ";
1044         } else {
1045             text = String.format(" VOLUME   %02d ", volume);
1046         }
1047         return text;
1048     }
1049
1050     private String buildVolumeLine1RightResponse() {
1051         String text;
1052         if (!power) {
1053             text = "";
1054         } else if (mute) {
1055             text = "MUTE ON";
1056         } else if (volume == minVolume) {
1057             text = "VOL MIN";
1058         } else if (volume == maxVolume) {
1059             text = "VOL MAX";
1060         } else {
1061             text = String.format("VOL  %02d", volume);
1062         }
1063         return text;
1064     }
1065
1066     private String buildZoneVolumeResponse(String zone, boolean muted, int vol) {
1067         String text;
1068         if (muted) {
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";
1074         } else {
1075             text = String.format("%s VOL %02d", zone, vol);
1076         }
1077         return text;
1078     }
1079
1080     private String buildBassLine1Response() {
1081         String text;
1082         if (bass == minToneLevel) {
1083             text = "   BASS  MIN ";
1084         } else if (bass == maxToneLevel) {
1085             text = "   BASS  MAX ";
1086         } else if (bass == 0) {
1087             text = "   BASS    0 ";
1088         } else if (bass > 0) {
1089             text = String.format("   BASS  +%02d ", bass);
1090         } else {
1091             text = String.format("   BASS  -%02d ", -bass);
1092         }
1093         return text;
1094     }
1095
1096     private String buildBassLine1RightResponse() {
1097         String text;
1098         if (bass == minToneLevel) {
1099             text = "LF  MIN";
1100         } else if (bass == maxToneLevel) {
1101             text = "LF  MAX";
1102         } else if (bass == 0) {
1103             text = "LF    0";
1104         } else if (bass > 0) {
1105             text = String.format("LF + %02d", bass);
1106         } else {
1107             text = String.format("LF - %02d", -bass);
1108         }
1109         return text;
1110     }
1111
1112     private String buildTrebleLine1Response() {
1113         String text;
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);
1122         } else {
1123             text = String.format(" TREBLE  -%02d ", -treble);
1124         }
1125         return text;
1126     }
1127
1128     private String buildTrebleLine1RightResponse() {
1129         String text;
1130         if (treble == minToneLevel) {
1131             text = "HF  MIN";
1132         } else if (treble == maxToneLevel) {
1133             text = "HF  MAX";
1134         } else if (treble == 0) {
1135             text = "HF    0";
1136         } else if (treble > 0) {
1137             text = String.format("HF + %02d", treble);
1138         } else {
1139             text = String.format("HF - %02d", -treble);
1140         }
1141         return text;
1142     }
1143
1144     private String getSourceLabel(RotelSource source, boolean considerFollowMain) {
1145         String label;
1146         if (considerFollowMain && source.getName().equals(RotelSource.CAT1_FOLLOW_MAIN.getName())) {
1147             label = "SOURCE";
1148         } else if (sourcesLabels.get(source) != null) {
1149             label = sourcesLabels.get(source);
1150         } else {
1151             label = source.getLabel();
1152         }
1153         return label;
1154     }
1155 }