From c1073cd89fefc912222cd39dd483c2d72b716de2 Mon Sep 17 00:00:00 2001 From: lolodomo Date: Mon, 18 Apr 2022 19:23:52 +0200 Subject: [PATCH] [rotel] Refactor to separate comm handling from protocol handling (#12521) * [rotel] Refactor to separate comm handling from protocol handling Each protocol is now handled in a separate class. Allow to reduce the size of class RotelConnector Signed-off-by: Laurent Garnier * buildCommandMessage now throwing RotelException Comment added when RotelException is catched without any specific handling Signed-off-by: Laurent Garnier --- .../rotel/internal/RotelBindingConstants.java | 78 ++ .../binding/rotel/internal/RotelModel.java | 145 +-- .../internal/communication/RotelCommand.java | 298 +++-- .../communication/RotelConnector.java | 1113 +---------------- .../communication/RotelIpConnector.java | 16 +- .../communication/RotelReaderThread.java | 65 +- .../communication/RotelSerialConnector.java | 22 +- .../communication/RotelSimuConnector.java | 168 +-- .../rotel/internal/handler/RotelHandler.java | 850 ++++++------- .../RotelAbstractProtocolHandler.java | 133 ++ .../RotelMessageEvent.java | 2 +- .../RotelMessageEventListener.java | 2 +- .../RotelProtocol.java | 2 +- .../RotelAbstractAsciiProtocolHandler.java | 195 +++ .../ascii/RotelAsciiV1ProtocolHandler.java | 100 ++ .../ascii/RotelAsciiV2ProtocolHandler.java | 100 ++ .../protocol/hex/RotelHexProtocolHandler.java | 778 ++++++++++++ 17 files changed, 2159 insertions(+), 1908 deletions(-) create mode 100644 bundles/org.openhab.binding.rotel/src/main/java/org/openhab/binding/rotel/internal/protocol/RotelAbstractProtocolHandler.java rename bundles/org.openhab.binding.rotel/src/main/java/org/openhab/binding/rotel/internal/{communication => protocol}/RotelMessageEvent.java (94%) rename bundles/org.openhab.binding.rotel/src/main/java/org/openhab/binding/rotel/internal/{communication => protocol}/RotelMessageEventListener.java (93%) rename bundles/org.openhab.binding.rotel/src/main/java/org/openhab/binding/rotel/internal/{communication => protocol}/RotelProtocol.java (96%) create mode 100644 bundles/org.openhab.binding.rotel/src/main/java/org/openhab/binding/rotel/internal/protocol/ascii/RotelAbstractAsciiProtocolHandler.java create mode 100644 bundles/org.openhab.binding.rotel/src/main/java/org/openhab/binding/rotel/internal/protocol/ascii/RotelAsciiV1ProtocolHandler.java create mode 100644 bundles/org.openhab.binding.rotel/src/main/java/org/openhab/binding/rotel/internal/protocol/ascii/RotelAsciiV2ProtocolHandler.java create mode 100644 bundles/org.openhab.binding.rotel/src/main/java/org/openhab/binding/rotel/internal/protocol/hex/RotelHexProtocolHandler.java diff --git a/bundles/org.openhab.binding.rotel/src/main/java/org/openhab/binding/rotel/internal/RotelBindingConstants.java b/bundles/org.openhab.binding.rotel/src/main/java/org/openhab/binding/rotel/internal/RotelBindingConstants.java index 8096e6227..611228a5c 100644 --- a/bundles/org.openhab.binding.rotel/src/main/java/org/openhab/binding/rotel/internal/RotelBindingConstants.java +++ b/bundles/org.openhab.binding.rotel/src/main/java/org/openhab/binding/rotel/internal/RotelBindingConstants.java @@ -161,4 +161,82 @@ public class RotelBindingConstants { // List of all properties public static final String PROPERTY_PROTOCOL = "protocol"; + + // Message types (HEX protocol) + public static final byte PRIMARY_CMD = (byte) 0x10; + public static final byte MAIN_ZONE_CMD = (byte) 0x14; + public static final byte RECORD_SRC_CMD = (byte) 0x15; + public static final byte ZONE2_CMD = (byte) 0x16; + public static final byte ZONE3_CMD = (byte) 0x17; + public static final byte ZONE4_CMD = (byte) 0x18; + public static final byte VOLUME_CMD = (byte) 0x30; + public static final byte ZONE2_VOLUME_CMD = (byte) 0x32; + public static final byte ZONE3_VOLUME_CMD = (byte) 0x33; + public static final byte ZONE4_VOLUME_CMD = (byte) 0x34; + public static final byte TRIGGER_CMD = (byte) 0x40; + public static final byte STANDARD_RESPONSE = (byte) 0x20; + public static final byte TRIGGER_STATUS = (byte) 0x21; + public static final byte SMART_DISPLAY_DATA_1 = (byte) 0x22; + public static final byte SMART_DISPLAY_DATA_2 = (byte) 0x23; + + // Common (output) keys used by the HEX and ASCII protocols + public static final String KEY_POWER = "power"; + public static final String KEY_VOLUME = "volume"; + public static final String KEY_MUTE = "mute"; + public static final String KEY_BASS = "bass"; + public static final String KEY_TREBLE = "treble"; + public static final String KEY_SOURCE = "source"; + public static final String KEY_DSP_MODE = "dsp_mode"; + public static final String KEY_ERROR = "error"; + // Keys only used by the ASCII protocol + public static final String KEY_UPDATE_MODE = "update_mode"; + public static final String KEY_DISPLAY_UPDATE = "display_update"; + public static final String KEY_VOLUME_MIN = "volume_min"; + public static final String KEY_VOLUME_MAX = "volume_max"; + public static final String KEY_TONE_MAX = "tone_max"; + public static final String KEY1_PLAY_STATUS = "play_status"; + public static final String KEY2_PLAY_STATUS = "status"; + public static final String KEY_TRACK = "track"; + public static final String KEY_DIMMER = "dimmer"; + public static final String KEY_FREQ = "freq"; + public static final String KEY_TONE = "tone"; + public static final String KEY_TCBYPASS = "bypass"; + public static final String KEY_BALANCE = "balance"; + public static final String KEY_SPEAKER = "speaker"; + // Output keys only used by the HEX protocol + public static final String KEY_LINE1 = "line1"; + public static final String KEY_LINE2 = "line2"; + public static final String KEY_RECORD = "record"; + public static final String KEY_RECORD_SEL = "record_sel"; + public static final String KEY_ZONE = "zone"; + public static final String KEY_POWER_ZONE2 = "power_zone2"; + public static final String KEY_POWER_ZONE3 = "power_zone3"; + public static final String KEY_POWER_ZONE4 = "power_zone4"; + public static final String KEY_SOURCE_ZONE2 = "source_zone2"; + public static final String KEY_SOURCE_ZONE3 = "source_zone3"; + public static final String KEY_SOURCE_ZONE4 = "source_zone4"; + public static final String KEY_VOLUME_ZONE2 = "volume_zone2"; + public static final String KEY_VOLUME_ZONE3 = "volume_zone3"; + public static final String KEY_VOLUME_ZONE4 = "volume_zone4"; + public static final String KEY_MUTE_ZONE2 = "mute_zone2"; + public static final String KEY_MUTE_ZONE3 = "mute_zone3"; + public static final String KEY_MUTE_ZONE4 = "mute_zone4"; + + // Specific values for keys + public static final String MSG_VALUE_OFF = "off"; + public static final String MSG_VALUE_ON = "on"; + public static final String POWER_ON = "on"; + public static final String STANDBY = "standby"; + public static final String POWER_OFF_DELAYED = "off_delayed"; + public static final String MSG_VALUE_SPEAKER_A = "a"; + public static final String MSG_VALUE_SPEAKER_B = "b"; + public static final String MSG_VALUE_SPEAKER_AB = "a_b"; + public static final String MSG_VALUE_MIN = "min"; + public static final String MSG_VALUE_MAX = "max"; + public static final String MSG_VALUE_FIX = "fix"; + public static final String AUTO = "auto"; + public static final String MANUAL = "manual"; + public static final String PLAY = "play"; + public static final String PAUSE = "pause"; + public static final String STOP = "stop"; } diff --git a/bundles/org.openhab.binding.rotel/src/main/java/org/openhab/binding/rotel/internal/RotelModel.java b/bundles/org.openhab.binding.rotel/src/main/java/org/openhab/binding/rotel/internal/RotelModel.java index 23c5f794d..6eb1c5305 100644 --- a/bundles/org.openhab.binding.rotel/src/main/java/org/openhab/binding/rotel/internal/RotelModel.java +++ b/bundles/org.openhab.binding.rotel/src/main/java/org/openhab/binding/rotel/internal/RotelModel.java @@ -12,13 +12,15 @@ */ package org.openhab.binding.rotel.internal; +import static org.openhab.binding.rotel.internal.communication.RotelCommand.*; +import static org.openhab.binding.rotel.internal.protocol.ascii.RotelAbstractAsciiProtocolHandler.*; + import java.util.ArrayList; import java.util.List; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.rotel.internal.communication.RotelCommand; -import org.openhab.binding.rotel.internal.communication.RotelConnector; import org.openhab.binding.rotel.internal.communication.RotelDsp; import org.openhab.binding.rotel.internal.communication.RotelFlagsMapping; import org.openhab.binding.rotel.internal.communication.RotelSource; @@ -32,78 +34,64 @@ import org.openhab.core.types.StateOption; @NonNullByDefault public enum RotelModel { - RSP1066("RSP-1066", 19200, 3, 1, false, 90, false, 12, false, RotelCommand.ZONE_SELECT, 1, (byte) 0xC2, 13, 8, true, + RSP1066("RSP-1066", 19200, 3, 1, false, 90, false, 12, false, ZONE_SELECT, 1, (byte) 0xC2, 13, 8, true, RotelFlagsMapping.MAPPING1), - RSP1068("RSP-1068", 19200, 1, 1, true, 96, true, 6, false, RotelCommand.RECORD_FONCTION_SELECT, 2, (byte) 0xA1, 42, - 5, true, RotelFlagsMapping.MAPPING2), - RSP1069("RSP-1069", 38400, 1, 3, true, 96, true, 6, false, RotelCommand.RECORD_FONCTION_SELECT, 2, (byte) 0xA2, 42, - 5, true, RotelFlagsMapping.MAPPING5), - RSP1098("RSP-1098", 19200, 1, 1, true, 96, true, 6, false, RotelCommand.ZONE_SELECT, 2, (byte) 0xA0, 13, 8, true, - RotelFlagsMapping.MAPPING1), - RSP1570("RSP-1570", 115200, 1, 3, true, 96, true, 6, false, RotelCommand.RECORD_FONCTION_SELECT, 3, (byte) 0xA3, 42, - 5, true, RotelFlagsMapping.MAPPING5), - RSP1572("RSP-1572", 115200, 2, 3, true, 96, true, null, false, RotelCommand.RECORD_FONCTION_SELECT, 4, (byte) 0xA5, - 42, 5, true, RotelFlagsMapping.MAPPING5), - RSX1055("RSX-1055", 19200, 3, 1, false, 90, false, 12, false, RotelCommand.ZONE_SELECT, 1, (byte) 0xC3, 13, 8, true, - RotelFlagsMapping.MAPPING1), - RSX1056("RSX-1056", 19200, 1, 1, true, 96, true, 12, false, RotelCommand.ZONE_SELECT, 2, (byte) 0xC5, 13, 8, true, - RotelFlagsMapping.MAPPING1), - RSX1057("RSX-1057", 19200, 1, 1, true, 96, true, 6, false, RotelCommand.RECORD_FONCTION_SELECT, 2, (byte) 0xC7, 13, - 8, true, RotelFlagsMapping.MAPPING1), - RSX1058("RSX-1058", 38400, 1, 3, true, 96, true, 6, false, RotelCommand.RECORD_FONCTION_SELECT, 2, (byte) 0xC8, 13, - 8, true, RotelFlagsMapping.MAPPING4), - RSX1065("RSX-1065", 19200, 3, 1, false, 96, false, 12, false, RotelCommand.ZONE_SELECT, 1, (byte) 0xC1, 42, 5, true, + RSP1068("RSP-1068", 19200, 1, 1, true, 96, true, 6, false, RECORD_FONCTION_SELECT, 2, (byte) 0xA1, 42, 5, true, RotelFlagsMapping.MAPPING2), - RSX1067("RSX-1067", 19200, 1, 1, true, 96, true, 6, false, RotelCommand.RECORD_FONCTION_SELECT, 2, (byte) 0xC4, 42, - 5, true, RotelFlagsMapping.MAPPING2), - RSX1550("RSX-1550", 115200, 1, 3, true, 96, true, 6, false, RotelCommand.RECORD_FONCTION_SELECT, 3, (byte) 0xC9, 13, - 8, true, RotelFlagsMapping.MAPPING3), - RSX1560("RSX-1560", 115200, 1, 3, true, 96, true, 6, false, RotelCommand.RECORD_FONCTION_SELECT, 3, (byte) 0xCA, 42, - 5, true, RotelFlagsMapping.MAPPING5), - RSX1562("RSX-1562", 115200, 2, 3, true, 96, true, null, false, RotelCommand.RECORD_FONCTION_SELECT, 4, (byte) 0xCC, - 42, 5, true, RotelFlagsMapping.MAPPING5), - A11("A11", 115200, 4, 96, true, 10, 15, false, -1, false, true, true, 6, 0, RotelConnector.NO_SPECIAL_CHARACTERS), - A12("A12", 115200, 5, 96, true, 10, 15, false, -1, true, true, true, 6, 0, RotelConnector.NO_SPECIAL_CHARACTERS), - A14("A14", 115200, 5, 96, true, 10, 15, false, -1, true, true, true, 6, 0, RotelConnector.NO_SPECIAL_CHARACTERS), - CD11("CD11", 57600, 0, null, false, null, true, -1, false, true, 6, 0, RotelConnector.NO_SPECIAL_CHARACTERS), - CD14("CD14", 57600, 0, null, false, null, true, -1, false, true, 6, 0, RotelConnector.NO_SPECIAL_CHARACTERS), - RA11("RA-11", 115200, 6, 96, true, 10, 15, true, -1, true, false, false, 6, 0, RotelConnector.SPECIAL_CHARACTERS), - RA12("RA-12", 115200, 6, 96, true, 10, 15, true, -1, true, false, false, 6, 0, RotelConnector.SPECIAL_CHARACTERS), - RA1570("RA-1570", 115200, 7, 96, true, 10, 15, true, -1, true, true, false, 6, 0, - RotelConnector.SPECIAL_CHARACTERS), - RA1572("RA-1572", 115200, 8, 96, true, 10, 15, false, -1, true, true, true, 6, 0, - RotelConnector.SPECIAL_CHARACTERS), - RA1592("RA-1592", 115200, 9, 96, true, 10, 15, false, -1, true, true, true, 6, 0, - RotelConnector.SPECIAL_CHARACTERS), - RAP1580("RAP-1580", 115200, 11, 96, true, null, false, 5, false, false, -10, 10, - RotelConnector.NO_SPECIAL_CHARACTERS), - RC1570("RC-1570", 115200, 7, 96, true, 10, 15, true, -1, true, false, false, 6, 0, - RotelConnector.SPECIAL_CHARACTERS), - RC1572("RC-1572", 115200, 8, 96, true, 10, 15, false, -1, true, false, true, 6, 0, - RotelConnector.SPECIAL_CHARACTERS), - RC1590("RC-1590", 115200, 9, 96, true, 10, 15, false, -1, true, false, true, 6, 0, - RotelConnector.SPECIAL_CHARACTERS), - RCD1570("RCD-1570", 115200, 0, null, false, null, true, -1, false, true, 6, 0, RotelConnector.SPECIAL_CHARACTERS), - RCD1572("RCD-1572", 57600, 0, null, false, null, true, -1, false, true, 6, 0, - RotelConnector.SPECIAL_CHARACTERS_RCD1572), - RCX1500("RCX-1500", 115200, 17, 86, true, null, true, -1, false, false, null, null, - RotelConnector.SPECIAL_CHARACTERS), - RDD1580("RDD-1580", 115200, 15, null, false, null, true, -1, true, false, null, null, - RotelConnector.NO_SPECIAL_CHARACTERS), - RDG1520("RDG-1520", 115200, 16, null, false, null, true, -1, false, false, null, null, - RotelConnector.SPECIAL_CHARACTERS), - RSP1576("RSP-1576", 115200, 10, 96, true, null, false, 5, false, false, -10, 10, - RotelConnector.NO_SPECIAL_CHARACTERS), - RSP1582("RSP-1582", 115200, 11, 96, true, null, false, 6, false, false, -10, 10, - RotelConnector.NO_SPECIAL_CHARACTERS), - RT11("RT-11", 115200, 12, null, false, null, false, -1, false, true, 6, 0, RotelConnector.NO_SPECIAL_CHARACTERS), - RT1570("RT-1570", 115200, 14, null, false, null, false, -1, false, true, 6, 0, - RotelConnector.NO_SPECIAL_CHARACTERS), - T11("T11", 115200, 12, null, false, null, false, -1, false, true, 6, 0, RotelConnector.NO_SPECIAL_CHARACTERS), - T14("T14", 115200, 13, null, false, null, false, -1, false, true, 6, 0, RotelConnector.NO_SPECIAL_CHARACTERS), - P5("P5", 115200, 20, 96, true, 10, 10, false, -1, true, false, true, 4, 0, RotelConnector.NO_SPECIAL_CHARACTERS), - X3("X3", 115200, 18, 96, true, 10, 10, false, -1, true, false, true, 4, 0, RotelConnector.NO_SPECIAL_CHARACTERS), - X5("X5", 115200, 19, 96, true, 10, 10, false, -1, true, false, true, 4, 0, RotelConnector.NO_SPECIAL_CHARACTERS); + RSP1069("RSP-1069", 38400, 1, 3, true, 96, true, 6, false, RECORD_FONCTION_SELECT, 2, (byte) 0xA2, 42, 5, true, + RotelFlagsMapping.MAPPING5), + RSP1098("RSP-1098", 19200, 1, 1, true, 96, true, 6, false, ZONE_SELECT, 2, (byte) 0xA0, 13, 8, true, + RotelFlagsMapping.MAPPING1), + RSP1570("RSP-1570", 115200, 1, 3, true, 96, true, 6, false, RECORD_FONCTION_SELECT, 3, (byte) 0xA3, 42, 5, true, + RotelFlagsMapping.MAPPING5), + RSP1572("RSP-1572", 115200, 2, 3, true, 96, true, null, false, RECORD_FONCTION_SELECT, 4, (byte) 0xA5, 42, 5, true, + RotelFlagsMapping.MAPPING5), + RSX1055("RSX-1055", 19200, 3, 1, false, 90, false, 12, false, ZONE_SELECT, 1, (byte) 0xC3, 13, 8, true, + RotelFlagsMapping.MAPPING1), + RSX1056("RSX-1056", 19200, 1, 1, true, 96, true, 12, false, ZONE_SELECT, 2, (byte) 0xC5, 13, 8, true, + RotelFlagsMapping.MAPPING1), + RSX1057("RSX-1057", 19200, 1, 1, true, 96, true, 6, false, RECORD_FONCTION_SELECT, 2, (byte) 0xC7, 13, 8, true, + RotelFlagsMapping.MAPPING1), + RSX1058("RSX-1058", 38400, 1, 3, true, 96, true, 6, false, RECORD_FONCTION_SELECT, 2, (byte) 0xC8, 13, 8, true, + RotelFlagsMapping.MAPPING4), + RSX1065("RSX-1065", 19200, 3, 1, false, 96, false, 12, false, ZONE_SELECT, 1, (byte) 0xC1, 42, 5, true, + RotelFlagsMapping.MAPPING2), + RSX1067("RSX-1067", 19200, 1, 1, true, 96, true, 6, false, RECORD_FONCTION_SELECT, 2, (byte) 0xC4, 42, 5, true, + RotelFlagsMapping.MAPPING2), + RSX1550("RSX-1550", 115200, 1, 3, true, 96, true, 6, false, RECORD_FONCTION_SELECT, 3, (byte) 0xC9, 13, 8, true, + RotelFlagsMapping.MAPPING3), + RSX1560("RSX-1560", 115200, 1, 3, true, 96, true, 6, false, RECORD_FONCTION_SELECT, 3, (byte) 0xCA, 42, 5, true, + RotelFlagsMapping.MAPPING5), + RSX1562("RSX-1562", 115200, 2, 3, true, 96, true, null, false, RECORD_FONCTION_SELECT, 4, (byte) 0xCC, 42, 5, true, + RotelFlagsMapping.MAPPING5), + A11("A11", 115200, 4, 96, true, 10, 15, false, -1, false, true, true, 6, 0, NO_SPECIAL_CHARACTERS), + A12("A12", 115200, 5, 96, true, 10, 15, false, -1, true, true, true, 6, 0, NO_SPECIAL_CHARACTERS), + A14("A14", 115200, 5, 96, true, 10, 15, false, -1, true, true, true, 6, 0, NO_SPECIAL_CHARACTERS), + CD11("CD11", 57600, 0, null, false, null, true, -1, false, true, 6, 0, NO_SPECIAL_CHARACTERS), + CD14("CD14", 57600, 0, null, false, null, true, -1, false, true, 6, 0, NO_SPECIAL_CHARACTERS), + RA11("RA-11", 115200, 6, 96, true, 10, 15, true, -1, true, false, false, 6, 0, SPECIAL_CHARACTERS), + RA12("RA-12", 115200, 6, 96, true, 10, 15, true, -1, true, false, false, 6, 0, SPECIAL_CHARACTERS), + RA1570("RA-1570", 115200, 7, 96, true, 10, 15, true, -1, true, true, false, 6, 0, SPECIAL_CHARACTERS), + RA1572("RA-1572", 115200, 8, 96, true, 10, 15, false, -1, true, true, true, 6, 0, SPECIAL_CHARACTERS), + RA1592("RA-1592", 115200, 9, 96, true, 10, 15, false, -1, true, true, true, 6, 0, SPECIAL_CHARACTERS), + RAP1580("RAP-1580", 115200, 11, 96, true, null, false, 5, false, false, -10, 10, NO_SPECIAL_CHARACTERS), + RC1570("RC-1570", 115200, 7, 96, true, 10, 15, true, -1, true, false, false, 6, 0, SPECIAL_CHARACTERS), + RC1572("RC-1572", 115200, 8, 96, true, 10, 15, false, -1, true, false, true, 6, 0, SPECIAL_CHARACTERS), + RC1590("RC-1590", 115200, 9, 96, true, 10, 15, false, -1, true, false, true, 6, 0, SPECIAL_CHARACTERS), + RCD1570("RCD-1570", 115200, 0, null, false, null, true, -1, false, true, 6, 0, SPECIAL_CHARACTERS), + RCD1572("RCD-1572", 57600, 0, null, false, null, true, -1, false, true, 6, 0, SPECIAL_CHARACTERS_RCD1572), + RCX1500("RCX-1500", 115200, 17, 86, true, null, true, -1, false, false, null, null, SPECIAL_CHARACTERS), + RDD1580("RDD-1580", 115200, 15, null, false, null, true, -1, true, false, null, null, NO_SPECIAL_CHARACTERS), + RDG1520("RDG-1520", 115200, 16, null, false, null, true, -1, false, false, null, null, SPECIAL_CHARACTERS), + RSP1576("RSP-1576", 115200, 10, 96, true, null, false, 5, false, false, -10, 10, NO_SPECIAL_CHARACTERS), + RSP1582("RSP-1582", 115200, 11, 96, true, null, false, 6, false, false, -10, 10, NO_SPECIAL_CHARACTERS), + RT11("RT-11", 115200, 12, null, false, null, false, -1, false, true, 6, 0, NO_SPECIAL_CHARACTERS), + RT1570("RT-1570", 115200, 14, null, false, null, false, -1, false, true, 6, 0, NO_SPECIAL_CHARACTERS), + T11("T11", 115200, 12, null, false, null, false, -1, false, true, 6, 0, NO_SPECIAL_CHARACTERS), + T14("T14", 115200, 13, null, false, null, false, -1, false, true, 6, 0, NO_SPECIAL_CHARACTERS), + P5("P5", 115200, 20, 96, true, 10, 10, false, -1, true, false, true, 4, 0, NO_SPECIAL_CHARACTERS), + X3("X3", 115200, 18, 96, true, 10, 10, false, -1, true, false, true, 4, 0, NO_SPECIAL_CHARACTERS), + X5("X5", 115200, 19, 96, true, 10, 10, false, -1, true, false, true, 4, 0, NO_SPECIAL_CHARACTERS); private String name; private int baudRate; @@ -154,10 +142,9 @@ public enum RotelModel { @Nullable Integer volumeMax, boolean directVolume, @Nullable Integer toneLevelMax, boolean playControl, @Nullable RotelCommand zoneSelectCmd, int dspCategory, byte deviceId, int respNbChars, int respNbFlags, boolean charsBeforeFlags, RotelFlagsMapping flagsMapping) { - this(name, baudRate, RotelCommand.DISPLAY_REFRESH, sourceCategory, nbAdditionalZones, additionalCommands, - volumeMax, directVolume, toneLevelMax, null, playControl, zoneSelectCmd, dspCategory, false, false, - false, null, null, deviceId, respNbChars, respNbFlags, charsBeforeFlags, flagsMapping, - RotelConnector.NO_SPECIAL_CHARACTERS); + this(name, baudRate, DISPLAY_REFRESH, sourceCategory, nbAdditionalZones, additionalCommands, volumeMax, + directVolume, toneLevelMax, null, playControl, zoneSelectCmd, dspCategory, false, false, false, null, + null, deviceId, respNbChars, respNbFlags, charsBeforeFlags, flagsMapping, NO_SPECIAL_CHARACTERS); } /** @@ -181,8 +168,8 @@ public enum RotelModel { @Nullable Integer toneLevelMax, boolean playControl, int dspCategory, boolean getFrequencyAvailable, boolean getDimmerLevelAvailable, @Nullable Integer diummerLevelMin, @Nullable Integer diummerLevelMax, byte[][] specialCharacters) { - this(name, baudRate, RotelCommand.POWER, sourceCategory, 0, false, volumeMax, directVolume, toneLevelMax, null, - playControl, null, dspCategory, getFrequencyAvailable, false, getDimmerLevelAvailable, diummerLevelMin, + this(name, baudRate, POWER, sourceCategory, 0, false, volumeMax, directVolume, toneLevelMax, null, playControl, + null, dspCategory, getFrequencyAvailable, false, getDimmerLevelAvailable, diummerLevelMin, diummerLevelMax, (byte) 0, 0, 0, false, RotelFlagsMapping.NO_MAPPING, specialCharacters); } @@ -209,8 +196,8 @@ public enum RotelModel { @Nullable Integer toneLevelMax, @Nullable Integer balanceLevelMax, boolean playControl, int dspCategory, boolean getFrequencyAvailable, boolean getSpeakerGroupsAvailable, boolean getDimmerLevelAvailable, @Nullable Integer diummerLevelMin, @Nullable Integer diummerLevelMax, byte[][] specialCharacters) { - this(name, baudRate, RotelCommand.POWER, sourceCategory, 0, false, volumeMax, directVolume, toneLevelMax, - balanceLevelMax, playControl, null, dspCategory, getFrequencyAvailable, getSpeakerGroupsAvailable, + this(name, baudRate, POWER, sourceCategory, 0, false, volumeMax, directVolume, toneLevelMax, balanceLevelMax, + playControl, null, dspCategory, getFrequencyAvailable, getSpeakerGroupsAvailable, getDimmerLevelAvailable, diummerLevelMin, diummerLevelMax, (byte) 0, 0, 0, false, RotelFlagsMapping.NO_MAPPING, specialCharacters); } diff --git a/bundles/org.openhab.binding.rotel/src/main/java/org/openhab/binding/rotel/internal/communication/RotelCommand.java b/bundles/org.openhab.binding.rotel/src/main/java/org/openhab/binding/rotel/internal/communication/RotelCommand.java index 2f59fa642..d2d249737 100644 --- a/bundles/org.openhab.binding.rotel/src/main/java/org/openhab/binding/rotel/internal/communication/RotelCommand.java +++ b/bundles/org.openhab.binding.rotel/src/main/java/org/openhab/binding/rotel/internal/communication/RotelCommand.java @@ -12,6 +12,8 @@ */ package org.openhab.binding.rotel.internal.communication; +import static org.openhab.binding.rotel.internal.RotelBindingConstants.*; + import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.rotel.internal.RotelException; @@ -24,71 +26,71 @@ import org.openhab.binding.rotel.internal.RotelException; @NonNullByDefault public enum RotelCommand { - POWER_TOGGLE("Power Toggle", RotelConnector.PRIMARY_CMD, (byte) 0x0A, "power_toggle", "power_toggle"), - POWER_OFF("Power Off", RotelConnector.PRIMARY_CMD, (byte) 0x4A, "power_off", "power_off"), - POWER_ON("Power On", RotelConnector.PRIMARY_CMD, (byte) 0x4B, "power_on", "power_on"), + POWER_TOGGLE("Power Toggle", PRIMARY_CMD, (byte) 0x0A, "power_toggle", "power_toggle"), + POWER_OFF("Power Off", PRIMARY_CMD, (byte) 0x4A, "power_off", "power_off"), + POWER_ON("Power On", PRIMARY_CMD, (byte) 0x4B, "power_on", "power_on"), POWER("Request current power status", "get_current_power", "power?"), - ZONE_SELECT("Zone Select", RotelConnector.PRIMARY_CMD, (byte) 0x23), - MAIN_ZONE_POWER_TOGGLE("Main Zone Power Toggle", RotelConnector.MAIN_ZONE_CMD, (byte) 0x0A), - MAIN_ZONE_POWER_OFF("Main Zone Power Off", RotelConnector.MAIN_ZONE_CMD, (byte) 0x4A), - MAIN_ZONE_POWER_ON("Main Zone Power On", RotelConnector.MAIN_ZONE_CMD, (byte) 0x4B), - ZONE2_POWER_TOGGLE("Zone 2 Power Toggle", RotelConnector.ZONE2_CMD, (byte) 0x0A), - ZONE2_POWER_OFF("Zone 2 Power Off", RotelConnector.ZONE2_CMD, (byte) 0x4A), - ZONE2_POWER_ON("Zone 2 Power On", RotelConnector.ZONE2_CMD, (byte) 0x4B), - ZONE3_POWER_TOGGLE("Zone 3 Power Toggle", RotelConnector.ZONE3_CMD, (byte) 0x0A), - ZONE3_POWER_OFF("Zone 3 Power Off", RotelConnector.ZONE3_CMD, (byte) 0x4A), - ZONE3_POWER_ON("Zone 3 Power On", RotelConnector.ZONE3_CMD, (byte) 0x4B), - ZONE4_POWER_TOGGLE("Zone 4 Power Toggle", RotelConnector.ZONE4_CMD, (byte) 0x0A), - ZONE4_POWER_OFF("Zone 4 Power Off", RotelConnector.ZONE4_CMD, (byte) 0x4A), - ZONE4_POWER_ON("Zone 4 Power On", RotelConnector.ZONE4_CMD, (byte) 0x4B), - VOLUME_UP("Volume Up", RotelConnector.PRIMARY_CMD, (byte) 0x0B, "volume_up", "vol_up"), - VOLUME_DOWN("Volume Down", RotelConnector.PRIMARY_CMD, (byte) 0x0C, "volume_down", "vol_dwn"), - VOLUME_SET("Set Volume to level", RotelConnector.VOLUME_CMD, (byte) 0, "volume_", "vol_"), + ZONE_SELECT("Zone Select", PRIMARY_CMD, (byte) 0x23), + MAIN_ZONE_POWER_TOGGLE("Main Zone Power Toggle", MAIN_ZONE_CMD, (byte) 0x0A), + MAIN_ZONE_POWER_OFF("Main Zone Power Off", MAIN_ZONE_CMD, (byte) 0x4A), + MAIN_ZONE_POWER_ON("Main Zone Power On", MAIN_ZONE_CMD, (byte) 0x4B), + ZONE2_POWER_TOGGLE("Zone 2 Power Toggle", ZONE2_CMD, (byte) 0x0A), + ZONE2_POWER_OFF("Zone 2 Power Off", ZONE2_CMD, (byte) 0x4A), + ZONE2_POWER_ON("Zone 2 Power On", ZONE2_CMD, (byte) 0x4B), + ZONE3_POWER_TOGGLE("Zone 3 Power Toggle", ZONE3_CMD, (byte) 0x0A), + ZONE3_POWER_OFF("Zone 3 Power Off", ZONE3_CMD, (byte) 0x4A), + ZONE3_POWER_ON("Zone 3 Power On", ZONE3_CMD, (byte) 0x4B), + ZONE4_POWER_TOGGLE("Zone 4 Power Toggle", ZONE4_CMD, (byte) 0x0A), + ZONE4_POWER_OFF("Zone 4 Power Off", ZONE4_CMD, (byte) 0x4A), + ZONE4_POWER_ON("Zone 4 Power On", ZONE4_CMD, (byte) 0x4B), + VOLUME_UP("Volume Up", PRIMARY_CMD, (byte) 0x0B, "volume_up", "vol_up"), + VOLUME_DOWN("Volume Down", PRIMARY_CMD, (byte) 0x0C, "volume_down", "vol_dwn"), + VOLUME_SET("Set Volume to level", VOLUME_CMD, (byte) 0, "volume_", "vol_"), VOLUME_GET("Request current volume level", "get_volume", "volume?"), VOLUME_GET_MIN("Request Min volume level", "get_volume_min", null), VOLUME_GET_MAX("Request Max volume level", "get_volume_max", null), - MUTE_TOGGLE("Mute Toggle", RotelConnector.PRIMARY_CMD, (byte) 0x1E, "mute", "mute"), + MUTE_TOGGLE("Mute Toggle", PRIMARY_CMD, (byte) 0x1E, "mute", "mute"), MUTE_ON("Mute On", "mute_on", "mute_on"), MUTE_OFF("Mute Off", "mute_off", "mute_off"), MUTE("Request current mute status", "get_mute_status", "mute?"), - MAIN_ZONE_VOLUME_UP("Main Zone Volume Up", RotelConnector.MAIN_ZONE_CMD, (byte) 0), - MAIN_ZONE_VOLUME_DOWN("Main Zone Volume Down", RotelConnector.MAIN_ZONE_CMD, (byte) 1), - MAIN_ZONE_MUTE_TOGGLE("Main Zone Mute Toggle", RotelConnector.MAIN_ZONE_CMD, (byte) 0x1E), - MAIN_ZONE_MUTE_ON("Main Zone Mute On", RotelConnector.MAIN_ZONE_CMD, (byte) 0x6C), - MAIN_ZONE_MUTE_OFF("Main Zone Mute Off", RotelConnector.MAIN_ZONE_CMD, (byte) 0x6D), - ZONE2_VOLUME_UP("Zone 2 Volume Up", RotelConnector.ZONE2_CMD, (byte) 0), - ZONE2_VOLUME_DOWN("Zone 2 Volume Down", RotelConnector.ZONE2_CMD, (byte) 1), - ZONE2_VOLUME_SET("Set Zone 2 Volume to level", RotelConnector.ZONE2_VOLUME_CMD, (byte) 0), - ZONE2_MUTE_TOGGLE("Zone 2 Mute Toggle", RotelConnector.ZONE2_CMD, (byte) 0x1E), - ZONE2_MUTE_ON("Zone 2 Mute On", RotelConnector.ZONE2_CMD, (byte) 0x6C), - ZONE2_MUTE_OFF("Zone 2 Mute Off", RotelConnector.ZONE2_CMD, (byte) 0x6D), - ZONE3_VOLUME_UP("Zone 3 Volume Up", RotelConnector.ZONE3_CMD, (byte) 0), - ZONE3_VOLUME_DOWN("Zone 3 Volume Down", RotelConnector.ZONE3_CMD, (byte) 1), - ZONE3_VOLUME_SET("Set Zone 3 Volume to level", RotelConnector.ZONE3_VOLUME_CMD, (byte) 0), - ZONE3_MUTE_TOGGLE("Zone 3 Mute Toggle", RotelConnector.ZONE3_CMD, (byte) 0x1E), - ZONE3_MUTE_ON("Zone 3 Mute On", RotelConnector.ZONE3_CMD, (byte) 0x6C), - ZONE3_MUTE_OFF("Zone 3 Mute Off", RotelConnector.ZONE3_CMD, (byte) 0x6D), - ZONE4_VOLUME_UP("Zone 4 Volume Up", RotelConnector.ZONE4_CMD, (byte) 0), - ZONE4_VOLUME_DOWN("Zone 4 Volume Down", RotelConnector.ZONE4_CMD, (byte) 1), - ZONE4_VOLUME_SET("Set Zone 4 Volume to level", RotelConnector.ZONE4_VOLUME_CMD, (byte) 0), - ZONE4_MUTE_TOGGLE("Zone 4 Mute Toggle", RotelConnector.ZONE4_CMD, (byte) 0x1E), - ZONE4_MUTE_ON("Zone 4 Mute On", RotelConnector.ZONE4_CMD, (byte) 0x6C), - ZONE4_MUTE_OFF("Zone 4 Mute Off", RotelConnector.ZONE4_CMD, (byte) 0x6D), - SOURCE_CD("Source CD", RotelConnector.PRIMARY_CMD, (byte) 0x02, "cd", "cd"), - SOURCE_TUNER("Source Tuner", RotelConnector.PRIMARY_CMD, (byte) 0x03, "tuner", "tuner"), - SOURCE_TAPE("Source Tape", RotelConnector.PRIMARY_CMD, (byte) 0x04, "tape", "tape"), - SOURCE_VIDEO1("Source Video 1", RotelConnector.PRIMARY_CMD, (byte) 0x05, "video1", "video1"), - SOURCE_VIDEO2("Source Video 2", RotelConnector.PRIMARY_CMD, (byte) 0x06, "video2", "video2"), - SOURCE_VIDEO3("Source Video 3", RotelConnector.PRIMARY_CMD, (byte) 0x07, "video3", "video3"), - SOURCE_VIDEO4("Source Video 4", RotelConnector.PRIMARY_CMD, (byte) 0x08, "video4", "video4"), - SOURCE_VIDEO5("Source Video 5", RotelConnector.PRIMARY_CMD, (byte) 0x09, "video5", "video5"), - SOURCE_VIDEO6("Source Video 6", RotelConnector.PRIMARY_CMD, (byte) 0x94, "video6", "video6"), + MAIN_ZONE_VOLUME_UP("Main Zone Volume Up", MAIN_ZONE_CMD, (byte) 0), + MAIN_ZONE_VOLUME_DOWN("Main Zone Volume Down", MAIN_ZONE_CMD, (byte) 1), + MAIN_ZONE_MUTE_TOGGLE("Main Zone Mute Toggle", MAIN_ZONE_CMD, (byte) 0x1E), + MAIN_ZONE_MUTE_ON("Main Zone Mute On", MAIN_ZONE_CMD, (byte) 0x6C), + MAIN_ZONE_MUTE_OFF("Main Zone Mute Off", MAIN_ZONE_CMD, (byte) 0x6D), + ZONE2_VOLUME_UP("Zone 2 Volume Up", ZONE2_CMD, (byte) 0), + ZONE2_VOLUME_DOWN("Zone 2 Volume Down", ZONE2_CMD, (byte) 1), + ZONE2_VOLUME_SET("Set Zone 2 Volume to level", ZONE2_VOLUME_CMD, (byte) 0), + ZONE2_MUTE_TOGGLE("Zone 2 Mute Toggle", ZONE2_CMD, (byte) 0x1E), + ZONE2_MUTE_ON("Zone 2 Mute On", ZONE2_CMD, (byte) 0x6C), + ZONE2_MUTE_OFF("Zone 2 Mute Off", ZONE2_CMD, (byte) 0x6D), + ZONE3_VOLUME_UP("Zone 3 Volume Up", ZONE3_CMD, (byte) 0), + ZONE3_VOLUME_DOWN("Zone 3 Volume Down", ZONE3_CMD, (byte) 1), + ZONE3_VOLUME_SET("Set Zone 3 Volume to level", ZONE3_VOLUME_CMD, (byte) 0), + ZONE3_MUTE_TOGGLE("Zone 3 Mute Toggle", ZONE3_CMD, (byte) 0x1E), + ZONE3_MUTE_ON("Zone 3 Mute On", ZONE3_CMD, (byte) 0x6C), + ZONE3_MUTE_OFF("Zone 3 Mute Off", ZONE3_CMD, (byte) 0x6D), + ZONE4_VOLUME_UP("Zone 4 Volume Up", ZONE4_CMD, (byte) 0), + ZONE4_VOLUME_DOWN("Zone 4 Volume Down", ZONE4_CMD, (byte) 1), + ZONE4_VOLUME_SET("Set Zone 4 Volume to level", ZONE4_VOLUME_CMD, (byte) 0), + ZONE4_MUTE_TOGGLE("Zone 4 Mute Toggle", ZONE4_CMD, (byte) 0x1E), + ZONE4_MUTE_ON("Zone 4 Mute On", ZONE4_CMD, (byte) 0x6C), + ZONE4_MUTE_OFF("Zone 4 Mute Off", ZONE4_CMD, (byte) 0x6D), + SOURCE_CD("Source CD", PRIMARY_CMD, (byte) 0x02, "cd", "cd"), + SOURCE_TUNER("Source Tuner", PRIMARY_CMD, (byte) 0x03, "tuner", "tuner"), + SOURCE_TAPE("Source Tape", PRIMARY_CMD, (byte) 0x04, "tape", "tape"), + SOURCE_VIDEO1("Source Video 1", PRIMARY_CMD, (byte) 0x05, "video1", "video1"), + SOURCE_VIDEO2("Source Video 2", PRIMARY_CMD, (byte) 0x06, "video2", "video2"), + SOURCE_VIDEO3("Source Video 3", PRIMARY_CMD, (byte) 0x07, "video3", "video3"), + SOURCE_VIDEO4("Source Video 4", PRIMARY_CMD, (byte) 0x08, "video4", "video4"), + SOURCE_VIDEO5("Source Video 5", PRIMARY_CMD, (byte) 0x09, "video5", "video5"), + SOURCE_VIDEO6("Source Video 6", PRIMARY_CMD, (byte) 0x94, "video6", "video6"), SOURCE_VIDEO7("Source Video 7", "video7", "video7"), SOURCE_VIDEO8("Source Video 8", "video8", "video8"), - SOURCE_PHONO("Source Phono", RotelConnector.PRIMARY_CMD, (byte) 0x35, "phono", "phono"), - SOURCE_USB("Source Front USB", RotelConnector.PRIMARY_CMD, (byte) 0x8E, "usb", "usb"), + SOURCE_PHONO("Source Phono", PRIMARY_CMD, (byte) 0x35, "phono", "phono"), + SOURCE_USB("Source Front USB", PRIMARY_CMD, (byte) 0x8E, "usb", "usb"), SOURCE_PCUSB("Source PC USB", "pc_usb", "pcusb"), - SOURCE_MULTI_INPUT("Source Multi Input", RotelConnector.PRIMARY_CMD, (byte) 0x15, "multi_input", "multi_input"), + SOURCE_MULTI_INPUT("Source Multi Input", PRIMARY_CMD, (byte) 0x15, "multi_input", "multi_input"), SOURCE_AUX("Source Aux", "aux", "aux"), SOURCE_AUX1("Source Aux 1", "aux1", "aux1"), SOURCE_AUX2("Source Aux 2", "aux2", "aux2"), @@ -111,126 +113,114 @@ public enum RotelCommand { SOURCE_IRADIO("Source iRadio", "iradio", "iradio"), SOURCE_NETWORK("Source Network", "network", "network"), SOURCE("Request current source", "get_current_source", "source?"), - MAIN_ZONE_SOURCE_CD("Main Zone Source CD", RotelConnector.MAIN_ZONE_CMD, (byte) 0x02, "main_zone_cd", - "main_zone_cd"), - MAIN_ZONE_SOURCE_TUNER("Main Zone Source Tuner", RotelConnector.MAIN_ZONE_CMD, (byte) 0x03, "main_zone_tuner", - "main_zone_tuner"), - MAIN_ZONE_SOURCE_TAPE("Main Zone Source Tape", RotelConnector.MAIN_ZONE_CMD, (byte) 0x04, "main_zone_tape", - "main_zone_tape"), - MAIN_ZONE_SOURCE_VIDEO1("Main Zone Source Video 1", RotelConnector.MAIN_ZONE_CMD, (byte) 0x05, "main_zone_video1", + MAIN_ZONE_SOURCE_CD("Main Zone Source CD", MAIN_ZONE_CMD, (byte) 0x02, "main_zone_cd", "main_zone_cd"), + MAIN_ZONE_SOURCE_TUNER("Main Zone Source Tuner", MAIN_ZONE_CMD, (byte) 0x03, "main_zone_tuner", "main_zone_tuner"), + MAIN_ZONE_SOURCE_TAPE("Main Zone Source Tape", MAIN_ZONE_CMD, (byte) 0x04, "main_zone_tape", "main_zone_tape"), + MAIN_ZONE_SOURCE_VIDEO1("Main Zone Source Video 1", MAIN_ZONE_CMD, (byte) 0x05, "main_zone_video1", "main_zone_video1"), - MAIN_ZONE_SOURCE_VIDEO2("Main Zone Source Video 2", RotelConnector.MAIN_ZONE_CMD, (byte) 0x06, "main_zone_video2", + MAIN_ZONE_SOURCE_VIDEO2("Main Zone Source Video 2", MAIN_ZONE_CMD, (byte) 0x06, "main_zone_video2", "main_zone_video2"), - MAIN_ZONE_SOURCE_VIDEO3("Main Zone Source Video 3", RotelConnector.MAIN_ZONE_CMD, (byte) 0x07, "main_zone_video3", + MAIN_ZONE_SOURCE_VIDEO3("Main Zone Source Video 3", MAIN_ZONE_CMD, (byte) 0x07, "main_zone_video3", "main_zone_video3"), - MAIN_ZONE_SOURCE_VIDEO4("Main Zone Source Video 4", RotelConnector.MAIN_ZONE_CMD, (byte) 0x08, "main_zone_video4", + MAIN_ZONE_SOURCE_VIDEO4("Main Zone Source Video 4", MAIN_ZONE_CMD, (byte) 0x08, "main_zone_video4", "main_zone_video4"), - MAIN_ZONE_SOURCE_VIDEO5("Main Zone Source Video 5", RotelConnector.MAIN_ZONE_CMD, (byte) 0x09, "main_zone_video5", + MAIN_ZONE_SOURCE_VIDEO5("Main Zone Source Video 5", MAIN_ZONE_CMD, (byte) 0x09, "main_zone_video5", "main_zone_video5"), - MAIN_ZONE_SOURCE_VIDEO6("Main Zone Source Video 6", RotelConnector.MAIN_ZONE_CMD, (byte) 0x94, "main_zone_video6", + MAIN_ZONE_SOURCE_VIDEO6("Main Zone Source Video 6", MAIN_ZONE_CMD, (byte) 0x94, "main_zone_video6", "main_zone_video6"), - MAIN_ZONE_SOURCE_USB("Main Zone Source Front USB", RotelConnector.MAIN_ZONE_CMD, (byte) 0x8E, "main_zone_usb", - "main_zone_usb"), - MAIN_ZONE_SOURCE_MULTI_INPUT("Main Zone Source Multi Input", RotelConnector.MAIN_ZONE_CMD, (byte) 0x15, - "main_zone_multi_input", "main_zone_multi_input"), - RECORD_SOURCE_CD("Record Source CD", RotelConnector.RECORD_SRC_CMD, (byte) 0x02, "record_cd", "record_cd"), - RECORD_SOURCE_TUNER("Record Source Tuner", RotelConnector.RECORD_SRC_CMD, (byte) 0x03, "record_tuner", - "record_tuner"), - RECORD_SOURCE_TAPE("Record Source Tape", RotelConnector.RECORD_SRC_CMD, (byte) 0x04, "record_tape", "record_tape"), - RECORD_SOURCE_VIDEO1("Record Source Video 1", RotelConnector.RECORD_SRC_CMD, (byte) 0x05, "record_video1", - "record_video1"), - RECORD_SOURCE_VIDEO2("Record Source Video 2", RotelConnector.RECORD_SRC_CMD, (byte) 0x06, "record_video2", - "record_video2"), - RECORD_SOURCE_VIDEO3("Record Source Video 3", RotelConnector.RECORD_SRC_CMD, (byte) 0x07, "record_video3", - "record_video3"), - RECORD_SOURCE_VIDEO4("Record Source Video 4", RotelConnector.RECORD_SRC_CMD, (byte) 0x08, "record_video4", - "record_video4"), - RECORD_SOURCE_VIDEO5("Record Source Video 5", RotelConnector.RECORD_SRC_CMD, (byte) 0x09, "record_video5", - "record_video5"), - RECORD_SOURCE_VIDEO6("Record Source Video 6", RotelConnector.RECORD_SRC_CMD, (byte) 0x94, "record_video6", - "record_video6"), - RECORD_SOURCE_USB("Record Source Front USB", RotelConnector.RECORD_SRC_CMD, (byte) 0x8E, "record_usb", - "record_usb"), - RECORD_SOURCE_MAIN("Record Follow Main Zone Source", RotelConnector.RECORD_SRC_CMD, (byte) 0x6B, - "record_follow_main", "record_follow_main"), - ZONE2_SOURCE_CD("Zone 2 Source CD", RotelConnector.ZONE2_CMD, (byte) 0x02, "zone2_cd", "zone2_cd"), - ZONE2_SOURCE_TUNER("Zone 2 Source Tuner", RotelConnector.ZONE2_CMD, (byte) 0x03, "zone2_tuner", "zone2_tuner"), - ZONE2_SOURCE_TAPE("Zone 2 Source Tape", RotelConnector.ZONE2_CMD, (byte) 0x04, "zone2_tape", "zone2_tape"), - ZONE2_SOURCE_VIDEO1("Zone 2 Source Video 1", RotelConnector.ZONE2_CMD, (byte) 0x05, "zone2_video1", "zone2_video1"), - ZONE2_SOURCE_VIDEO2("Zone 2 Source Video 2", RotelConnector.ZONE2_CMD, (byte) 0x06, "zone2_video2", "zone2_video2"), - ZONE2_SOURCE_VIDEO3("Zone 2 Source Video 3", RotelConnector.ZONE2_CMD, (byte) 0x07, "zone2_video3", "zone2_video3"), - ZONE2_SOURCE_VIDEO4("Zone 2 Source Video 4", RotelConnector.ZONE2_CMD, (byte) 0x08, "zone2_video4", "zone2_video4"), - ZONE2_SOURCE_VIDEO5("Zone 2 Source Video 5", RotelConnector.ZONE2_CMD, (byte) 0x09, "zone2_video5", "zone2_video5"), - ZONE2_SOURCE_VIDEO6("Zone 2 Source Video 6", RotelConnector.ZONE2_CMD, (byte) 0x94, "zone2_video6", "zone2_video6"), - ZONE2_SOURCE_USB("Zone 2 Source Front USB", RotelConnector.ZONE2_CMD, (byte) 0x8E, "zone2_usb", "zone2_usb"), - ZONE2_SOURCE_MAIN("Zone 2 Follow Main Zone Source", RotelConnector.ZONE2_CMD, (byte) 0x6B, "zone2_follow_main", + MAIN_ZONE_SOURCE_USB("Main Zone Source Front USB", MAIN_ZONE_CMD, (byte) 0x8E, "main_zone_usb", "main_zone_usb"), + MAIN_ZONE_SOURCE_MULTI_INPUT("Main Zone Source Multi Input", MAIN_ZONE_CMD, (byte) 0x15, "main_zone_multi_input", + "main_zone_multi_input"), + RECORD_SOURCE_CD("Record Source CD", RECORD_SRC_CMD, (byte) 0x02, "record_cd", "record_cd"), + RECORD_SOURCE_TUNER("Record Source Tuner", RECORD_SRC_CMD, (byte) 0x03, "record_tuner", "record_tuner"), + RECORD_SOURCE_TAPE("Record Source Tape", RECORD_SRC_CMD, (byte) 0x04, "record_tape", "record_tape"), + RECORD_SOURCE_VIDEO1("Record Source Video 1", RECORD_SRC_CMD, (byte) 0x05, "record_video1", "record_video1"), + RECORD_SOURCE_VIDEO2("Record Source Video 2", RECORD_SRC_CMD, (byte) 0x06, "record_video2", "record_video2"), + RECORD_SOURCE_VIDEO3("Record Source Video 3", RECORD_SRC_CMD, (byte) 0x07, "record_video3", "record_video3"), + RECORD_SOURCE_VIDEO4("Record Source Video 4", RECORD_SRC_CMD, (byte) 0x08, "record_video4", "record_video4"), + RECORD_SOURCE_VIDEO5("Record Source Video 5", RECORD_SRC_CMD, (byte) 0x09, "record_video5", "record_video5"), + RECORD_SOURCE_VIDEO6("Record Source Video 6", RECORD_SRC_CMD, (byte) 0x94, "record_video6", "record_video6"), + RECORD_SOURCE_USB("Record Source Front USB", RECORD_SRC_CMD, (byte) 0x8E, "record_usb", "record_usb"), + RECORD_SOURCE_MAIN("Record Follow Main Zone Source", RECORD_SRC_CMD, (byte) 0x6B, "record_follow_main", + "record_follow_main"), + ZONE2_SOURCE_CD("Zone 2 Source CD", ZONE2_CMD, (byte) 0x02, "zone2_cd", "zone2_cd"), + ZONE2_SOURCE_TUNER("Zone 2 Source Tuner", ZONE2_CMD, (byte) 0x03, "zone2_tuner", "zone2_tuner"), + ZONE2_SOURCE_TAPE("Zone 2 Source Tape", ZONE2_CMD, (byte) 0x04, "zone2_tape", "zone2_tape"), + ZONE2_SOURCE_VIDEO1("Zone 2 Source Video 1", ZONE2_CMD, (byte) 0x05, "zone2_video1", "zone2_video1"), + ZONE2_SOURCE_VIDEO2("Zone 2 Source Video 2", ZONE2_CMD, (byte) 0x06, "zone2_video2", "zone2_video2"), + ZONE2_SOURCE_VIDEO3("Zone 2 Source Video 3", ZONE2_CMD, (byte) 0x07, "zone2_video3", "zone2_video3"), + ZONE2_SOURCE_VIDEO4("Zone 2 Source Video 4", ZONE2_CMD, (byte) 0x08, "zone2_video4", "zone2_video4"), + ZONE2_SOURCE_VIDEO5("Zone 2 Source Video 5", ZONE2_CMD, (byte) 0x09, "zone2_video5", "zone2_video5"), + ZONE2_SOURCE_VIDEO6("Zone 2 Source Video 6", ZONE2_CMD, (byte) 0x94, "zone2_video6", "zone2_video6"), + ZONE2_SOURCE_USB("Zone 2 Source Front USB", ZONE2_CMD, (byte) 0x8E, "zone2_usb", "zone2_usb"), + ZONE2_SOURCE_MAIN("Zone 2 Follow Main Zone Source", ZONE2_CMD, (byte) 0x6B, "zone2_follow_main", "zone2_follow_main"), - ZONE3_SOURCE_CD("Zone 3 Source CD", RotelConnector.ZONE3_CMD, (byte) 0x02, "zone3_cd", "zone3_cd"), - ZONE3_SOURCE_TUNER("Zone 3 Source Tuner", RotelConnector.ZONE3_CMD, (byte) 0x03, "zone3_tuner", "zone3_tuner"), - ZONE3_SOURCE_TAPE("Zone 3 Source Tape", RotelConnector.ZONE3_CMD, (byte) 0x04, "zone3_tape", "zone3_tape"), - ZONE3_SOURCE_VIDEO1("Zone 3 Source Video 1", RotelConnector.ZONE3_CMD, (byte) 0x05, "zone3_video1", "zone3_video1"), - ZONE3_SOURCE_VIDEO2("Zone 3 Source Video 2", RotelConnector.ZONE3_CMD, (byte) 0x06, "zone3_video2", "zone3_video2"), - ZONE3_SOURCE_VIDEO3("Zone 3 Source Video 3", RotelConnector.ZONE3_CMD, (byte) 0x07, "zone3_video3", "zone3_video3"), - ZONE3_SOURCE_VIDEO4("Zone 3 Source Video 4", RotelConnector.ZONE3_CMD, (byte) 0x08, "zone3_video4", "zone3_video4"), - ZONE3_SOURCE_VIDEO5("Zone 3 Source Video 5", RotelConnector.ZONE3_CMD, (byte) 0x09, "zone3_video5", "zone3_video5"), - ZONE3_SOURCE_VIDEO6("Zone 3 Source Video 6", RotelConnector.ZONE3_CMD, (byte) 0x94, "zone3_video6", "zone3_video6"), - ZONE3_SOURCE_USB("Zone 3 Source Front USB", RotelConnector.ZONE3_CMD, (byte) 0x8E, "zone3_usb", "zone3_usb"), - ZONE3_SOURCE_MAIN("Zone 3 Follow Main Zone Source", RotelConnector.ZONE3_CMD, (byte) 0x6B, "zone3_follow_main", + ZONE3_SOURCE_CD("Zone 3 Source CD", ZONE3_CMD, (byte) 0x02, "zone3_cd", "zone3_cd"), + ZONE3_SOURCE_TUNER("Zone 3 Source Tuner", ZONE3_CMD, (byte) 0x03, "zone3_tuner", "zone3_tuner"), + ZONE3_SOURCE_TAPE("Zone 3 Source Tape", ZONE3_CMD, (byte) 0x04, "zone3_tape", "zone3_tape"), + ZONE3_SOURCE_VIDEO1("Zone 3 Source Video 1", ZONE3_CMD, (byte) 0x05, "zone3_video1", "zone3_video1"), + ZONE3_SOURCE_VIDEO2("Zone 3 Source Video 2", ZONE3_CMD, (byte) 0x06, "zone3_video2", "zone3_video2"), + ZONE3_SOURCE_VIDEO3("Zone 3 Source Video 3", ZONE3_CMD, (byte) 0x07, "zone3_video3", "zone3_video3"), + ZONE3_SOURCE_VIDEO4("Zone 3 Source Video 4", ZONE3_CMD, (byte) 0x08, "zone3_video4", "zone3_video4"), + ZONE3_SOURCE_VIDEO5("Zone 3 Source Video 5", ZONE3_CMD, (byte) 0x09, "zone3_video5", "zone3_video5"), + ZONE3_SOURCE_VIDEO6("Zone 3 Source Video 6", ZONE3_CMD, (byte) 0x94, "zone3_video6", "zone3_video6"), + ZONE3_SOURCE_USB("Zone 3 Source Front USB", ZONE3_CMD, (byte) 0x8E, "zone3_usb", "zone3_usb"), + ZONE3_SOURCE_MAIN("Zone 3 Follow Main Zone Source", ZONE3_CMD, (byte) 0x6B, "zone3_follow_main", "zone3_follow_main"), - ZONE4_SOURCE_CD("Zone 4 Source CD", RotelConnector.ZONE4_CMD, (byte) 0x02, "zone4_cd", "zone4_cd"), - ZONE4_SOURCE_TUNER("Zone 4 Source Tuner", RotelConnector.ZONE4_CMD, (byte) 0x03, "zone4_tuner", "zone4_tuner"), - ZONE4_SOURCE_TAPE("Zone 4 Source Tape", RotelConnector.ZONE4_CMD, (byte) 0x04, "zone4_tape", "zone4_tape"), - ZONE4_SOURCE_VIDEO1("Zone 4 Source Video 1", RotelConnector.ZONE4_CMD, (byte) 0x05, "zone4_video1", "zone4_video1"), - ZONE4_SOURCE_VIDEO2("Zone 4 Source Video 2", RotelConnector.ZONE4_CMD, (byte) 0x06, "zone4_video2", "zone4_video2"), - ZONE4_SOURCE_VIDEO3("Zone 4 Source Video 3", RotelConnector.ZONE4_CMD, (byte) 0x07, "zone4_video3", "zone4_video3"), - ZONE4_SOURCE_VIDEO4("Zone 4 Source Video 4", RotelConnector.ZONE4_CMD, (byte) 0x08, "zone4_video4", "zone4_video4"), - ZONE4_SOURCE_VIDEO5("Zone 4 Source Video 5", RotelConnector.ZONE4_CMD, (byte) 0x09, "zone4_video5", "zone4_video5"), - ZONE4_SOURCE_VIDEO6("Zone 4 Source Video 6", RotelConnector.ZONE4_CMD, (byte) 0x94, "zone4_video6", "zone4_video6"), - ZONE4_SOURCE_USB("Zone 4 Source Front USB", RotelConnector.ZONE4_CMD, (byte) 0x8E, "zone4_usb", "zone4_usb"), - ZONE4_SOURCE_MAIN("Zone 4 Follow Main Zone Source", RotelConnector.ZONE4_CMD, (byte) 0x6B, "zone4_follow_main", + ZONE4_SOURCE_CD("Zone 4 Source CD", ZONE4_CMD, (byte) 0x02, "zone4_cd", "zone4_cd"), + ZONE4_SOURCE_TUNER("Zone 4 Source Tuner", ZONE4_CMD, (byte) 0x03, "zone4_tuner", "zone4_tuner"), + ZONE4_SOURCE_TAPE("Zone 4 Source Tape", ZONE4_CMD, (byte) 0x04, "zone4_tape", "zone4_tape"), + ZONE4_SOURCE_VIDEO1("Zone 4 Source Video 1", ZONE4_CMD, (byte) 0x05, "zone4_video1", "zone4_video1"), + ZONE4_SOURCE_VIDEO2("Zone 4 Source Video 2", ZONE4_CMD, (byte) 0x06, "zone4_video2", "zone4_video2"), + ZONE4_SOURCE_VIDEO3("Zone 4 Source Video 3", ZONE4_CMD, (byte) 0x07, "zone4_video3", "zone4_video3"), + ZONE4_SOURCE_VIDEO4("Zone 4 Source Video 4", ZONE4_CMD, (byte) 0x08, "zone4_video4", "zone4_video4"), + ZONE4_SOURCE_VIDEO5("Zone 4 Source Video 5", ZONE4_CMD, (byte) 0x09, "zone4_video5", "zone4_video5"), + ZONE4_SOURCE_VIDEO6("Zone 4 Source Video 6", ZONE4_CMD, (byte) 0x94, "zone4_video6", "zone4_video6"), + ZONE4_SOURCE_USB("Zone 4 Source Front USB", ZONE4_CMD, (byte) 0x8E, "zone4_usb", "zone4_usb"), + ZONE4_SOURCE_MAIN("Zone 4 Follow Main Zone Source", ZONE4_CMD, (byte) 0x6B, "zone4_follow_main", "zone4_follow_main"), - STEREO("Stereo", RotelConnector.PRIMARY_CMD, (byte) 0x11, "2channel", "2channel"), - STEREO3("Dolby 3 Stereo ", RotelConnector.PRIMARY_CMD, (byte) 0x12, "3channel", "3channel"), - STEREO5("5 Channel Stereo", RotelConnector.PRIMARY_CMD, (byte) 0x5B, "5channel", "5channel"), - STEREO7("7 Channel Stereo", RotelConnector.PRIMARY_CMD, (byte) 0x5C, "7channel", "7channel"), + STEREO("Stereo", PRIMARY_CMD, (byte) 0x11, "2channel", "2channel"), + STEREO3("Dolby 3 Stereo ", PRIMARY_CMD, (byte) 0x12, "3channel", "3channel"), + STEREO5("5 Channel Stereo", PRIMARY_CMD, (byte) 0x5B, "5channel", "5channel"), + STEREO7("7 Channel Stereo", PRIMARY_CMD, (byte) 0x5C, "7channel", "7channel"), STEREO9("9 Channel Stereo", "9channel", "9channel"), STEREO11("11 Channel Stereo", "11channel", "11channel"), - DSP1("DSP 1", RotelConnector.PRIMARY_CMD, (byte) 0x57), - DSP2("DSP 2", RotelConnector.PRIMARY_CMD, (byte) 0x58), - DSP3("DSP 3", RotelConnector.PRIMARY_CMD, (byte) 0x59), - DSP4("DSP 4", RotelConnector.PRIMARY_CMD, (byte) 0x5A), - PROLOGIC("Dolby Pro Logic", RotelConnector.PRIMARY_CMD, (byte) 0x5F), - PLII_CINEMA("Dolby PLII Cinema", RotelConnector.PRIMARY_CMD, (byte) 0x5D, "prologic_movie", "prologic_movie"), - PLII_MUSIC("Dolby PLII Music", RotelConnector.PRIMARY_CMD, (byte) 0x5E, "prologic_music", "prologic_music"), - PLII_GAME("Dolby PLII Game", RotelConnector.PRIMARY_CMD, (byte) 0x74, "prologic_game", "prologic_game"), - PLIIZ("Dolby PLIIz", RotelConnector.PRIMARY_CMD, (byte) 0x92, "prologic_iiz", "prologic_iiz"), - NEO6_MUSIC("dts Neo:6 Music", RotelConnector.PRIMARY_CMD, (byte) 0x60, "neo6_music", "neo6_music"), - NEO6_CINEMA("dts Neo:6 Cinema", RotelConnector.PRIMARY_CMD, (byte) 0x61, "neo6_cinema", "neo6_cinema"), + DSP1("DSP 1", PRIMARY_CMD, (byte) 0x57), + DSP2("DSP 2", PRIMARY_CMD, (byte) 0x58), + DSP3("DSP 3", PRIMARY_CMD, (byte) 0x59), + DSP4("DSP 4", PRIMARY_CMD, (byte) 0x5A), + PROLOGIC("Dolby Pro Logic", PRIMARY_CMD, (byte) 0x5F), + PLII_CINEMA("Dolby PLII Cinema", PRIMARY_CMD, (byte) 0x5D, "prologic_movie", "prologic_movie"), + PLII_MUSIC("Dolby PLII Music", PRIMARY_CMD, (byte) 0x5E, "prologic_music", "prologic_music"), + PLII_GAME("Dolby PLII Game", PRIMARY_CMD, (byte) 0x74, "prologic_game", "prologic_game"), + PLIIZ("Dolby PLIIz", PRIMARY_CMD, (byte) 0x92, "prologic_iiz", "prologic_iiz"), + NEO6_MUSIC("dts Neo:6 Music", PRIMARY_CMD, (byte) 0x60, "neo6_music", "neo6_music"), + NEO6_CINEMA("dts Neo:6 Cinema", PRIMARY_CMD, (byte) 0x61, "neo6_cinema", "neo6_cinema"), ATMOS("Dolby Atmos", "dolby_atmos", "dolby_atmos"), NEURAL_X("dts Neural:X", "dts_neural", "dts_neural"), - BYPASS("Analog Bypass", RotelConnector.PRIMARY_CMD, (byte) 0x11, "bypass", "bypass"), + BYPASS("Analog Bypass", PRIMARY_CMD, (byte) 0x11, "bypass", "bypass"), DSP_MODE("Request current DSP mode", "get_dsp_mode", "dsp_mode"), TONE_MAX("Request Max tone level", "get_tone_max", null), - TONE_CONTROL_SELECT("Tone Control Select", RotelConnector.PRIMARY_CMD, (byte) 0x67), - TREBLE_UP("Treble Up", RotelConnector.PRIMARY_CMD, (byte) 0x0D, "treble_up", "treble_up"), - TREBLE_DOWN("Treble Down", RotelConnector.PRIMARY_CMD, (byte) 0x0E, "treble_down", "treble_down"), + TONE_CONTROL_SELECT("Tone Control Select", PRIMARY_CMD, (byte) 0x67), + TREBLE_UP("Treble Up", PRIMARY_CMD, (byte) 0x0D, "treble_up", "treble_up"), + TREBLE_DOWN("Treble Down", PRIMARY_CMD, (byte) 0x0E, "treble_down", "treble_down"), TREBLE_SET("Set Treble to level", "treble_", "treble_"), TREBLE("Request current treble level", "get_treble", "treble?"), - BASS_UP("Bass Up", RotelConnector.PRIMARY_CMD, (byte) 0x0F, "bass_up", "bass_up"), - BASS_DOWN("Bass Down", RotelConnector.PRIMARY_CMD, (byte) 0x10, "bass_down", "bass_down"), + BASS_UP("Bass Up", PRIMARY_CMD, (byte) 0x0F, "bass_up", "bass_up"), + BASS_DOWN("Bass Down", PRIMARY_CMD, (byte) 0x10, "bass_down", "bass_down"), BASS_SET("Set Bass to level", "bass_", "bass_"), BASS("Request current bass level", "get_bass", "bass?"), - RECORD_FONCTION_SELECT("Record Function Select", RotelConnector.PRIMARY_CMD, (byte) 0x17), - PLAY("Play Source", RotelConnector.PRIMARY_CMD, (byte) 0x04, "play", "play"), - STOP("Stop Source", RotelConnector.PRIMARY_CMD, (byte) 0x06, "stop", "stop"), - PAUSE("Pause Source", RotelConnector.PRIMARY_CMD, (byte) 0x05, "pause", "pause"), + RECORD_FONCTION_SELECT("Record Function Select", PRIMARY_CMD, (byte) 0x17), + PLAY("Play Source", PRIMARY_CMD, (byte) 0x04, "play", "play"), + STOP("Stop Source", PRIMARY_CMD, (byte) 0x06, "stop", "stop"), + PAUSE("Pause Source", PRIMARY_CMD, (byte) 0x05, "pause", "pause"), CD_PLAY_STATUS("Request CD play status", "get_cd_play_status", null), PLAY_STATUS("Request source play status", "get_play_status", "status"), - TRACK_FORWARD("Track Forward", RotelConnector.PRIMARY_CMD, (byte) 0x09, "track_fwd", "trkf"), - TRACK_BACKWORD("Track Backward", RotelConnector.PRIMARY_CMD, (byte) 0x08, "track_back", "trkb"), + TRACK_FORWARD("Track Forward", PRIMARY_CMD, (byte) 0x09, "track_fwd", "trkf"), + TRACK_BACKWORD("Track Backward", PRIMARY_CMD, (byte) 0x08, "track_back", "trkb"), TRACK("Request current CD track number", null, "track"), FREQUENCY("Request current frequency for digital source input", "get_current_freq", "freq?"), - DISPLAY_REFRESH("Display Refresh", RotelConnector.PRIMARY_CMD, (byte) 0xFF), + DISPLAY_REFRESH("Display Refresh", PRIMARY_CMD, (byte) 0xFF), DIMMER_LEVEL_GET("Request current front display dimmer level", "get_current_dimmer", "dimmer?"), DIMMER_LEVEL_SET("Set front display dimmer to level", "dimmer_", "dimmer_"), UPDATE_AUTO("Set Update to Auto", "display_update_auto", "rs232_update_on"), @@ -245,10 +235,10 @@ public enum RotelCommand { BALANCE_LEFT("Balance Left", "balance_left", "balance_l"), BALANCE_SET("Set Balance to level", "balance_", "balance_"), BALANCE("Request current balance setting", "get_balance", "balance?"), - SPEAKER_A_TOGGLE("Toggle Speaker A Output", RotelConnector.PRIMARY_CMD, (byte) 0x50, "speaker_a", "speaker_a"), + SPEAKER_A_TOGGLE("Toggle Speaker A Output", PRIMARY_CMD, (byte) 0x50, "speaker_a", "speaker_a"), SPEAKER_A_ON("Set Speaker A Output", "speaker_a_on", "speaker_a_on"), SPEAKER_A_OFF("Unset Speaker A Output", "speaker_a_off", "speaker_a_off"), - SPEAKER_B_TOGGLE("Toggle Speaker B Output", RotelConnector.PRIMARY_CMD, (byte) 0x51, "speaker_b", "speaker_b"), + SPEAKER_B_TOGGLE("Toggle Speaker B Output", PRIMARY_CMD, (byte) 0x51, "speaker_b", "speaker_b"), SPEAKER_B_ON("Set Speaker B Output", "speaker_b_on", "speaker_b_on"), SPEAKER_B_OFF("Unset Speaker B Output", "speaker_b_off", "speaker_b_off"), SPEAKER("Request current active speaker outputs", "get_current_speaker", "speaker?"); diff --git a/bundles/org.openhab.binding.rotel/src/main/java/org/openhab/binding/rotel/internal/communication/RotelConnector.java b/bundles/org.openhab.binding.rotel/src/main/java/org/openhab/binding/rotel/internal/communication/RotelConnector.java index 06fbdd916..0849ac4a7 100644 --- a/bundles/org.openhab.binding.rotel/src/main/java/org/openhab/binding/rotel/internal/communication/RotelConnector.java +++ b/bundles/org.openhab.binding.rotel/src/main/java/org/openhab/binding/rotel/internal/communication/RotelConnector.java @@ -16,18 +16,11 @@ import java.io.IOException; import java.io.InputStream; import java.io.InterruptedIOException; import java.io.OutputStream; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Map; -import java.util.regex.PatternSyntaxException; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.rotel.internal.RotelException; -import org.openhab.binding.rotel.internal.RotelModel; -import org.openhab.core.util.HexUtils; +import org.openhab.binding.rotel.internal.protocol.RotelAbstractProtocolHandler; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -41,148 +34,8 @@ public abstract class RotelConnector { private final Logger logger = LoggerFactory.getLogger(RotelConnector.class); - public static final byte[] READ_ERROR = "read_error".getBytes(StandardCharsets.US_ASCII); - - protected static final byte START = (byte) 0xFE; - - // Message types - public static final byte PRIMARY_CMD = (byte) 0x10; - public static final byte MAIN_ZONE_CMD = (byte) 0x14; - public static final byte RECORD_SRC_CMD = (byte) 0x15; - public static final byte ZONE2_CMD = (byte) 0x16; - public static final byte ZONE3_CMD = (byte) 0x17; - public static final byte ZONE4_CMD = (byte) 0x18; - public static final byte VOLUME_CMD = (byte) 0x30; - public static final byte ZONE2_VOLUME_CMD = (byte) 0x32; - public static final byte ZONE3_VOLUME_CMD = (byte) 0x33; - public static final byte ZONE4_VOLUME_CMD = (byte) 0x34; - private static final byte TRIGGER_CMD = (byte) 0x40; - protected static final byte STANDARD_RESPONSE = (byte) 0x20; - private static final byte TRIGGER_STATUS = (byte) 0x21; - private static final byte SMART_DISPLAY_DATA_1 = (byte) 0x22; - private static final byte SMART_DISPLAY_DATA_2 = (byte) 0x23; - - // Keys used by the HEX protocol - private static final String KEY1_HEX_VOLUME = "volume "; - private static final String KEY2_HEX_VOLUME = "vol "; - private static final String KEY_HEX_MUTE = "mute "; - private static final String KEY1_HEX_BASS = "bass "; - private static final String KEY2_HEX_BASS = "lf "; - private static final String KEY1_HEX_TREBLE = "treble "; - private static final String KEY2_HEX_TREBLE = "hf "; - private static final String KEY_HEX_MULTI_IN = "multi in "; - private static final String KEY_HEX_STEREO = "stereo"; - private static final String KEY1_HEX_3CH = "3 stereo"; - private static final String KEY2_HEX_3CH = "dolby 3 stereo"; - private static final String KEY_HEX_5CH = "5ch stereo"; - private static final String KEY_HEX_7CH = "7ch stereo"; - private static final String KEY_HEX_MUSIC1 = "music 1"; - private static final String KEY_HEX_MUSIC2 = "music 2"; - private static final String KEY_HEX_MUSIC3 = "music 3"; - private static final String KEY_HEX_MUSIC4 = "music 4"; - private static final String KEY_HEX_DSP1 = "dsp 1"; - private static final String KEY_HEX_DSP2 = "dsp 2"; - private static final String KEY_HEX_DSP3 = "dsp 3"; - private static final String KEY_HEX_DSP4 = "dsp 4"; - private static final String KEY1_HEX_PROLOGIC = "prologic emu"; - private static final String KEY2_HEX_PROLOGIC = "dolby pro logic"; - private static final String KEY1_HEX_PLII_CINEMA = "prologic cin"; - private static final String KEY2_HEX_PLII_CINEMA = "dolby pl c"; - private static final String KEY1_HEX_PLII_MUSIC = "prologic mus"; - private static final String KEY2_HEX_PLII_MUSIC = "dolby pl m"; - private static final String KEY1_HEX_PLII_GAME = "prologic gam"; - private static final String KEY2_HEX_PLII_GAME = "dolby pl g"; - private static final String KEY1_HEX_PLIIX_CINEMA = "pl x cinema"; - private static final String KEY2_HEX_PLIIX_CINEMA = "dolby pl x c"; - private static final String KEY1_HEX_PLIIX_MUSIC = "pl x music"; - private static final String KEY2_HEX_PLIIX_MUSIC = "dolby pl x m"; - private static final String KEY1_HEX_PLIIX_GAME = "pl x game"; - private static final String KEY2_HEX_PLIIX_GAME = "dolby pl x g"; - private static final String KEY_HEX_PLIIZ = "dolby pl z"; - private static final String KEY1_HEX_DTS_NEO6_CINEMA = "neo 6 cinema"; - private static final String KEY2_HEX_DTS_NEO6_CINEMA = "dts neo:6 c"; - private static final String KEY1_HEX_DTS_NEO6_MUSIC = "neo 6 music"; - private static final String KEY2_HEX_DTS_NEO6_MUSIC = "dts neo:6 m"; - private static final String KEY_HEX_DTS = "dts"; - private static final String KEY_HEX_DTS_ES = "dts-es"; - private static final String KEY_HEX_DTS_96 = "dts 96"; - private static final String KEY_HEX_DD = "dolby digital"; - private static final String KEY_HEX_DD_EX = "dolby d ex"; - private static final String KEY_HEX_PCM = "pcm"; - private static final String KEY_HEX_LPCM = "lpcm"; - private static final String KEY_HEX_MPEG = "mpeg"; - private static final String KEY_HEX_BYPASS = "bypass"; - private static final String KEY1_HEX_ZONE2 = "zone "; - private static final String KEY2_HEX_ZONE2 = "zone2 "; - private static final String KEY_HEX_ZONE3 = "zone3 "; - private static final String KEY_HEX_ZONE4 = "zone4 "; - private static final String KEY_HEX_RECORD = "rec "; - - // Keys used by the ASCII protocol - public static final String KEY_UPDATE_MODE = "update_mode"; - public static final String KEY_DISPLAY_UPDATE = "display_update"; - public static final String KEY_POWER = "power"; - public static final String KEY_VOLUME_MIN = "volume_min"; - public static final String KEY_VOLUME_MAX = "volume_max"; - public static final String KEY_VOLUME = "volume"; - public static final String KEY_MUTE = "mute"; - public static final String KEY_TONE_MAX = "tone_max"; - public static final String KEY_BASS = "bass"; - public static final String KEY_TREBLE = "treble"; - public static final String KEY_SOURCE = "source"; - public static final String KEY1_PLAY_STATUS = "play_status"; - public static final String KEY2_PLAY_STATUS = "status"; - public static final String KEY_TRACK = "track"; - public static final String KEY_DSP_MODE = "dsp_mode"; - public static final String KEY_DIMMER = "dimmer"; - public static final String KEY_FREQ = "freq"; - public static final String KEY_TONE = "tone"; - public static final String KEY_TCBYPASS = "bypass"; - public static final String KEY_BALANCE = "balance"; - public static final String KEY_SPEAKER = "speaker"; - - // Special keys used by the binding - public static final String KEY_LINE1 = "line1"; - public static final String KEY_LINE2 = "line2"; - public static final String KEY_RECORD = "record"; - public static final String KEY_RECORD_SEL = "record_sel"; - public static final String KEY_ZONE = "zone"; - public static final String KEY_POWER_ZONE2 = "power_zone2"; - public static final String KEY_POWER_ZONE3 = "power_zone3"; - public static final String KEY_POWER_ZONE4 = "power_zone4"; - public static final String KEY_SOURCE_ZONE2 = "source_zone2"; - public static final String KEY_SOURCE_ZONE3 = "source_zone3"; - public static final String KEY_SOURCE_ZONE4 = "source_zone4"; - public static final String KEY_VOLUME_ZONE2 = "volume_zone2"; - public static final String KEY_VOLUME_ZONE3 = "volume_zone3"; - public static final String KEY_VOLUME_ZONE4 = "volume_zone4"; - public static final String KEY_MUTE_ZONE2 = "mute_zone2"; - public static final String KEY_MUTE_ZONE3 = "mute_zone3"; - public static final String KEY_MUTE_ZONE4 = "mute_zone4"; - public static final String KEY_ERROR = "error"; - - public static final String MSG_VALUE_OFF = "off"; - public static final String MSG_VALUE_ON = "on"; - public static final String POWER_ON = "on"; - public static final String STANDBY = "standby"; - public static final String POWER_OFF_DELAYED = "off_delayed"; - protected static final String AUTO = "auto"; - protected static final String MANUAL = "manual"; - public static final String MSG_VALUE_MIN = "min"; - public static final String MSG_VALUE_MAX = "max"; - public static final String MSG_VALUE_FIX = "fix"; - public static final String PLAY = "play"; - public static final String PAUSE = "pause"; - public static final String STOP = "stop"; - private static final String SOURCE = "source"; - public static final String MSG_VALUE_SPEAKER_A = "a"; - public static final String MSG_VALUE_SPEAKER_B = "b"; - public static final String MSG_VALUE_SPEAKER_AB = "a_b"; - - private RotelModel model; - private RotelProtocol protocol; - protected Map sourcesLabels; - private boolean simu; + private final boolean simu; + protected final Thread readerThread; /** The output stream */ protected @Nullable OutputStream dataOut; @@ -193,69 +46,16 @@ public abstract class RotelConnector { /** true if the connection is established, false if not */ private boolean connected; - protected String readerThreadName; - private @Nullable Thread readerThread; - - private List listeners = new ArrayList<>(); - - /** Special characters that can be found in the feedback messages for several devices using the ASCII protocol */ - public static final byte[][] SPECIAL_CHARACTERS = { { (byte) 0xEE, (byte) 0x82, (byte) 0x85 }, - { (byte) 0xEE, (byte) 0x82, (byte) 0x84 }, { (byte) 0xEE, (byte) 0x82, (byte) 0x92 }, - { (byte) 0xEE, (byte) 0x82, (byte) 0x87 }, { (byte) 0xEE, (byte) 0x82, (byte) 0x8E }, - { (byte) 0xEE, (byte) 0x82, (byte) 0x89 }, { (byte) 0xEE, (byte) 0x82, (byte) 0x93 }, - { (byte) 0xEE, (byte) 0x82, (byte) 0x8C }, { (byte) 0xEE, (byte) 0x82, (byte) 0x8F }, - { (byte) 0xEE, (byte) 0x82, (byte) 0x8A }, { (byte) 0xEE, (byte) 0x82, (byte) 0x8B }, - { (byte) 0xEE, (byte) 0x82, (byte) 0x81 }, { (byte) 0xEE, (byte) 0x82, (byte) 0x82 }, - { (byte) 0xEE, (byte) 0x82, (byte) 0x83 }, { (byte) 0xEE, (byte) 0x82, (byte) 0x94 }, - { (byte) 0xEE, (byte) 0x82, (byte) 0x97 }, { (byte) 0xEE, (byte) 0x82, (byte) 0x98 }, - { (byte) 0xEE, (byte) 0x82, (byte) 0x80 }, { (byte) 0xEE, (byte) 0x82, (byte) 0x99 }, - { (byte) 0xEE, (byte) 0x82, (byte) 0x9A }, { (byte) 0xEE, (byte) 0x82, (byte) 0x88 }, - { (byte) 0xEE, (byte) 0x82, (byte) 0x95 }, { (byte) 0xEE, (byte) 0x82, (byte) 0x96 }, - { (byte) 0xEE, (byte) 0x82, (byte) 0x90 }, { (byte) 0xEE, (byte) 0x82, (byte) 0x91 }, - { (byte) 0xEE, (byte) 0x82, (byte) 0x8D }, { (byte) 0xEE, (byte) 0x80, (byte) 0x80, (byte) 0xEE, - (byte) 0x80, (byte) 0x81, (byte) 0xEE, (byte) 0x80, (byte) 0x82 } }; - - /** Special characters that can be found in the feedback messages for the RCD-1572 */ - public static final byte[][] SPECIAL_CHARACTERS_RCD1572 = { { (byte) 0xC2, (byte) 0x8C }, - { (byte) 0xC2, (byte) 0x54 }, { (byte) 0xC2, (byte) 0x81 }, { (byte) 0xC2, (byte) 0x82 }, - { (byte) 0xC2, (byte) 0x83 } }; - - /** Empty table of special characters */ - public static final byte[][] NO_SPECIAL_CHARACTERS = {}; - /** * Constructor * - * @param model the Rotel model in use - * @param protocol the protocol to be used + * @param protocolHandler the protocol handler * @param simu whether the communication is simulated or real * @param readerThreadName the name of thread to be created */ - public RotelConnector(RotelModel model, RotelProtocol protocol, Map sourcesLabels, - boolean simu, String readerThreadName) { - this.model = model; - this.protocol = protocol; - this.sourcesLabels = sourcesLabels; + public RotelConnector(RotelAbstractProtocolHandler protocolHandler, boolean simu, String readerThreadName) { this.simu = simu; - this.readerThreadName = readerThreadName; - } - - /** - * Get the Rotel model - * - * @return the model - */ - public RotelModel getModel() { - return model; - } - - /** - * Get the protocol to be used - * - * @return the protocol - */ - public RotelProtocol getProtocol() { - return protocol; + this.readerThread = new RotelReaderThread(this, protocolHandler, readerThreadName); } /** @@ -276,15 +76,6 @@ public abstract class RotelConnector { this.connected = connected; } - /** - * Set the thread that handles the feedback messages - * - * @param readerThread the thread - */ - protected void setReaderThread(Thread readerThread) { - this.readerThread = readerThread; - } - /** * Open the connection with the Rotel device * @@ -301,14 +92,10 @@ public abstract class RotelConnector { * Stop the thread that handles the feedback messages and close the opened input and output streams */ protected void cleanup() { - Thread readerThread = this.readerThread; - if (readerThread != null) { - readerThread.interrupt(); - try { - readerThread.join(); - } catch (InterruptedException e) { - } - this.readerThread = null; + readerThread.interrupt(); + try { + readerThread.join(); + } catch (InterruptedException e) { } OutputStream dataOut = this.dataOut; if (dataOut != null) { @@ -361,890 +148,26 @@ public abstract class RotelConnector { /** * Request the Rotel device to execute a command * - * @param cmd the command to execute + * @param cmdName the command name + * @param dataBuffer the data buffer containing the encoded command * * @throws RotelException - In case of any problem */ - public void sendCommand(RotelCommand cmd) throws RotelException { - sendCommand(cmd, null); - } - - /** - * Request the Rotel device to execute a command - * - * @param cmd the command to execute - * @param value the integer value to consider for volume, bass or treble adjustment - * - * @throws RotelException - In case of any problem - */ - public void sendCommand(RotelCommand cmd, @Nullable Integer value) throws RotelException { - String messageStr; - byte[] message = new byte[0]; - switch (protocol) { - case HEX: - if (cmd.getHexType() == 0) { - logger.debug("Send comman \"{}\" ignored: not available for HEX protocol", cmd.getName()); - return; - } else { - final int size = 6; - message = new byte[size]; - int idx = 0; - message[idx++] = START; - message[idx++] = 3; - message[idx++] = model.getDeviceId(); - message[idx++] = cmd.getHexType(); - message[idx++] = (value == null) ? cmd.getHexKey() : (byte) (value & 0x000000FF); - final byte checksum = computeCheckSum(message, idx - 1); - if ((checksum & 0x000000FF) == 0x000000FD || (checksum & 0x000000FF) == 0x000000FE) { - message = Arrays.copyOf(message, size + 1); - message[idx++] = (byte) 0xFD; - message[idx++] = ((checksum & 0x000000FF) == 0x000000FD) ? (byte) 0 : (byte) 1; - } else { - message[idx++] = checksum; - } - logger.debug("Send command \"{}\" => {}", cmd.getName(), HexUtils.bytesToHex(message)); - } - break; - case ASCII_V1: - messageStr = cmd.getAsciiCommandV1(); - if (messageStr == null) { - logger.debug("Send comman \"{}\" ignored: not available for ASCII V1 protocol", cmd.getName()); - return; - } else { - if (value != null) { - switch (cmd) { - case VOLUME_SET: - messageStr += String.format("%d", value); - break; - case BASS_SET: - case TREBLE_SET: - if (value == 0) { - messageStr += "000"; - } else if (value > 0) { - messageStr += String.format("+%02d", value); - } else { - messageStr += String.format("-%02d", -value); - } - break; - case BALANCE_SET: - if (value == 0) { - messageStr += "000"; - } else if (value > 0) { - messageStr += String.format("R%02d", value); - } else { - messageStr += String.format("L%02d", -value); - } - break; - case DIMMER_LEVEL_SET: - if (value > 0 && model.getDimmerLevelMin() < 0) { - messageStr += String.format("+%d", value); - } else { - messageStr += String.format("%d", value); - } - break; - default: - break; - } - } - if (!messageStr.endsWith("?")) { - messageStr += "!"; - } - message = messageStr.getBytes(StandardCharsets.US_ASCII); - logger.debug("Send command \"{}\" => {}", cmd.getName(), messageStr); - } - break; - case ASCII_V2: - messageStr = cmd.getAsciiCommandV2(); - if (messageStr == null) { - logger.debug("Send comman \"{}\" ignored: not available for ASCII V2 protocol", cmd.getName()); - return; - } else { - if (value != null) { - switch (cmd) { - case VOLUME_SET: - messageStr += String.format("%02d", value); - break; - case BASS_SET: - case TREBLE_SET: - if (value == 0) { - messageStr += "000"; - } else if (value > 0) { - messageStr += String.format("+%02d", value); - } else { - messageStr += String.format("-%02d", -value); - } - break; - case BALANCE_SET: - if (value == 0) { - messageStr += "000"; - } else if (value > 0) { - messageStr += String.format("r%02d", value); - } else { - messageStr += String.format("l%02d", -value); - } - break; - case DIMMER_LEVEL_SET: - if (value > 0 && model.getDimmerLevelMin() < 0) { - messageStr += String.format("+%d", value); - } else { - messageStr += String.format("%d", value); - } - break; - default: - break; - } - } - if (!messageStr.endsWith("?")) { - messageStr += "!"; - } - message = messageStr.getBytes(StandardCharsets.US_ASCII); - logger.debug("Send command \"{}\" => {}", cmd.getName(), messageStr); - } - break; - } + public void writeOutput(String cmdName, byte[] dataBuffer) throws RotelException { if (simu) { return; } OutputStream dataOut = this.dataOut; if (dataOut == null) { - throw new RotelException("Send command \"" + cmd.getName() + "\" failed: output stream is null"); + throw new RotelException("Send command \"" + cmdName + "\" failed: output stream is null"); } try { - dataOut.write(message); + dataOut.write(dataBuffer); dataOut.flush(); } catch (IOException e) { - logger.debug("Send command \"{}\" failed: {}", cmd.getName(), e.getMessage()); - throw new RotelException("Send command \"" + cmd.getName() + "\" failed", e); - } - logger.debug("Send command \"{}\" succeeded", cmd.getName()); - } - - /** - * Validate the content of a feedback message - * - * @param responseMessage the buffer containing the feedback message - * - * @throws RotelException - If the message has unexpected content - */ - private void validateResponse(byte[] responseMessage) throws RotelException { - if (protocol == RotelProtocol.HEX) { - // Check minimum message length - if (responseMessage.length < 6) { - logger.debug("Unexpected message length: {}", responseMessage.length); - throw new RotelException("Unexpected message length"); - } - - // Check START - if (responseMessage[0] != START) { - logger.debug("Unexpected START in response: {} rather than {}", - Integer.toHexString(responseMessage[0] & 0x000000FF), Integer.toHexString(START & 0x000000FF)); - throw new RotelException("Unexpected START in response"); - } - - // Check ID - if (responseMessage[2] != model.getDeviceId()) { - logger.debug("Unexpected ID in response: {} rather than {}", - Integer.toHexString(responseMessage[2] & 0x000000FF), - Integer.toHexString(model.getDeviceId() & 0x000000FF)); - throw new RotelException("Unexpected ID in response"); - } - - // Check TYPE - if (responseMessage[3] != STANDARD_RESPONSE && responseMessage[3] != TRIGGER_STATUS - && responseMessage[3] != SMART_DISPLAY_DATA_1 && responseMessage[3] != SMART_DISPLAY_DATA_2 - && responseMessage[3] != PRIMARY_CMD && responseMessage[3] != MAIN_ZONE_CMD - && responseMessage[3] != RECORD_SRC_CMD && responseMessage[3] != ZONE2_CMD - && responseMessage[3] != ZONE3_CMD && responseMessage[3] != ZONE4_CMD - && responseMessage[3] != VOLUME_CMD && responseMessage[3] != ZONE2_VOLUME_CMD - && responseMessage[3] != ZONE3_VOLUME_CMD && responseMessage[3] != ZONE4_VOLUME_CMD - && responseMessage[3] != TRIGGER_CMD) { - logger.debug("Unexpected TYPE in response: {}", Integer.toHexString(responseMessage[3] & 0x000000FF)); - throw new RotelException("Unexpected TYPE in response"); - } - - int expectedLen = (responseMessage[3] == STANDARD_RESPONSE) - ? (5 + model.getRespNbChars() + model.getRespNbFlags()) - : responseMessage.length; - - // Check COUNT - if (responseMessage[1] != (expectedLen - 3)) { - logger.debug("Unexpected COUNT in response: {} rather than {}", - Integer.toHexString(responseMessage[1] & 0x000000FF), - Integer.toHexString((expectedLen - 3) & 0x000000FF)); - throw new RotelException("Unexpected COUNT in response"); - } - - final byte checksum = computeCheckSum(responseMessage, expectedLen - 2); - if ((checksum & 0x000000FF) == 0x000000FD || (checksum & 0x000000FF) == 0x000000FE) { - expectedLen++; - } - - // Check message length - if (responseMessage.length != expectedLen) { - logger.debug("Unexpected message length: {} rather than {}", responseMessage.length, expectedLen); - throw new RotelException("Unexpected message length"); - } - - // Check sum - if ((checksum & 0x000000FF) == 0x000000FD) { - if ((responseMessage[responseMessage.length - 2] & 0x000000FF) != 0x000000FD - || (responseMessage[responseMessage.length - 1] & 0x000000FF) != 0) { - logger.debug("Invalid check sum in response: {} rather than FD00", HexUtils.bytesToHex( - Arrays.copyOfRange(responseMessage, responseMessage.length - 2, responseMessage.length))); - throw new RotelException("Invalid check sum in response"); - } - } else if ((checksum & 0x000000FF) == 0x000000FE) { - if ((responseMessage[responseMessage.length - 2] & 0x000000FF) != 0x000000FD - || (responseMessage[responseMessage.length - 1] & 0x000000FF) != 1) { - logger.debug("Invalid check sum in response: {} rather than FD01", HexUtils.bytesToHex( - Arrays.copyOfRange(responseMessage, responseMessage.length - 2, responseMessage.length))); - throw new RotelException("Invalid check sum in response"); - } - } else if ((checksum & 0x000000FF) != (responseMessage[responseMessage.length - 1] & 0x000000FF)) { - logger.debug("Invalid check sum in response: {} rather than {}", - Integer.toHexString(responseMessage[responseMessage.length - 1] & 0x000000FF), - Integer.toHexString(checksum & 0x000000FF)); - throw new RotelException("Invalid check sum in response"); - } - } else { - // Check minimum message length - if (responseMessage.length < 1) { - logger.debug("Unexpected message length: {}", responseMessage.length); - throw new RotelException("Unexpected message length"); - } - - if (responseMessage[responseMessage.length - 1] != '!' - && responseMessage[responseMessage.length - 1] != '$') { - logger.debug("Unexpected ending character in response: {}", - Integer.toHexString(responseMessage[responseMessage.length - 1] & 0x000000FF)); - throw new RotelException("Unexpected ending character in response"); - } - } - } - - /** - * Compute the checksum of a message - * - * @param message the buffer containing the message - * @param maxIdx the position in the buffer at which the sum has to be stopped - * - * @return the checksum as a byte - */ - protected byte computeCheckSum(byte[] message, int maxIdx) { - int result = 0; - for (int i = 1; i <= maxIdx; i++) { - result += (message[i] & 0x000000FF); - } - return (byte) (result & 0x000000FF); - } - - /** - * Add a listener to the list of listeners to be notified with events - * - * @param listener the listener - */ - public void addEventListener(RotelMessageEventListener listener) { - listeners.add(listener); - } - - /** - * Remove a listener from the list of listeners to be notified with events - * - * @param listener the listener - */ - public void removeEventListener(RotelMessageEventListener listener) { - listeners.remove(listener); - } - - /** - * Analyze an incoming message and dispatch corresponding (key, value) to the event listeners - * - * @param incomingMessage the received message - */ - public void handleIncomingMessage(byte[] incomingMessage) { - logger.debug("handleIncomingMessage: bytes {}", HexUtils.bytesToHex(incomingMessage)); - - if (READ_ERROR.equals(incomingMessage)) { - dispatchKeyValue(KEY_ERROR, MSG_VALUE_ON); - return; - } - - try { - validateResponse(incomingMessage); - } catch (RotelException e) { - return; - } - - if (protocol == RotelProtocol.HEX) { - handleValidHexMessage(incomingMessage); - } else { - handleValidAsciiMessage(incomingMessage); - } - } - - /** - * Analyze a valid HEX message and dispatch corresponding (key, value) to the event listeners - * - * @param incomingMessage the received message - */ - private void handleValidHexMessage(byte[] incomingMessage) { - if (incomingMessage[3] != STANDARD_RESPONSE) { - return; - } - - final int idxChars = model.isCharsBeforeFlags() ? 4 : (4 + model.getRespNbFlags()); - - // Replace characters with code < 32 by a space before converting to a string - for (int i = idxChars; i < (idxChars + model.getRespNbChars()); i++) { - if (incomingMessage[i] < 0x20) { - incomingMessage[i] = 0x20; - } - } - - String value = new String(incomingMessage, idxChars, model.getRespNbChars(), StandardCharsets.US_ASCII); - logger.debug("handleValidHexMessage: chars *{}*", value); - - final int idxFlags = model.isCharsBeforeFlags() ? (4 + model.getRespNbChars()) : 4; - final byte[] flags = Arrays.copyOfRange(incomingMessage, idxFlags, idxFlags + model.getRespNbFlags()); - if (logger.isTraceEnabled()) { - for (int i = 1; i <= flags.length; i++) { - try { - logger.trace("handleValidHexMessage: Flag {} = {} bits 7-0 = {} {} {} {} {} {} {} {}", i, - Integer.toHexString(flags[i - 1] & 0x000000FF), RotelFlagsMapping.isBitFlagOn(flags, i, 7), - RotelFlagsMapping.isBitFlagOn(flags, i, 6), RotelFlagsMapping.isBitFlagOn(flags, i, 5), - RotelFlagsMapping.isBitFlagOn(flags, i, 4), RotelFlagsMapping.isBitFlagOn(flags, i, 3), - RotelFlagsMapping.isBitFlagOn(flags, i, 2), RotelFlagsMapping.isBitFlagOn(flags, i, 1), - RotelFlagsMapping.isBitFlagOn(flags, i, 0)); - } catch (RotelException e1) { - } - } - } - try { - dispatchKeyValue(KEY_POWER_ZONE2, model.isZone2On(flags) ? POWER_ON : STANDBY); - } catch (RotelException e1) { - } - try { - dispatchKeyValue(KEY_POWER_ZONE3, model.isZone3On(flags) ? POWER_ON : STANDBY); - } catch (RotelException e1) { - } - try { - dispatchKeyValue(KEY_POWER_ZONE4, model.isZone4On(flags) ? POWER_ON : STANDBY); - } catch (RotelException e1) { - } - boolean checkMultiIn = false; - boolean checkSource = true; - try { - if (model.isMultiInputOn(flags)) { - checkSource = false; - try { - RotelSource source = model.getSourceFromName(RotelSource.CAT1_MULTI.getName()); - RotelCommand cmd = source.getCommand(); - if (cmd != null) { - String value2 = cmd.getAsciiCommandV2(); - if (value2 != null) { - dispatchKeyValue(KEY_SOURCE, value2); - } - } - } catch (RotelException e1) { - } - } - } catch (RotelException e1) { - checkMultiIn = true; - } - boolean checkStereo = true; - try { - checkStereo = !model.isMoreThan2Channels(flags); - } catch (RotelException e1) { - } - - String valueLowerCase = value.trim().toLowerCase(); - if (!valueLowerCase.isEmpty() && !valueLowerCase.startsWith(KEY1_HEX_ZONE2) - && !valueLowerCase.startsWith(KEY2_HEX_ZONE2) && !valueLowerCase.startsWith(KEY_HEX_ZONE3) - && !valueLowerCase.startsWith(KEY_HEX_ZONE4)) { - dispatchKeyValue(KEY_POWER, POWER_ON); - } - - if (model.getRespNbChars() == 42) { - // 2 lines of 21 characters with a left part and a right part - - // Line 1 left - value = new String(incomingMessage, idxChars, 14, StandardCharsets.US_ASCII); - logger.debug("handleValidHexMessage: line 1 left *{}*", value); - parseText(value, checkSource, checkMultiIn, false, false, false, false, false, true); - - // Line 1 right - value = new String(incomingMessage, idxChars + 14, 7, StandardCharsets.US_ASCII); - logger.debug("handleValidHexMessage: line 1 right *{}*", value); - parseText(value, false, false, false, false, false, false, false, true); - - // Full line 1 - value = new String(incomingMessage, idxChars, 21, StandardCharsets.US_ASCII); - dispatchKeyValue(KEY_LINE1, value); - - // Line 2 right - value = new String(incomingMessage, idxChars + 35, 7, StandardCharsets.US_ASCII); - logger.debug("handleValidHexMessage: line 2 right *{}*", value); - parseText(value, false, false, false, false, false, false, false, true); - - // Full line 2 - value = new String(incomingMessage, idxChars + 21, 21, StandardCharsets.US_ASCII); - logger.debug("handleValidHexMessage: line 2 *{}*", value); - parseText(value, false, false, true, true, false, true, true, true); - dispatchKeyValue(KEY_LINE2, value); - } else { - value = new String(incomingMessage, idxChars, model.getRespNbChars(), StandardCharsets.US_ASCII); - parseText(value, checkSource, checkMultiIn, true, false, true, true, checkStereo, false); - dispatchKeyValue(KEY_LINE1, value); - } - - if (valueLowerCase.isEmpty()) { - dispatchKeyValue(KEY_POWER, POWER_OFF_DELAYED); - } - } - - /** - * Analyze a valid ASCII message and dispatch corresponding (key, value) to the event listeners - * - * @param incomingMessage the received message - */ - public void handleValidAsciiMessage(byte[] incomingMessage) { - byte[] message = filterMessage(incomingMessage, model.getSpecialCharacters()); - - // Replace characters with code < 32 by a space before converting to a string - for (int i = 0; i < message.length; i++) { - if (message[i] < 0x20) { - message[i] = 0x20; - } - } - - String value = new String(message, 0, message.length - 1, StandardCharsets.US_ASCII); - logger.debug("handleValidAsciiMessage: chars *{}*", value); - value = value.trim(); - if (value.isEmpty()) { - return; - } - try { - String[] splittedValue = value.split("="); - if (splittedValue.length != 2) { - logger.debug("handleValidAsciiMessage: ignored message {}", value); - } else { - dispatchKeyValue(splittedValue[0].trim().toLowerCase(), splittedValue[1]); - } - } catch (PatternSyntaxException e) { - logger.debug("handleValidAsciiMessage: ignored message {}", value); - } - } - - /** - * Parse a text and dispatch appropriate (key, value) to the event listeners for found information - * - * @param text the text to be parsed - * @param searchSource true if a source has to be searched in the text - * @param searchMultiIn true if MULTI IN indication has to be searched in the text - * @param searchZone true if a zone information has to be searched in the text - * @param searchRecord true if a record source has to be searched in the text - * @param searchRecordAfterSource true if a record source has to be searched in the text after the a found source - * @param searchDsp true if a DSP mode has to be searched in the text - * @param searchStereo true if a STEREO has to be considered in the search - * @param multipleInfo true if source and volume/mute are provided separately - */ - private void parseText(String text, boolean searchSource, boolean searchMultiIn, boolean searchZone, - boolean searchRecord, boolean searchRecordAfterSource, boolean searchDsp, boolean searchStereo, - boolean multipleInfo) { - String value = text.trim(); - String valueLowerCase = value.toLowerCase(); - if (searchRecord) { - dispatchKeyValue(KEY_RECORD_SEL, valueLowerCase.startsWith(KEY_HEX_RECORD) ? MSG_VALUE_ON : MSG_VALUE_OFF); - } - if (searchZone) { - if (valueLowerCase.startsWith(KEY1_HEX_ZONE2) || valueLowerCase.startsWith(KEY2_HEX_ZONE2)) { - dispatchKeyValue(KEY_ZONE, "2"); - } else if (valueLowerCase.startsWith(KEY_HEX_ZONE3)) { - dispatchKeyValue(KEY_ZONE, "3"); - } else if (valueLowerCase.startsWith(KEY_HEX_ZONE4)) { - dispatchKeyValue(KEY_ZONE, "4"); - } else { - dispatchKeyValue(KEY_ZONE, "1"); - } - } - if (valueLowerCase.startsWith(KEY1_HEX_VOLUME) || valueLowerCase.startsWith(KEY2_HEX_VOLUME)) { - value = extractNumber(value, - valueLowerCase.startsWith(KEY1_HEX_VOLUME) ? KEY1_HEX_VOLUME.length() : KEY2_HEX_VOLUME.length()); - dispatchKeyValue(KEY_VOLUME, value); - dispatchKeyValue(KEY_MUTE, MSG_VALUE_OFF); - } else if (valueLowerCase.startsWith(KEY_HEX_MUTE)) { - value = value.substring(KEY_HEX_MUTE.length()).trim(); - if (MSG_VALUE_ON.equalsIgnoreCase(value)) { - dispatchKeyValue(KEY_MUTE, MSG_VALUE_ON); - } else { - logger.debug("Invalid value {} for zone mute", value); - } - } else if (valueLowerCase.startsWith(KEY1_HEX_BASS) || valueLowerCase.startsWith(KEY2_HEX_BASS)) { - value = extractNumber(value, - valueLowerCase.startsWith(KEY1_HEX_BASS) ? KEY1_HEX_BASS.length() : KEY2_HEX_BASS.length()); - dispatchKeyValue(KEY_BASS, value); - } else if (valueLowerCase.startsWith(KEY1_HEX_TREBLE) || valueLowerCase.startsWith(KEY2_HEX_TREBLE)) { - value = extractNumber(value, - valueLowerCase.startsWith(KEY1_HEX_TREBLE) ? KEY1_HEX_TREBLE.length() : KEY2_HEX_TREBLE.length()); - dispatchKeyValue(KEY_TREBLE, value); - } else if (searchMultiIn && valueLowerCase.startsWith(KEY_HEX_MULTI_IN)) { - value = value.substring(KEY_HEX_MULTI_IN.length()).trim(); - if (MSG_VALUE_ON.equalsIgnoreCase(value)) { - try { - RotelSource source = model.getSourceFromName(RotelSource.CAT1_MULTI.getName()); - RotelCommand cmd = source.getCommand(); - if (cmd != null) { - String value2 = cmd.getAsciiCommandV2(); - if (value2 != null) { - dispatchKeyValue(KEY_SOURCE, value2); - } - } - } catch (RotelException e1) { - } - } else if (!MSG_VALUE_OFF.equalsIgnoreCase(value)) { - logger.debug("Invalid value {} for MULTI IN", value); - } - } else if (searchDsp && valueLowerCase.startsWith(KEY_HEX_BYPASS)) { - dispatchKeyValue(KEY_DSP_MODE, RotelDsp.CAT4_BYPASS.getFeedback()); - } else if (searchDsp && searchStereo && valueLowerCase.startsWith(KEY_HEX_STEREO)) { - dispatchKeyValue(KEY_DSP_MODE, RotelDsp.CAT4_NONE.getFeedback()); - } else if (searchDsp && (valueLowerCase.startsWith(KEY1_HEX_3CH) || valueLowerCase.startsWith(KEY2_HEX_3CH))) { - dispatchKeyValue(KEY_DSP_MODE, RotelDsp.CAT4_STEREO3.getFeedback()); - } else if (searchDsp && valueLowerCase.startsWith(KEY_HEX_5CH)) { - dispatchKeyValue(KEY_DSP_MODE, RotelDsp.CAT4_STEREO5.getFeedback()); - } else if (searchDsp && valueLowerCase.startsWith(KEY_HEX_7CH)) { - dispatchKeyValue(KEY_DSP_MODE, RotelDsp.CAT4_STEREO7.getFeedback()); - } else if (searchDsp - && (valueLowerCase.startsWith(KEY_HEX_MUSIC1) || valueLowerCase.startsWith(KEY_HEX_DSP1))) { - dispatchKeyValue(KEY_DSP_MODE, RotelDsp.CAT4_DSP1.getFeedback()); - } else if (searchDsp - && (valueLowerCase.startsWith(KEY_HEX_MUSIC2) || valueLowerCase.startsWith(KEY_HEX_DSP2))) { - dispatchKeyValue(KEY_DSP_MODE, RotelDsp.CAT4_DSP2.getFeedback()); - } else if (searchDsp - && (valueLowerCase.startsWith(KEY_HEX_MUSIC3) || valueLowerCase.startsWith(KEY_HEX_DSP3))) { - dispatchKeyValue(KEY_DSP_MODE, RotelDsp.CAT4_DSP3.getFeedback()); - } else if (searchDsp - && (valueLowerCase.startsWith(KEY_HEX_MUSIC4) || valueLowerCase.startsWith(KEY_HEX_DSP4))) { - dispatchKeyValue(KEY_DSP_MODE, RotelDsp.CAT4_DSP4.getFeedback()); - } else if (searchDsp && (valueLowerCase.startsWith(KEY1_HEX_PLII_CINEMA) - || valueLowerCase.startsWith(KEY2_HEX_PLII_CINEMA) || valueLowerCase.startsWith(KEY1_HEX_PLIIX_CINEMA) - || searchDsp && valueLowerCase.startsWith(KEY2_HEX_PLIIX_CINEMA))) { - dispatchKeyValue(KEY_DSP_MODE, RotelDsp.CAT2_PLII_CINEMA.getFeedback()); - } else if (searchDsp && (valueLowerCase.startsWith(KEY1_HEX_PLII_MUSIC) - || valueLowerCase.startsWith(KEY2_HEX_PLII_MUSIC) || valueLowerCase.startsWith(KEY1_HEX_PLIIX_MUSIC) - || valueLowerCase.startsWith(KEY2_HEX_PLIIX_MUSIC))) { - dispatchKeyValue(KEY_DSP_MODE, RotelDsp.CAT2_PLII_MUSIC.getFeedback()); - } else if (searchDsp && (valueLowerCase.startsWith(KEY1_HEX_PLII_GAME) - || valueLowerCase.startsWith(KEY2_HEX_PLII_GAME) || valueLowerCase.startsWith(KEY1_HEX_PLIIX_GAME) - || valueLowerCase.startsWith(KEY2_HEX_PLIIX_GAME))) { - dispatchKeyValue(KEY_DSP_MODE, RotelDsp.CAT2_PLII_GAME.getFeedback()); - } else if (searchDsp && valueLowerCase.startsWith(KEY_HEX_PLIIZ)) { - dispatchKeyValue(KEY_DSP_MODE, RotelDsp.CAT4_PLIIZ.getFeedback()); - } else if (searchDsp - && (valueLowerCase.startsWith(KEY1_HEX_PROLOGIC) || valueLowerCase.startsWith(KEY2_HEX_PROLOGIC))) { - dispatchKeyValue(KEY_DSP_MODE, RotelDsp.CAT4_PROLOGIC.getFeedback()); - } else if (searchDsp && (valueLowerCase.startsWith(KEY1_HEX_DTS_NEO6_CINEMA) - || valueLowerCase.startsWith(KEY2_HEX_DTS_NEO6_CINEMA))) { - dispatchKeyValue(KEY_DSP_MODE, RotelDsp.CAT4_NEO6_CINEMA.getFeedback()); - } else if (searchDsp && (valueLowerCase.startsWith(KEY1_HEX_DTS_NEO6_MUSIC) - || valueLowerCase.startsWith(KEY2_HEX_DTS_NEO6_MUSIC))) { - dispatchKeyValue(KEY_DSP_MODE, RotelDsp.CAT4_NEO6_MUSIC.getFeedback()); - } else if (searchDsp && valueLowerCase.startsWith(KEY_HEX_DTS_ES)) { - logger.debug("DTS-ES"); - dispatchKeyValue(KEY_DSP_MODE, RotelDsp.CAT4_NONE.getFeedback()); - } else if (searchDsp && valueLowerCase.startsWith(KEY_HEX_DTS_96)) { - logger.debug("DTS 96"); - dispatchKeyValue(KEY_DSP_MODE, RotelDsp.CAT4_NONE.getFeedback()); - } else if (searchDsp && valueLowerCase.startsWith(KEY_HEX_DTS)) { - logger.debug("DTS"); - dispatchKeyValue(KEY_DSP_MODE, RotelDsp.CAT4_NONE.getFeedback()); - } else if (searchDsp && valueLowerCase.startsWith(KEY_HEX_DD_EX)) { - logger.debug("DD-EX"); - dispatchKeyValue(KEY_DSP_MODE, RotelDsp.CAT4_NONE.getFeedback()); - } else if (searchDsp && valueLowerCase.startsWith(KEY_HEX_DD)) { - logger.debug("DD"); - dispatchKeyValue(KEY_DSP_MODE, RotelDsp.CAT4_NONE.getFeedback()); - } else if (searchDsp && valueLowerCase.startsWith(KEY_HEX_LPCM)) { - logger.debug("LPCM"); - dispatchKeyValue(KEY_DSP_MODE, RotelDsp.CAT4_NONE.getFeedback()); - } else if (searchDsp && valueLowerCase.startsWith(KEY_HEX_PCM)) { - logger.debug("PCM"); - dispatchKeyValue(KEY_DSP_MODE, RotelDsp.CAT4_NONE.getFeedback()); - } else if (searchDsp && valueLowerCase.startsWith(KEY_HEX_MPEG)) { - logger.debug("MPEG"); - dispatchKeyValue(KEY_DSP_MODE, RotelDsp.CAT4_NONE.getFeedback()); - } else if (searchZone - && (valueLowerCase.startsWith(KEY1_HEX_ZONE2) || valueLowerCase.startsWith(KEY2_HEX_ZONE2))) { - value = value.substring( - valueLowerCase.startsWith(KEY1_HEX_ZONE2) ? KEY1_HEX_ZONE2.length() : KEY2_HEX_ZONE2.length()); - parseZone2(value, multipleInfo); - } else if (searchZone && valueLowerCase.startsWith(KEY_HEX_ZONE3)) { - parseZone3(value.substring(KEY_HEX_ZONE3.length()), multipleInfo); - } else if (searchZone && valueLowerCase.startsWith(KEY_HEX_ZONE4)) { - parseZone4(value.substring(KEY_HEX_ZONE4.length()), multipleInfo); - } else if (searchRecord && valueLowerCase.startsWith(KEY_HEX_RECORD)) { - parseRecord(value.substring(KEY_HEX_RECORD.length())); - } else if (searchSource || searchRecordAfterSource) { - parseSourceAndRecord(value, searchSource, searchRecordAfterSource, multipleInfo); - } - } - - /** - * Parse a text to identify a source - * - * @param text the text to be parsed - * @param acceptFollowMain true if follow main has to be considered in the search - * - * @return the identified source or null if no source is identified in the text - */ - private @Nullable RotelSource parseSource(String text, boolean acceptFollowMain) { - String value = text.trim(); - RotelSource source = null; - if (!value.isEmpty()) { - if (acceptFollowMain && SOURCE.equalsIgnoreCase(value)) { - try { - source = model.getSourceFromName(RotelSource.CAT1_FOLLOW_MAIN.getName()); - } catch (RotelException e) { - } - } else { - for (RotelSource src : sourcesLabels.keySet()) { - String label = sourcesLabels.get(src); - if (label != null && value.startsWith(label)) { - if (source == null || sourcesLabels.get(source).length() < label.length()) { - source = src; - } - } - } - } - } - return source; - } - - private void parseSourceAndRecord(String text, boolean searchSource, boolean searchRecordAfterSource, - boolean multipleInfo) { - RotelSource source = parseSource(text, false); - if (source != null) { - if (searchSource) { - RotelCommand cmd = source.getCommand(); - if (cmd != null) { - String value2 = cmd.getAsciiCommandV2(); - if (value2 != null) { - dispatchKeyValue(KEY_SOURCE, value2); - if (!multipleInfo) { - dispatchKeyValue(KEY_MUTE, MSG_VALUE_OFF); - } - } - } - } - - if (searchRecordAfterSource) { - String value = text.substring(getSourceLabel(source).length()).trim(); - source = parseSource(value, true); - if (source != null) { - RotelCommand cmd = source.getRecordCommand(); - if (cmd != null) { - value = cmd.getAsciiCommandV2(); - if (value != null) { - dispatchKeyValue(KEY_RECORD, value); - } - } - } - } - } - } - - private String getSourceLabel(RotelSource source) { - String label = sourcesLabels.get(source); - return (label == null) ? source.getLabel() : label; - } - - private void parseRecord(String text) { - String value = text.trim(); - RotelSource source = parseSource(value, true); - if (source != null) { - RotelCommand cmd = source.getRecordCommand(); - if (cmd != null) { - value = cmd.getAsciiCommandV2(); - if (value != null) { - dispatchKeyValue(KEY_RECORD, value); - } - } - } else { - logger.debug("Invalid value {} for record source", value); - } - } - - private void parseZone2(String text, boolean multipleInfo) { - String value = text.trim(); - String valueLowerCase = value.toLowerCase(); - if (valueLowerCase.startsWith(KEY1_HEX_VOLUME) || valueLowerCase.startsWith(KEY2_HEX_VOLUME)) { - value = extractNumber(value, - valueLowerCase.startsWith(KEY1_HEX_VOLUME) ? KEY1_HEX_VOLUME.length() : KEY2_HEX_VOLUME.length()); - dispatchKeyValue(KEY_VOLUME_ZONE2, value); - dispatchKeyValue(KEY_MUTE_ZONE2, MSG_VALUE_OFF); - } else if (valueLowerCase.startsWith(KEY_HEX_MUTE)) { - value = value.substring(KEY_HEX_MUTE.length()).trim(); - if (MSG_VALUE_ON.equalsIgnoreCase(value)) { - dispatchKeyValue(KEY_MUTE_ZONE2, MSG_VALUE_ON); - } else { - logger.debug("Invalid value {} for zone mute", value); - } - } else if (!MSG_VALUE_OFF.equalsIgnoreCase(value)) { - RotelSource source = parseSource(value, true); - if (source != null) { - RotelCommand cmd = source.getZone2Command(); - if (cmd != null) { - value = cmd.getAsciiCommandV2(); - if (value != null) { - dispatchKeyValue(KEY_SOURCE_ZONE2, value); - if (!multipleInfo) { - dispatchKeyValue(KEY_MUTE_ZONE2, MSG_VALUE_OFF); - } - } - } - } else { - logger.debug("Invalid value {} for zone 2 source", value); - } - } - } - - private void parseZone3(String text, boolean multipleInfo) { - String value = text.trim(); - String valueLowerCase = value.toLowerCase(); - if (valueLowerCase.startsWith(KEY1_HEX_VOLUME) || valueLowerCase.startsWith(KEY2_HEX_VOLUME)) { - value = extractNumber(value, - valueLowerCase.startsWith(KEY1_HEX_VOLUME) ? KEY1_HEX_VOLUME.length() : KEY2_HEX_VOLUME.length()); - dispatchKeyValue(KEY_VOLUME_ZONE3, value); - dispatchKeyValue(KEY_MUTE_ZONE3, MSG_VALUE_OFF); - } else if (valueLowerCase.startsWith(KEY_HEX_MUTE)) { - value = value.substring(KEY_HEX_MUTE.length()).trim(); - if (MSG_VALUE_ON.equalsIgnoreCase(value)) { - dispatchKeyValue(KEY_MUTE_ZONE3, MSG_VALUE_ON); - } else { - logger.debug("Invalid value {} for zone mute", value); - } - } else if (!MSG_VALUE_OFF.equalsIgnoreCase(value)) { - RotelSource source = parseSource(value, true); - if (source != null) { - RotelCommand cmd = source.getZone3Command(); - if (cmd != null) { - value = cmd.getAsciiCommandV2(); - if (value != null) { - dispatchKeyValue(KEY_SOURCE_ZONE3, value); - if (!multipleInfo) { - dispatchKeyValue(KEY_MUTE_ZONE3, MSG_VALUE_OFF); - } - } - } - } else { - logger.debug("Invalid value {} for zone 3 source", value); - } - } - } - - private void parseZone4(String text, boolean multipleInfo) { - String value = text.trim(); - String valueLowerCase = value.toLowerCase(); - if (valueLowerCase.startsWith(KEY1_HEX_VOLUME) || valueLowerCase.startsWith(KEY2_HEX_VOLUME)) { - value = extractNumber(value, - valueLowerCase.startsWith(KEY1_HEX_VOLUME) ? KEY1_HEX_VOLUME.length() : KEY2_HEX_VOLUME.length()); - dispatchKeyValue(KEY_VOLUME_ZONE4, value); - dispatchKeyValue(KEY_MUTE_ZONE4, MSG_VALUE_OFF); - } else if (valueLowerCase.startsWith(KEY_HEX_MUTE)) { - value = value.substring(KEY_HEX_MUTE.length()).trim(); - if (MSG_VALUE_ON.equalsIgnoreCase(value)) { - dispatchKeyValue(KEY_MUTE_ZONE4, MSG_VALUE_ON); - } else { - logger.debug("Invalid value {} for zone mute", value); - } - } else if (!MSG_VALUE_OFF.equalsIgnoreCase(value)) { - RotelSource source = parseSource(value, true); - if (source != null) { - RotelCommand cmd = source.getZone4Command(); - if (cmd != null) { - value = cmd.getAsciiCommandV2(); - if (value != null) { - dispatchKeyValue(KEY_SOURCE_ZONE4, value); - if (!multipleInfo) { - dispatchKeyValue(KEY_MUTE_ZONE4, MSG_VALUE_OFF); - } - } - } - } else { - logger.debug("Invalid value {} for zone 4 source", value); - } - } - } - - /** - * Extract from a string a number - * - * @param value the string - * @param startIndex the index in the string at which the integer has to be extracted - * - * @return the number as a string with its sign and no blank between the sign and the digits - */ - private String extractNumber(String value, int startIndex) { - String result = value.substring(startIndex).trim(); - // Delete possible blank(s) between the sign and the number - if (result.startsWith("+") || result.startsWith("-")) { - result = result.substring(0, 1) + result.substring(1, result.length()).trim(); - } - return result; - } - - /** - * Suppress certain sequences of bytes from a message - * - * @param message the message as a table of bytes - * @param bytesSequences the table containing the sequence of bytes to be ignored - * - * @return the message without the unexpected sequence of bytes - */ - private byte[] filterMessage(byte[] message, byte[][] bytesSequences) { - if (bytesSequences.length == 0) { - return message; - } - byte[] filteredMsg = new byte[message.length]; - int srcIdx = 0; - int dstIdx = 0; - while (srcIdx < message.length) { - int ignoredLength = 0; - for (int i = 0; i < bytesSequences.length; i++) { - int size = bytesSequences[i].length; - if ((message.length - srcIdx) >= size) { - boolean match = true; - for (int j = 0; j < size; j++) { - if (message[srcIdx + j] != bytesSequences[i][j]) { - match = false; - break; - } - } - if (match) { - ignoredLength = size; - break; - } - } - } - if (ignoredLength > 0) { - srcIdx += ignoredLength; - } else { - filteredMsg[dstIdx++] = message[srcIdx++]; - } - } - return Arrays.copyOf(filteredMsg, dstIdx); - } - - /** - * Dispatch an event (key, value) to the event listeners - * - * @param key the key - * @param value the value - */ - private void dispatchKeyValue(String key, String value) { - RotelMessageEvent event = new RotelMessageEvent(this, key, value); - for (int i = 0; i < listeners.size(); i++) { - listeners.get(i).onNewMessageEvent(event); + logger.debug("Send command \"{}\" failed: {}", cmdName, e.getMessage()); + throw new RotelException("Send command \"" + cmdName + "\" failed", e); } + logger.debug("Send command \"{}\" succeeded", cmdName); } } diff --git a/bundles/org.openhab.binding.rotel/src/main/java/org/openhab/binding/rotel/internal/communication/RotelIpConnector.java b/bundles/org.openhab.binding.rotel/src/main/java/org/openhab/binding/rotel/internal/communication/RotelIpConnector.java index 6ca28af2d..2d53d0326 100644 --- a/bundles/org.openhab.binding.rotel/src/main/java/org/openhab/binding/rotel/internal/communication/RotelIpConnector.java +++ b/bundles/org.openhab.binding.rotel/src/main/java/org/openhab/binding/rotel/internal/communication/RotelIpConnector.java @@ -19,12 +19,11 @@ import java.io.InputStream; import java.io.InterruptedIOException; import java.net.Socket; import java.net.SocketTimeoutException; -import java.util.Map; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.rotel.internal.RotelException; -import org.openhab.binding.rotel.internal.RotelModel; +import org.openhab.binding.rotel.internal.protocol.RotelAbstractProtocolHandler; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -48,13 +47,12 @@ public class RotelIpConnector extends RotelConnector { * * @param address the IP address of the projector * @param port the TCP port to be used - * @param model the projector model in use - * @param protocol the protocol to be used + * @param protocolHandler the protocol handler * @param readerThreadName the name of thread to be created */ - public RotelIpConnector(String address, Integer port, RotelModel model, RotelProtocol protocol, - Map sourcesLabels, String readerThreadName) { - super(model, protocol, sourcesLabels, false, readerThreadName); + public RotelIpConnector(String address, Integer port, RotelAbstractProtocolHandler protocolHandler, + String readerThreadName) { + super(protocolHandler, false, readerThreadName); this.address = address; this.port = port; @@ -70,9 +68,7 @@ public class RotelIpConnector extends RotelConnector { dataOut = new DataOutputStream(clientSocket.getOutputStream()); dataIn = new DataInputStream(clientSocket.getInputStream()); - Thread thread = new RotelReaderThread(this, readerThreadName); - setReaderThread(thread); - thread.start(); + readerThread.start(); this.clientSocket = clientSocket; diff --git a/bundles/org.openhab.binding.rotel/src/main/java/org/openhab/binding/rotel/internal/communication/RotelReaderThread.java b/bundles/org.openhab.binding.rotel/src/main/java/org/openhab/binding/rotel/internal/communication/RotelReaderThread.java index e629c3994..52f5efab5 100644 --- a/bundles/org.openhab.binding.rotel/src/main/java/org/openhab/binding/rotel/internal/communication/RotelReaderThread.java +++ b/bundles/org.openhab.binding.rotel/src/main/java/org/openhab/binding/rotel/internal/communication/RotelReaderThread.java @@ -13,10 +13,10 @@ package org.openhab.binding.rotel.internal.communication; import java.io.InterruptedIOException; -import java.util.Arrays; import org.eclipse.jdt.annotation.NonNullByDefault; import org.openhab.binding.rotel.internal.RotelException; +import org.openhab.binding.rotel.internal.protocol.RotelAbstractProtocolHandler; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -28,79 +28,38 @@ import org.slf4j.LoggerFactory; @NonNullByDefault public class RotelReaderThread extends Thread { - private final Logger logger = LoggerFactory.getLogger(RotelReaderThread.class); - private static final int READ_BUFFER_SIZE = 16; - private RotelConnector connector; + private final Logger logger = LoggerFactory.getLogger(RotelReaderThread.class); + + private final RotelConnector connector; + private final RotelAbstractProtocolHandler protocolHandler; /** * Constructor * - * @param connector the object that should handle the received message + * @param connector the connector to read input data + * @param protocolHandler the protocol handler * @param threadName the name of the thread */ - public RotelReaderThread(RotelConnector connector, String threadName) { + public RotelReaderThread(RotelConnector connector, RotelAbstractProtocolHandler protocolHandler, + String threadName) { super(threadName); this.connector = connector; + this.protocolHandler = protocolHandler; } @Override public void run() { logger.debug("Data listener started"); - RotelProtocol protocol = connector.getProtocol(); - final int size = (protocol == RotelProtocol.HEX) - ? (6 + connector.getModel().getRespNbChars() + connector.getModel().getRespNbFlags()) - : 64; byte[] readDataBuffer = new byte[READ_BUFFER_SIZE]; - byte[] dataBuffer = new byte[size]; - boolean startCodeReached = false; - int count = 0; - int index = 0; - final char terminatingChar = (protocol == RotelProtocol.ASCII_V1) ? '!' : '$'; try { while (!Thread.interrupted()) { int len = connector.readInput(readDataBuffer); if (len > 0) { - for (int i = 0; i < len; i++) { - if (protocol == RotelProtocol.HEX) { - if (readDataBuffer[i] == RotelConnector.START) { - startCodeReached = true; - count = 0; - index = 0; - } - if (startCodeReached) { - if (index < size) { - dataBuffer[index++] = readDataBuffer[i]; - } - if (index == 2) { - count = readDataBuffer[i]; - } else if ((count > 0) && (index == (count + 3))) { - if ((readDataBuffer[i] & 0x000000FF) == 0x000000FD) { - count++; - } else { - byte[] msg = Arrays.copyOf(dataBuffer, index); - connector.handleIncomingMessage(msg); - startCodeReached = false; - } - } - } - } else { - if (index < size) { - dataBuffer[index++] = readDataBuffer[i]; - } - if (readDataBuffer[i] == terminatingChar) { - if (index >= size) { - dataBuffer[index - 1] = (byte) terminatingChar; - } - byte[] msg = Arrays.copyOf(dataBuffer, index); - connector.handleIncomingMessage(msg); - index = 0; - } - } - } + protocolHandler.handleIncomingData(readDataBuffer, len); } } } catch (InterruptedIOException e) { @@ -108,7 +67,7 @@ public class RotelReaderThread extends Thread { logger.debug("Interrupted via InterruptedIOException"); } catch (RotelException e) { logger.debug("Reading failed: {}", e.getMessage(), e); - connector.handleIncomingMessage(RotelConnector.READ_ERROR); + protocolHandler.handleInIncomingError(); } logger.debug("Data listener stopped"); diff --git a/bundles/org.openhab.binding.rotel/src/main/java/org/openhab/binding/rotel/internal/communication/RotelSerialConnector.java b/bundles/org.openhab.binding.rotel/src/main/java/org/openhab/binding/rotel/internal/communication/RotelSerialConnector.java index bf9114bb0..a1abeb8f1 100644 --- a/bundles/org.openhab.binding.rotel/src/main/java/org/openhab/binding/rotel/internal/communication/RotelSerialConnector.java +++ b/bundles/org.openhab.binding.rotel/src/main/java/org/openhab/binding/rotel/internal/communication/RotelSerialConnector.java @@ -15,12 +15,11 @@ package org.openhab.binding.rotel.internal.communication; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import java.util.Map; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.rotel.internal.RotelException; -import org.openhab.binding.rotel.internal.RotelModel; +import org.openhab.binding.rotel.internal.protocol.RotelAbstractProtocolHandler; import org.openhab.core.io.transport.serial.PortInUseException; import org.openhab.core.io.transport.serial.SerialPort; import org.openhab.core.io.transport.serial.SerialPortIdentifier; @@ -42,6 +41,8 @@ public class RotelSerialConnector extends RotelConnector { private String serialPortName; private SerialPortManager serialPortManager; + private int baudRate; + private @Nullable SerialPort serialPort; /** @@ -49,14 +50,15 @@ public class RotelSerialConnector extends RotelConnector { * * @param serialPortManager the serial port manager * @param serialPortName the serial port name to be used - * @param model the projector model in use - * @param protocol the protocol to be used + * @param baudRate the baud rate to be used + * @param protocolHandler the protocol handler * @param readerThreadName the name of thread to be created */ - public RotelSerialConnector(SerialPortManager serialPortManager, String serialPortName, RotelModel model, - RotelProtocol protocol, Map sourcesLabels, String readerThreadName) { - super(model, protocol, sourcesLabels, false, readerThreadName); + public RotelSerialConnector(SerialPortManager serialPortManager, String serialPortName, int baudRate, + RotelAbstractProtocolHandler protocolHandler, String readerThreadName) { + super(protocolHandler, false, readerThreadName); + this.baudRate = baudRate; this.serialPortManager = serialPortManager; this.serialPortName = serialPortName; } @@ -73,7 +75,7 @@ public class RotelSerialConnector extends RotelConnector { SerialPort commPort = portIdentifier.open(this.getClass().getName(), 2000); - commPort.setSerialPortParams(getModel().getBaudRate(), SerialPort.DATABITS_8, SerialPort.STOPBITS_1, + commPort.setSerialPortParams(baudRate, SerialPort.DATABITS_8, SerialPort.STOPBITS_1, SerialPort.PARITY_NONE); commPort.enableReceiveThreshold(1); commPort.enableReceiveTimeout(100); @@ -92,9 +94,7 @@ public class RotelSerialConnector extends RotelConnector { } } - Thread thread = new RotelReaderThread(this, readerThreadName); - setReaderThread(thread); - thread.start(); + readerThread.start(); this.serialPort = commPort; this.dataIn = dataIn; diff --git a/bundles/org.openhab.binding.rotel/src/main/java/org/openhab/binding/rotel/internal/communication/RotelSimuConnector.java b/bundles/org.openhab.binding.rotel/src/main/java/org/openhab/binding/rotel/internal/communication/RotelSimuConnector.java index 4f3e543f5..2062aceed 100644 --- a/bundles/org.openhab.binding.rotel/src/main/java/org/openhab/binding/rotel/internal/communication/RotelSimuConnector.java +++ b/bundles/org.openhab.binding.rotel/src/main/java/org/openhab/binding/rotel/internal/communication/RotelSimuConnector.java @@ -12,6 +12,9 @@ */ package org.openhab.binding.rotel.internal.communication; +import static org.openhab.binding.rotel.internal.RotelBindingConstants.*; +import static org.openhab.binding.rotel.internal.protocol.hex.RotelHexProtocolHandler.START; + import java.io.InterruptedIOException; import java.nio.charset.StandardCharsets; import java.util.Arrays; @@ -23,6 +26,9 @@ import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.rotel.internal.RotelException; import org.openhab.binding.rotel.internal.RotelModel; import org.openhab.binding.rotel.internal.RotelPlayStatus; +import org.openhab.binding.rotel.internal.protocol.RotelAbstractProtocolHandler; +import org.openhab.binding.rotel.internal.protocol.RotelProtocol; +import org.openhab.binding.rotel.internal.protocol.hex.RotelHexProtocolHandler; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -34,9 +40,13 @@ import org.slf4j.LoggerFactory; @NonNullByDefault public class RotelSimuConnector extends RotelConnector { + private static final int STEP_TONE_LEVEL = 1; + private final Logger logger = LoggerFactory.getLogger(RotelSimuConnector.class); - private static final int STEP_TONE_LEVEL = 1; + private final RotelModel model; + private final RotelProtocol protocol; + private final Map sourcesLabels; private Object lock = new Object(); @@ -80,12 +90,16 @@ public class RotelSimuConnector extends RotelConnector { * Constructor * * @param model the projector model in use - * @param protocol the protocol to be used + * @param protocolHandler the protocol handler + * @param sourcesLabels the custom labels for sources * @param readerThreadName the name of thread to be created */ - public RotelSimuConnector(RotelModel model, RotelProtocol protocol, Map sourcesLabels, - String readerThreadName) { - super(model, protocol, sourcesLabels, true, readerThreadName); + public RotelSimuConnector(RotelModel model, RotelAbstractProtocolHandler protocolHandler, + Map sourcesLabels, String readerThreadName) { + super(protocolHandler, true, readerThreadName); + this.model = model; + this.protocol = protocolHandler.getProtocol(); + this.sourcesLabels = sourcesLabels; this.minVolume = 0; this.maxVolume = model.hasVolumeControl() ? model.getVolumeMax() : 0; this.maxToneLevel = model.hasToneControl() ? model.getToneLevelMax() : 0; @@ -95,9 +109,7 @@ public class RotelSimuConnector extends RotelConnector { @Override public synchronized void open() throws RotelException { logger.debug("Opening simulated connection"); - Thread thread = new RotelReaderThread(this, readerThreadName); - setReaderThread(thread); - thread.start(); + readerThread.start(); setConnected(true); logger.debug("Simulated connection opened"); } @@ -132,23 +144,13 @@ public class RotelSimuConnector extends RotelConnector { return 0; } - @Override - public void sendCommand(RotelCommand cmd, @Nullable Integer value) throws RotelException { - super.sendCommand(cmd, value); - if ((getProtocol() == RotelProtocol.HEX && cmd.getHexType() != 0) - || (getProtocol() == RotelProtocol.ASCII_V1 && cmd.getAsciiCommandV1() != null) - || (getProtocol() == RotelProtocol.ASCII_V2 && cmd.getAsciiCommandV2() != null)) { - buildFeedbackMessage(cmd, value); - } - } - /** * Built the simulated feedback message for a sent command * * @param cmd the sent command * @param value the integer value considered in the sent command for volume, bass or treble adjustment */ - private void buildFeedbackMessage(RotelCommand cmd, @Nullable Integer value) { + public void buildFeedbackMessage(RotelCommand cmd, @Nullable Integer value) { String text = buildSourceLine1Response(); String textLine1Left = buildSourceLine1LeftResponse(); String textLine1Right = buildVolumeLine1RightResponse(); @@ -180,14 +182,14 @@ public class RotelSimuConnector extends RotelConnector { break; case ZONE2_POWER_OFF: powerZone2 = false; - text = textLine2 = buildZonePowerResponse(getModel().getNbAdditionalZones() > 1 ? "ZONE2" : "ZONE", + text = textLine2 = buildZonePowerResponse(model.getNbAdditionalZones() > 1 ? "ZONE2" : "ZONE", powerZone2, sourceZone2); showZone = 2; resetZone = false; break; case ZONE2_POWER_ON: powerZone2 = true; - text = textLine2 = buildZonePowerResponse(getModel().getNbAdditionalZones() > 1 ? "ZONE2" : "ZONE", + text = textLine2 = buildZonePowerResponse(model.getNbAdditionalZones() > 1 ? "ZONE2" : "ZONE", powerZone2, sourceZone2); showZone = 2; resetZone = false; @@ -217,9 +219,9 @@ public class RotelSimuConnector extends RotelConnector { resetZone = false; break; case RECORD_FONCTION_SELECT: - if (getModel().getNbAdditionalZones() >= 1 && getModel().getZoneSelectCmd() == cmd) { + if (model.getNbAdditionalZones() >= 1 && model.getZoneSelectCmd() == cmd) { showZone++; - if (showZone > getModel().getNbAdditionalZones()) { + if (showZone > model.getNbAdditionalZones()) { showZone = 1; if (!power) { showZone++; @@ -234,7 +236,7 @@ public class RotelSimuConnector extends RotelConnector { textLine2 = buildRecordResponse(); } else if (showZone == 2) { selectingRecord = false; - text = textLine2 = buildZonePowerResponse(getModel().getNbAdditionalZones() > 1 ? "ZONE2" : "ZONE", + text = textLine2 = buildZonePowerResponse(model.getNbAdditionalZones() > 1 ? "ZONE2" : "ZONE", powerZone2, sourceZone2); } else if (showZone == 3) { selectingRecord = false; @@ -246,12 +248,12 @@ public class RotelSimuConnector extends RotelConnector { resetZone = false; break; case ZONE_SELECT: - if (getModel().getNbAdditionalZones() == 0 - || (getModel().getNbAdditionalZones() > 1 && getModel().getZoneSelectCmd() == cmd) - || (showZone == 1 && getModel().getZoneSelectCmd() != cmd)) { + if (model.getNbAdditionalZones() == 0 + || (model.getNbAdditionalZones() > 1 && model.getZoneSelectCmd() == cmd) + || (showZone == 1 && model.getZoneSelectCmd() != cmd)) { accepted = false; } else { - if (getModel().getZoneSelectCmd() == cmd) { + if (model.getZoneSelectCmd() == cmd) { if (!power && !powerZone2) { showZone = 2; powerZone2 = true; @@ -270,8 +272,8 @@ public class RotelSimuConnector extends RotelConnector { } } if (showZone == 2) { - text = textLine2 = buildZonePowerResponse( - getModel().getNbAdditionalZones() > 1 ? "ZONE2" : "ZONE", powerZone2, sourceZone2); + text = textLine2 = buildZonePowerResponse(model.getNbAdditionalZones() > 1 ? "ZONE2" : "ZONE", + powerZone2, sourceZone2); } else if (showZone == 3) { text = textLine2 = buildZonePowerResponse("ZONE3", powerZone3, sourceZone3); } else if (showZone == 4) { @@ -291,54 +293,54 @@ public class RotelSimuConnector extends RotelConnector { if (volumeZone2 < maxVolume) { volumeZone2++; } - text = textLine2 = buildZoneVolumeResponse(getModel().getNbAdditionalZones() > 1 ? "ZONE2" : "ZONE", + text = textLine2 = buildZoneVolumeResponse(model.getNbAdditionalZones() > 1 ? "ZONE2" : "ZONE", muteZone2, volumeZone2); break; case ZONE2_VOLUME_DOWN: if (volumeZone2 > minVolume) { volumeZone2--; } - text = textLine2 = buildZoneVolumeResponse(getModel().getNbAdditionalZones() > 1 ? "ZONE2" : "ZONE", + text = textLine2 = buildZoneVolumeResponse(model.getNbAdditionalZones() > 1 ? "ZONE2" : "ZONE", muteZone2, volumeZone2); break; case ZONE2_VOLUME_SET: if (value != null) { volumeZone2 = value; } - text = textLine2 = buildZoneVolumeResponse(getModel().getNbAdditionalZones() > 1 ? "ZONE2" : "ZONE", + text = textLine2 = buildZoneVolumeResponse(model.getNbAdditionalZones() > 1 ? "ZONE2" : "ZONE", muteZone2, volumeZone2); break; case VOLUME_UP: - if (!getModel().hasZone2Commands() && getModel().getNbAdditionalZones() >= 1 && showZone == 2) { + if (!model.hasZone2Commands() && model.getNbAdditionalZones() >= 1 && showZone == 2) { if (volumeZone2 < maxVolume) { volumeZone2++; } - text = textLine2 = buildZoneVolumeResponse( - getModel().getNbAdditionalZones() > 1 ? "ZONE2" : "ZONE", muteZone2, volumeZone2); + text = textLine2 = buildZoneVolumeResponse(model.getNbAdditionalZones() > 1 ? "ZONE2" : "ZONE", + muteZone2, volumeZone2); resetZone = false; } else { accepted = false; } break; case VOLUME_DOWN: - if (!getModel().hasZone2Commands() && getModel().getNbAdditionalZones() >= 1 && showZone == 2) { + if (!model.hasZone2Commands() && model.getNbAdditionalZones() >= 1 && showZone == 2) { if (volumeZone2 > minVolume) { volumeZone2--; } - text = textLine2 = buildZoneVolumeResponse( - getModel().getNbAdditionalZones() > 1 ? "ZONE2" : "ZONE", muteZone2, volumeZone2); + text = textLine2 = buildZoneVolumeResponse(model.getNbAdditionalZones() > 1 ? "ZONE2" : "ZONE", + muteZone2, volumeZone2); resetZone = false; } else { accepted = false; } break; case VOLUME_SET: - if (!getModel().hasZone2Commands() && getModel().getNbAdditionalZones() >= 1 && showZone == 2) { + if (!model.hasZone2Commands() && model.getNbAdditionalZones() >= 1 && showZone == 2) { if (value != null) { volumeZone2 = value; } - text = textLine2 = buildZoneVolumeResponse( - getModel().getNbAdditionalZones() > 1 ? "ZONE2" : "ZONE", muteZone2, volumeZone2); + text = textLine2 = buildZoneVolumeResponse(model.getNbAdditionalZones() > 1 ? "ZONE2" : "ZONE", + muteZone2, volumeZone2); resetZone = false; } else { accepted = false; @@ -346,17 +348,17 @@ public class RotelSimuConnector extends RotelConnector { break; case ZONE2_MUTE_TOGGLE: muteZone2 = !muteZone2; - text = textLine2 = buildZoneVolumeResponse(getModel().getNbAdditionalZones() > 1 ? "ZONE2" : "ZONE", + text = textLine2 = buildZoneVolumeResponse(model.getNbAdditionalZones() > 1 ? "ZONE2" : "ZONE", muteZone2, volumeZone2); break; case ZONE2_MUTE_ON: muteZone2 = true; - text = textLine2 = buildZoneVolumeResponse(getModel().getNbAdditionalZones() > 1 ? "ZONE2" : "ZONE", + text = textLine2 = buildZoneVolumeResponse(model.getNbAdditionalZones() > 1 ? "ZONE2" : "ZONE", muteZone2, volumeZone2); break; case ZONE2_MUTE_OFF: muteZone2 = false; - text = textLine2 = buildZoneVolumeResponse(getModel().getNbAdditionalZones() > 1 ? "ZONE2" : "ZONE", + text = textLine2 = buildZoneVolumeResponse(model.getNbAdditionalZones() > 1 ? "ZONE2" : "ZONE", muteZone2, volumeZone2); break; default: @@ -365,9 +367,9 @@ public class RotelSimuConnector extends RotelConnector { } if (!accepted) { try { - sourceZone2 = getModel().getZone2SourceFromCommand(cmd); + sourceZone2 = model.getZone2SourceFromCommand(cmd); powerZone2 = true; - text = textLine2 = buildZonePowerResponse(getModel().getNbAdditionalZones() > 1 ? "ZONE2" : "ZONE", + text = textLine2 = buildZonePowerResponse(model.getNbAdditionalZones() > 1 ? "ZONE2" : "ZONE", powerZone2, sourceZone2); muteZone2 = false; accepted = true; @@ -376,12 +378,11 @@ public class RotelSimuConnector extends RotelConnector { } catch (RotelException e) { } } - if (!accepted && !getModel().hasZone2Commands() && getModel().getNbAdditionalZones() >= 1 - && showZone == 2) { + if (!accepted && !model.hasZone2Commands() && model.getNbAdditionalZones() >= 1 && showZone == 2) { try { - sourceZone2 = getModel().getSourceFromCommand(cmd); + sourceZone2 = model.getSourceFromCommand(cmd); powerZone2 = true; - text = textLine2 = buildZonePowerResponse(getModel().getNbAdditionalZones() > 1 ? "ZONE2" : "ZONE", + text = textLine2 = buildZonePowerResponse(model.getNbAdditionalZones() > 1 ? "ZONE2" : "ZONE", powerZone2, sourceZone2); muteZone2 = false; accepted = true; @@ -429,7 +430,7 @@ public class RotelSimuConnector extends RotelConnector { } if (!accepted) { try { - sourceZone3 = getModel().getZone3SourceFromCommand(cmd); + sourceZone3 = model.getZone3SourceFromCommand(cmd); powerZone3 = true; text = textLine2 = buildZonePowerResponse("ZONE3", powerZone3, sourceZone3); muteZone3 = false; @@ -479,7 +480,7 @@ public class RotelSimuConnector extends RotelConnector { } if (!accepted) { try { - sourceZone4 = getModel().getZone4SourceFromCommand(cmd); + sourceZone4 = model.getZone4SourceFromCommand(cmd); powerZone4 = true; text = textLine2 = buildZonePowerResponse("ZONE4", powerZone4, sourceZone4); muteZone4 = false; @@ -495,11 +496,11 @@ public class RotelSimuConnector extends RotelConnector { switch (cmd) { case UPDATE_AUTO: textAscii = buildAsciiResponse( - getProtocol() == RotelProtocol.ASCII_V1 ? KEY_DISPLAY_UPDATE : KEY_UPDATE_MODE, "AUTO"); + protocol == RotelProtocol.ASCII_V1 ? KEY_DISPLAY_UPDATE : KEY_UPDATE_MODE, AUTO); break; case UPDATE_MANUAL: textAscii = buildAsciiResponse( - getProtocol() == RotelProtocol.ASCII_V1 ? KEY_DISPLAY_UPDATE : KEY_UPDATE_MODE, "MANUAL"); + protocol == RotelProtocol.ASCII_V1 ? KEY_DISPLAY_UPDATE : KEY_UPDATE_MODE, MANUAL); break; case VOLUME_GET_MIN: textAscii = buildAsciiResponse(KEY_VOLUME_MIN, minVolume); @@ -668,7 +669,7 @@ public class RotelSimuConnector extends RotelConnector { multiinput = !multiinput; text = "MULTI IN " + (multiinput ? "ON" : "OFF"); try { - source = getModel().getSourceFromCommand(cmd); + source = model.getSourceFromCommand(cmd); textLine1Left = buildSourceLine1LeftResponse(); textAscii = buildSourceAsciiResponse(); mute = false; @@ -795,7 +796,7 @@ public class RotelSimuConnector extends RotelConnector { } if (!accepted) { try { - source = getModel().getMainZoneSourceFromCommand(cmd); + source = model.getMainZoneSourceFromCommand(cmd); text = buildSourceLine1Response(); textLine1Left = buildSourceLine1LeftResponse(); textAscii = buildSourceAsciiResponse(); @@ -805,10 +806,10 @@ public class RotelSimuConnector extends RotelConnector { } if (!accepted) { try { - if (selectingRecord && !getModel().hasOtherThanPrimaryCommands()) { - recordSource = getModel().getSourceFromCommand(cmd); + if (selectingRecord && !model.hasOtherThanPrimaryCommands()) { + recordSource = model.getSourceFromCommand(cmd); } else { - source = getModel().getSourceFromCommand(cmd); + source = model.getSourceFromCommand(cmd); } text = buildSourceLine1Response(); textLine1Left = buildSourceLine1LeftResponse(); @@ -820,7 +821,7 @@ public class RotelSimuConnector extends RotelConnector { } if (!accepted) { try { - recordSource = getModel().getRecordSourceFromCommand(cmd); + recordSource = model.getRecordSourceFromCommand(cmd); text = buildSourceLine1Response(); textLine2 = buildRecordResponse(); accepted = true; @@ -840,7 +841,7 @@ public class RotelSimuConnector extends RotelConnector { showZone = 0; } - if (getModel().getRespNbChars() == 42) { + if (model.getRespNbChars() == 42) { while (textLine1Left.length() < 14) { textLine1Left += " "; } @@ -853,44 +854,44 @@ public class RotelSimuConnector extends RotelConnector { text = textLine1Left + textLine1Right + textLine2; } - if (getProtocol() == RotelProtocol.HEX) { - byte[] chars = Arrays.copyOf(text.getBytes(StandardCharsets.US_ASCII), getModel().getRespNbChars()); - byte[] flags = new byte[getModel().getRespNbFlags()]; + if (protocol == RotelProtocol.HEX) { + byte[] chars = Arrays.copyOf(text.getBytes(StandardCharsets.US_ASCII), model.getRespNbChars()); + byte[] flags = new byte[model.getRespNbFlags()]; try { - getModel().setMultiInput(flags, multiinput); + model.setMultiInput(flags, multiinput); } catch (RotelException e) { } try { - getModel().setZone2(flags, powerZone2); + model.setZone2(flags, powerZone2); } catch (RotelException e) { } try { - getModel().setZone3(flags, powerZone3); + model.setZone3(flags, powerZone3); } catch (RotelException e) { } try { - getModel().setZone4(flags, powerZone4); + model.setZone4(flags, powerZone4); } catch (RotelException e) { } - int size = 6 + getModel().getRespNbChars() + getModel().getRespNbFlags(); + int size = 6 + model.getRespNbChars() + model.getRespNbFlags(); byte[] dataBuffer = new byte[size]; int idx = 0; dataBuffer[idx++] = START; dataBuffer[idx++] = (byte) (size - 4); - dataBuffer[idx++] = getModel().getDeviceId(); + dataBuffer[idx++] = model.getDeviceId(); dataBuffer[idx++] = STANDARD_RESPONSE; - if (getModel().isCharsBeforeFlags()) { - System.arraycopy(chars, 0, dataBuffer, idx, getModel().getRespNbChars()); - idx += getModel().getRespNbChars(); - System.arraycopy(flags, 0, dataBuffer, idx, getModel().getRespNbFlags()); - idx += getModel().getRespNbFlags(); + if (model.isCharsBeforeFlags()) { + System.arraycopy(chars, 0, dataBuffer, idx, model.getRespNbChars()); + idx += model.getRespNbChars(); + System.arraycopy(flags, 0, dataBuffer, idx, model.getRespNbFlags()); + idx += model.getRespNbFlags(); } else { - System.arraycopy(flags, 0, dataBuffer, idx, getModel().getRespNbFlags()); - idx += getModel().getRespNbFlags(); - System.arraycopy(chars, 0, dataBuffer, idx, getModel().getRespNbChars()); - idx += getModel().getRespNbChars(); + System.arraycopy(flags, 0, dataBuffer, idx, model.getRespNbFlags()); + idx += model.getRespNbFlags(); + System.arraycopy(chars, 0, dataBuffer, idx, model.getRespNbChars()); + idx += model.getRespNbChars(); } - byte checksum = computeCheckSum(dataBuffer, idx - 1); + byte checksum = RotelHexProtocolHandler.computeCheckSum(dataBuffer, idx - 1); if ((checksum & 0x000000FF) == 0x000000FD) { dataBuffer[idx++] = (byte) 0xFD; dataBuffer[idx++] = 0; @@ -905,7 +906,7 @@ public class RotelSimuConnector extends RotelConnector { idxInFeedbackMsg = 0; } } else { - String command = textAscii + (getProtocol() == RotelProtocol.ASCII_V1 ? "!" : "$"); + String command = textAscii + (protocol == RotelProtocol.ASCII_V1 ? "!" : "$"); synchronized (lock) { feedbackMsg = command.getBytes(StandardCharsets.US_ASCII); idxInFeedbackMsg = 0; @@ -978,8 +979,7 @@ public class RotelSimuConnector extends RotelConnector { status = STOP; break; } - return buildAsciiResponse(getProtocol() == RotelProtocol.ASCII_V1 ? KEY1_PLAY_STATUS : KEY2_PLAY_STATUS, - status); + return buildAsciiResponse(protocol == RotelProtocol.ASCII_V1 ? KEY1_PLAY_STATUS : KEY2_PLAY_STATUS, status); } private String buildTrackAsciiResponse() { diff --git a/bundles/org.openhab.binding.rotel/src/main/java/org/openhab/binding/rotel/internal/handler/RotelHandler.java b/bundles/org.openhab.binding.rotel/src/main/java/org/openhab/binding/rotel/internal/handler/RotelHandler.java index bb64cb328..4a383e7f0 100644 --- a/bundles/org.openhab.binding.rotel/src/main/java/org/openhab/binding/rotel/internal/handler/RotelHandler.java +++ b/bundles/org.openhab.binding.rotel/src/main/java/org/openhab/binding/rotel/internal/handler/RotelHandler.java @@ -34,13 +34,17 @@ import org.openhab.binding.rotel.internal.communication.RotelCommand; import org.openhab.binding.rotel.internal.communication.RotelConnector; import org.openhab.binding.rotel.internal.communication.RotelDsp; import org.openhab.binding.rotel.internal.communication.RotelIpConnector; -import org.openhab.binding.rotel.internal.communication.RotelMessageEvent; -import org.openhab.binding.rotel.internal.communication.RotelMessageEventListener; -import org.openhab.binding.rotel.internal.communication.RotelProtocol; import org.openhab.binding.rotel.internal.communication.RotelSerialConnector; import org.openhab.binding.rotel.internal.communication.RotelSimuConnector; import org.openhab.binding.rotel.internal.communication.RotelSource; import org.openhab.binding.rotel.internal.configuration.RotelThingConfiguration; +import org.openhab.binding.rotel.internal.protocol.RotelAbstractProtocolHandler; +import org.openhab.binding.rotel.internal.protocol.RotelMessageEvent; +import org.openhab.binding.rotel.internal.protocol.RotelMessageEventListener; +import org.openhab.binding.rotel.internal.protocol.RotelProtocol; +import org.openhab.binding.rotel.internal.protocol.ascii.RotelAsciiV1ProtocolHandler; +import org.openhab.binding.rotel.internal.protocol.ascii.RotelAsciiV2ProtocolHandler; +import org.openhab.binding.rotel.internal.protocol.hex.RotelHexProtocolHandler; import org.openhab.core.io.transport.serial.SerialPortManager; import org.openhab.core.library.types.DecimalType; import org.openhab.core.library.types.IncreaseDecreaseType; @@ -87,8 +91,10 @@ public class RotelHandler extends BaseThingHandler implements RotelMessageEventL private RotelStateDescriptionOptionProvider stateDescriptionProvider; private SerialPortManager serialPortManager; - private RotelConnector connector = new RotelSimuConnector(DEFAULT_MODEL, RotelProtocol.HEX, new HashMap<>(), - "OH-binding-rotel"); + private RotelModel model; + private RotelProtocol protocol; + private RotelAbstractProtocolHandler protocolHandler; + private RotelConnector connector; private int minVolume; private int maxVolume; @@ -143,193 +149,196 @@ public class RotelHandler extends BaseThingHandler implements RotelMessageEventL super(thing); this.stateDescriptionProvider = stateDescriptionProvider; this.serialPortManager = serialPortManager; + this.model = DEFAULT_MODEL; + this.protocolHandler = new RotelHexProtocolHandler(model, Map.of()); + this.protocol = protocolHandler.getProtocol(); + this.connector = new RotelSimuConnector(model, protocolHandler, new HashMap<>(), "OH-binding-rotel"); } @Override public void initialize() { logger.debug("Start initializing handler for thing {}", getThing().getUID()); - RotelModel rotelModel; switch (getThing().getThingTypeUID().getId()) { case THING_TYPE_ID_RSP1066: - rotelModel = RotelModel.RSP1066; + model = RotelModel.RSP1066; break; case THING_TYPE_ID_RSP1068: - rotelModel = RotelModel.RSP1068; + model = RotelModel.RSP1068; break; case THING_TYPE_ID_RSP1069: - rotelModel = RotelModel.RSP1069; + model = RotelModel.RSP1069; break; case THING_TYPE_ID_RSP1098: - rotelModel = RotelModel.RSP1098; + model = RotelModel.RSP1098; break; case THING_TYPE_ID_RSP1570: - rotelModel = RotelModel.RSP1570; + model = RotelModel.RSP1570; break; case THING_TYPE_ID_RSP1572: - rotelModel = RotelModel.RSP1572; + model = RotelModel.RSP1572; break; case THING_TYPE_ID_RSX1055: - rotelModel = RotelModel.RSX1055; + model = RotelModel.RSX1055; break; case THING_TYPE_ID_RSX1056: - rotelModel = RotelModel.RSX1056; + model = RotelModel.RSX1056; break; case THING_TYPE_ID_RSX1057: - rotelModel = RotelModel.RSX1057; + model = RotelModel.RSX1057; break; case THING_TYPE_ID_RSX1058: - rotelModel = RotelModel.RSX1058; + model = RotelModel.RSX1058; break; case THING_TYPE_ID_RSX1065: - rotelModel = RotelModel.RSX1065; + model = RotelModel.RSX1065; break; case THING_TYPE_ID_RSX1067: - rotelModel = RotelModel.RSX1067; + model = RotelModel.RSX1067; break; case THING_TYPE_ID_RSX1550: - rotelModel = RotelModel.RSX1550; + model = RotelModel.RSX1550; break; case THING_TYPE_ID_RSX1560: - rotelModel = RotelModel.RSX1560; + model = RotelModel.RSX1560; break; case THING_TYPE_ID_RSX1562: - rotelModel = RotelModel.RSX1562; + model = RotelModel.RSX1562; break; case THING_TYPE_ID_A11: - rotelModel = RotelModel.A11; + model = RotelModel.A11; break; case THING_TYPE_ID_A12: - rotelModel = RotelModel.A12; + model = RotelModel.A12; break; case THING_TYPE_ID_A14: - rotelModel = RotelModel.A14; + model = RotelModel.A14; break; case THING_TYPE_ID_CD11: - rotelModel = RotelModel.CD11; + model = RotelModel.CD11; break; case THING_TYPE_ID_CD14: - rotelModel = RotelModel.CD14; + model = RotelModel.CD14; break; case THING_TYPE_ID_RA11: - rotelModel = RotelModel.RA11; + model = RotelModel.RA11; break; case THING_TYPE_ID_RA12: - rotelModel = RotelModel.RA12; + model = RotelModel.RA12; break; case THING_TYPE_ID_RA1570: - rotelModel = RotelModel.RA1570; + model = RotelModel.RA1570; break; case THING_TYPE_ID_RA1572: - rotelModel = RotelModel.RA1572; + model = RotelModel.RA1572; break; case THING_TYPE_ID_RA1592: - rotelModel = RotelModel.RA1592; + model = RotelModel.RA1592; break; case THING_TYPE_ID_RAP1580: - rotelModel = RotelModel.RAP1580; + model = RotelModel.RAP1580; break; case THING_TYPE_ID_RC1570: - rotelModel = RotelModel.RC1570; + model = RotelModel.RC1570; break; case THING_TYPE_ID_RC1572: - rotelModel = RotelModel.RC1572; + model = RotelModel.RC1572; break; case THING_TYPE_ID_RC1590: - rotelModel = RotelModel.RC1590; + model = RotelModel.RC1590; break; case THING_TYPE_ID_RCD1570: - rotelModel = RotelModel.RCD1570; + model = RotelModel.RCD1570; break; case THING_TYPE_ID_RCD1572: - rotelModel = RotelModel.RCD1572; + model = RotelModel.RCD1572; break; case THING_TYPE_ID_RCX1500: - rotelModel = RotelModel.RCX1500; + model = RotelModel.RCX1500; break; case THING_TYPE_ID_RDD1580: - rotelModel = RotelModel.RDD1580; + model = RotelModel.RDD1580; break; case THING_TYPE_ID_RDG1520: case THING_TYPE_ID_RT09: - rotelModel = RotelModel.RDG1520; + model = RotelModel.RDG1520; break; case THING_TYPE_ID_RSP1576: - rotelModel = RotelModel.RSP1576; + model = RotelModel.RSP1576; break; case THING_TYPE_ID_RSP1582: - rotelModel = RotelModel.RSP1582; + model = RotelModel.RSP1582; break; case THING_TYPE_ID_RT11: - rotelModel = RotelModel.RT11; + model = RotelModel.RT11; break; case THING_TYPE_ID_RT1570: - rotelModel = RotelModel.RT1570; + model = RotelModel.RT1570; break; case THING_TYPE_ID_T11: - rotelModel = RotelModel.T11; + model = RotelModel.T11; break; case THING_TYPE_ID_T14: - rotelModel = RotelModel.T14; + model = RotelModel.T14; break; case THING_TYPE_ID_P5: - rotelModel = RotelModel.P5; + model = RotelModel.P5; break; case THING_TYPE_ID_X3: - rotelModel = RotelModel.X3; + model = RotelModel.X3; break; case THING_TYPE_ID_X5: - rotelModel = RotelModel.X5; + model = RotelModel.X5; break; default: - rotelModel = DEFAULT_MODEL; + model = DEFAULT_MODEL; break; } RotelThingConfiguration config = getConfigAs(RotelThingConfiguration.class); - RotelProtocol rotelProtocol = RotelProtocol.HEX; + protocol = RotelProtocol.HEX; if (config.protocol != null && !config.protocol.isEmpty()) { try { - rotelProtocol = RotelProtocol.getFromName(config.protocol); + protocol = RotelProtocol.getFromName(config.protocol); } catch (RotelException e) { + // Invalid protocol name in configuration, HEX will be considered by default } } else { Map properties = editProperties(); String property = properties.get(RotelBindingConstants.PROPERTY_PROTOCOL); if (property != null && !property.isEmpty()) { try { - rotelProtocol = RotelProtocol.getFromName(property); + protocol = RotelProtocol.getFromName(property); } catch (RotelException e) { + // Invalid protocol name in thing property, HEX will be considered by default } } } - logger.debug("rotelProtocol {}", rotelProtocol.getName()); + logger.debug("rotelProtocol {}", protocol.getName()); Map sourcesCustomLabels = new HashMap<>(); Map sourcesLabels = new HashMap<>(); String readerThreadName = "OH-binding-" + getThing().getUID().getAsString(); - connector = new RotelSimuConnector(rotelModel, rotelProtocol, sourcesLabels, readerThreadName); - - if (rotelModel.hasVolumeControl()) { - maxVolume = rotelModel.getVolumeMax(); - if (!rotelModel.hasDirectVolumeControl()) { + if (model.hasVolumeControl()) { + maxVolume = model.getVolumeMax(); + if (!model.hasDirectVolumeControl()) { logger.info( "Set minValue to {} and maxValue to {} for your sitemap widget attached to your volume item.", minVolume, maxVolume); } } - if (rotelModel.hasToneControl()) { - maxToneLevel = rotelModel.getToneLevelMax(); + if (model.hasToneControl()) { + maxToneLevel = model.getToneLevelMax(); minToneLevel = -maxToneLevel; logger.info( "Set minValue to {} and maxValue to {} for your sitemap widget attached to your bass or treble item.", minToneLevel, maxToneLevel); } - if (rotelModel.hasBalanceControl()) { - maxBalanceLevel = rotelModel.getBalanceLevelMax(); + if (model.hasBalanceControl()) { + maxBalanceLevel = model.getBalanceLevelMax(); minBalanceLevel = -maxBalanceLevel; logger.info("Set minValue to {} and maxValue to {} for your sitemap widget attached to your balance item.", minBalanceLevel, maxBalanceLevel); @@ -355,7 +364,7 @@ public class RotelHandler extends BaseThingHandler implements RotelMessageEventL if (configError != null) { updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, configError); } else { - for (RotelSource src : rotelModel.getSources()) { + for (RotelSource src : model.getSources()) { // Consider custom input labels String label = null; switch (src.getName()) { @@ -404,42 +413,49 @@ public class RotelHandler extends BaseThingHandler implements RotelMessageEventL sourcesLabels.put(src, (label == null || label.isEmpty()) ? src.getLabel() : label); } - if (USE_SIMULATED_DEVICE) { - connector = new RotelSimuConnector(rotelModel, rotelProtocol, sourcesLabels, readerThreadName); - } else if (config.serialPort != null) { - connector = new RotelSerialConnector(serialPortManager, config.serialPort, rotelModel, rotelProtocol, - sourcesLabels, readerThreadName); + if (protocol == RotelProtocol.HEX) { + protocolHandler = new RotelHexProtocolHandler(model, sourcesLabels); + } else if (protocol == RotelProtocol.ASCII_V1) { + protocolHandler = new RotelAsciiV1ProtocolHandler(model); } else { - connector = new RotelIpConnector(config.host, config.port, rotelModel, rotelProtocol, sourcesLabels, - readerThreadName); + protocolHandler = new RotelAsciiV2ProtocolHandler(model); } - if (rotelModel.hasSourceControl()) { + if (USE_SIMULATED_DEVICE) { + connector = new RotelSimuConnector(model, protocolHandler, sourcesLabels, readerThreadName); + } else if (config.serialPort != null) { + connector = new RotelSerialConnector(serialPortManager, config.serialPort, model.getBaudRate(), + protocolHandler, readerThreadName); + } else { + connector = new RotelIpConnector(config.host, config.port, protocolHandler, readerThreadName); + } + + if (model.hasSourceControl()) { stateDescriptionProvider.setStateOptions(new ChannelUID(getThing().getUID(), CHANNEL_SOURCE), - getStateOptions(rotelModel.getSources(), sourcesCustomLabels)); + getStateOptions(model.getSources(), sourcesCustomLabels)); stateDescriptionProvider.setStateOptions(new ChannelUID(getThing().getUID(), CHANNEL_MAIN_SOURCE), - getStateOptions(rotelModel.getSources(), sourcesCustomLabels)); + getStateOptions(model.getSources(), sourcesCustomLabels)); stateDescriptionProvider.setStateOptions( new ChannelUID(getThing().getUID(), CHANNEL_MAIN_RECORD_SOURCE), - getStateOptions(rotelModel.getRecordSources(), sourcesCustomLabels)); + getStateOptions(model.getRecordSources(), sourcesCustomLabels)); } - if (rotelModel.hasZone2SourceControl()) { + if (model.hasZone2SourceControl()) { stateDescriptionProvider.setStateOptions(new ChannelUID(getThing().getUID(), CHANNEL_ZONE2_SOURCE), - getStateOptions(rotelModel.getZone2Sources(), sourcesCustomLabels)); + getStateOptions(model.getZone2Sources(), sourcesCustomLabels)); } - if (rotelModel.hasZone3SourceControl()) { + if (model.hasZone3SourceControl()) { stateDescriptionProvider.setStateOptions(new ChannelUID(getThing().getUID(), CHANNEL_ZONE3_SOURCE), - getStateOptions(rotelModel.getZone3Sources(), sourcesCustomLabels)); + getStateOptions(model.getZone3Sources(), sourcesCustomLabels)); } - if (rotelModel.hasZone4SourceControl()) { + if (model.hasZone4SourceControl()) { stateDescriptionProvider.setStateOptions(new ChannelUID(getThing().getUID(), CHANNEL_ZONE4_SOURCE), - getStateOptions(rotelModel.getZone4Sources(), sourcesCustomLabels)); + getStateOptions(model.getZone4Sources(), sourcesCustomLabels)); } - if (rotelModel.hasDspControl()) { + if (model.hasDspControl()) { stateDescriptionProvider.setStateOptions(new ChannelUID(getThing().getUID(), CHANNEL_DSP), - rotelModel.getDspStateOptions()); + model.getDspStateOptions()); stateDescriptionProvider.setStateOptions(new ChannelUID(getThing().getUID(), CHANNEL_MAIN_DSP), - rotelModel.getDspStateOptions()); + model.getDspStateOptions()); } updateStatus(ThingStatus.UNKNOWN); @@ -502,20 +518,20 @@ public class RotelHandler extends BaseThingHandler implements RotelMessageEventL handlePowerCmd(channel, command, getPowerOnCommand(), getPowerOffCommand()); break; case CHANNEL_ZONE2_POWER: - if (connector.getModel().hasZone2Commands()) { + if (model.hasZone2Commands()) { handlePowerCmd(channel, command, RotelCommand.ZONE2_POWER_ON, RotelCommand.ZONE2_POWER_OFF); - } else if (connector.getModel().getNbAdditionalZones() == 1) { + } else if (model.getNbAdditionalZones() == 1) { if (isPowerOn() || powerZone2) { - selectZone(2, connector.getModel().getZoneSelectCmd()); + selectZone(2, model.getZoneSelectCmd()); } - connector.sendCommand(RotelCommand.ZONE_SELECT); + sendCommand(RotelCommand.ZONE_SELECT); } else { success = false; logger.debug("Command {} from channel {} failed: unavailable feature", command, channel); } break; case CHANNEL_ZONE3_POWER: - if (connector.getModel().hasZone3Commands()) { + if (model.hasZone3Commands()) { handlePowerCmd(channel, command, RotelCommand.ZONE3_POWER_ON, RotelCommand.ZONE3_POWER_OFF); } else { success = false; @@ -523,7 +539,7 @@ public class RotelHandler extends BaseThingHandler implements RotelMessageEventL } break; case CHANNEL_ZONE4_POWER: - if (connector.getModel().hasZone4Commands()) { + if (model.hasZone4Commands()) { handlePowerCmd(channel, command, RotelCommand.ZONE4_POWER_ON, RotelCommand.ZONE4_POWER_OFF); } else { success = false; @@ -536,12 +552,11 @@ public class RotelHandler extends BaseThingHandler implements RotelMessageEventL success = false; logger.debug("Command {} from channel {} ignored: device in standby", command, channel); } else { - src = connector.getModel().getSourceFromName(command.toString()); - cmd = connector.getModel().hasOtherThanPrimaryCommands() ? src.getMainZoneCommand() - : src.getCommand(); + src = model.getSourceFromName(command.toString()); + cmd = model.hasOtherThanPrimaryCommands() ? src.getMainZoneCommand() : src.getCommand(); if (cmd != null) { - connector.sendCommand(cmd); - if (connector.getModel().canGetFrequency()) { + sendCommand(cmd); + if (model.canGetFrequency()) { // send returns // 1.) the selected // 2.) the used frequency @@ -549,7 +564,7 @@ public class RotelHandler extends BaseThingHandler implements RotelMessageEventL // at response-time the frequency has the value of // so we must wait a short moment to get the frequency of Thread.sleep(1000); - connector.sendCommand(RotelCommand.FREQUENCY); + sendCommand(RotelCommand.FREQUENCY); Thread.sleep(100); updateChannelState(CHANNEL_FREQUENCY); } @@ -564,23 +579,23 @@ public class RotelHandler extends BaseThingHandler implements RotelMessageEventL if (!isPowerOn()) { success = false; logger.debug("Command {} from channel {} ignored: device in standby", command, channel); - } else if (connector.getModel().hasOtherThanPrimaryCommands()) { - src = connector.getModel().getSourceFromName(command.toString()); + } else if (model.hasOtherThanPrimaryCommands()) { + src = model.getSourceFromName(command.toString()); cmd = src.getRecordCommand(); if (cmd != null) { - connector.sendCommand(cmd); + sendCommand(cmd); } else { success = false; logger.debug("Command {} from channel {} failed: undefined record source command", command, channel); } } else { - src = connector.getModel().getSourceFromName(command.toString()); + src = model.getSourceFromName(command.toString()); cmd = src.getCommand(); if (cmd != null) { - connector.sendCommand(RotelCommand.RECORD_FONCTION_SELECT); + sendCommand(RotelCommand.RECORD_FONCTION_SELECT); Thread.sleep(100); - connector.sendCommand(cmd); + sendCommand(cmd); } else { success = false; logger.debug("Command {} from channel {} failed: undefined source command", command, @@ -592,22 +607,22 @@ public class RotelHandler extends BaseThingHandler implements RotelMessageEventL if (!powerZone2) { success = false; logger.debug("Command {} from channel {} ignored: zone 2 in standby", command, channel); - } else if (connector.getModel().hasZone2Commands()) { - src = connector.getModel().getSourceFromName(command.toString()); + } else if (model.hasZone2Commands()) { + src = model.getSourceFromName(command.toString()); cmd = src.getZone2Command(); if (cmd != null) { - connector.sendCommand(cmd); + sendCommand(cmd); } else { success = false; logger.debug("Command {} from channel {} failed: undefined zone 2 source command", command, channel); } - } else if (connector.getModel().getNbAdditionalZones() >= 1) { - src = connector.getModel().getSourceFromName(command.toString()); + } else if (model.getNbAdditionalZones() >= 1) { + src = model.getSourceFromName(command.toString()); cmd = src.getCommand(); if (cmd != null) { - selectZone(2, connector.getModel().getZoneSelectCmd()); - connector.sendCommand(cmd); + selectZone(2, model.getZoneSelectCmd()); + sendCommand(cmd); } else { success = false; logger.debug("Command {} from channel {} failed: undefined source command", command, @@ -622,11 +637,11 @@ public class RotelHandler extends BaseThingHandler implements RotelMessageEventL if (!powerZone3) { success = false; logger.debug("Command {} from channel {} ignored: zone 3 in standby", command, channel); - } else if (connector.getModel().hasZone3Commands()) { - src = connector.getModel().getSourceFromName(command.toString()); + } else if (model.hasZone3Commands()) { + src = model.getSourceFromName(command.toString()); cmd = src.getZone3Command(); if (cmd != null) { - connector.sendCommand(cmd); + sendCommand(cmd); } else { success = false; logger.debug("Command {} from channel {} failed: undefined zone 3 source command", @@ -641,11 +656,11 @@ public class RotelHandler extends BaseThingHandler implements RotelMessageEventL if (!powerZone4) { success = false; logger.debug("Command {} from channel {} ignored: zone 4 in standby", command, channel); - } else if (connector.getModel().hasZone4Commands()) { - src = connector.getModel().getSourceFromName(command.toString()); + } else if (model.hasZone4Commands()) { + src = model.getSourceFromName(command.toString()); cmd = src.getZone4Command(); if (cmd != null) { - connector.sendCommand(cmd); + sendCommand(cmd); } else { success = false; logger.debug("Command {} from channel {} failed: undefined zone 4 source command", @@ -662,7 +677,7 @@ public class RotelHandler extends BaseThingHandler implements RotelMessageEventL success = false; logger.debug("Command {} from channel {} ignored: device in standby", command, channel); } else { - connector.sendCommand(connector.getModel().getCommandFromDspName(command.toString())); + sendCommand(model.getCommandFromDspName(command.toString())); } break; case CHANNEL_VOLUME: @@ -670,7 +685,7 @@ public class RotelHandler extends BaseThingHandler implements RotelMessageEventL if (!isPowerOn()) { success = false; logger.debug("Command {} from channel {} ignored: device in standby", command, channel); - } else if (connector.getModel().hasVolumeControl()) { + } else if (model.hasVolumeControl()) { handleVolumeCmd(volume, channel, command, getVolumeUpCommand(), getVolumeDownCommand(), RotelCommand.VOLUME_SET); } else { @@ -682,7 +697,7 @@ public class RotelHandler extends BaseThingHandler implements RotelMessageEventL if (!isPowerOn()) { success = false; logger.debug("Command {} from channel {} ignored: device in standby", command, channel); - } else if (connector.getModel().hasVolumeControl()) { + } else if (model.hasVolumeControl()) { handleVolumeCmd(volume, channel, command, getVolumeUpCommand(), getVolumeDownCommand(), null); } else { @@ -698,13 +713,12 @@ public class RotelHandler extends BaseThingHandler implements RotelMessageEventL success = false; logger.debug("Command {} from channel {} ignored: fixed volume in zone 2", command, channel); - } else if (connector.getModel().hasVolumeControl() - && connector.getModel().getNbAdditionalZones() >= 1) { - if (connector.getModel().hasZone2Commands()) { + } else if (model.hasVolumeControl() && model.getNbAdditionalZones() >= 1) { + if (model.hasZone2Commands()) { handleVolumeCmd(volumeZone2, channel, command, RotelCommand.ZONE2_VOLUME_UP, RotelCommand.ZONE2_VOLUME_DOWN, RotelCommand.ZONE2_VOLUME_SET); } else { - selectZone(2, connector.getModel().getZoneSelectCmd()); + selectZone(2, model.getZoneSelectCmd()); handleVolumeCmd(volumeZone2, channel, command, RotelCommand.VOLUME_UP, RotelCommand.VOLUME_DOWN, RotelCommand.VOLUME_SET); } @@ -721,13 +735,12 @@ public class RotelHandler extends BaseThingHandler implements RotelMessageEventL success = false; logger.debug("Command {} from channel {} ignored: fixed volume in zone 2", command, channel); - } else if (connector.getModel().hasVolumeControl() - && connector.getModel().getNbAdditionalZones() >= 1) { - if (connector.getModel().hasZone2Commands()) { + } else if (model.hasVolumeControl() && model.getNbAdditionalZones() >= 1) { + if (model.hasZone2Commands()) { handleVolumeCmd(volumeZone2, channel, command, RotelCommand.ZONE2_VOLUME_UP, RotelCommand.ZONE2_VOLUME_DOWN, null); } else { - selectZone(2, connector.getModel().getZoneSelectCmd()); + selectZone(2, model.getZoneSelectCmd()); handleVolumeCmd(volumeZone2, channel, command, RotelCommand.VOLUME_UP, RotelCommand.VOLUME_DOWN, null); } @@ -744,7 +757,7 @@ public class RotelHandler extends BaseThingHandler implements RotelMessageEventL success = false; logger.debug("Command {} from channel {} ignored: fixed volume in zone 3", command, channel); - } else if (connector.getModel().hasVolumeControl() && connector.getModel().hasZone3Commands()) { + } else if (model.hasVolumeControl() && model.hasZone3Commands()) { handleVolumeCmd(volumeZone3, channel, command, RotelCommand.ZONE3_VOLUME_UP, RotelCommand.ZONE3_VOLUME_DOWN, RotelCommand.ZONE3_VOLUME_SET); } else { @@ -760,7 +773,7 @@ public class RotelHandler extends BaseThingHandler implements RotelMessageEventL success = false; logger.debug("Command {} from channel {} ignored: fixed volume in zone 4", command, channel); - } else if (connector.getModel().hasVolumeControl() && connector.getModel().hasZone4Commands()) { + } else if (model.hasVolumeControl() && model.hasZone4Commands()) { handleVolumeCmd(volumeZone4, channel, command, RotelCommand.ZONE4_VOLUME_UP, RotelCommand.ZONE4_VOLUME_DOWN, RotelCommand.ZONE4_VOLUME_SET); } else { @@ -773,9 +786,9 @@ public class RotelHandler extends BaseThingHandler implements RotelMessageEventL if (!isPowerOn()) { success = false; logger.debug("Command {} from channel {} ignored: device in standby", command, channel); - } else if (connector.getModel().hasVolumeControl()) { - handleMuteCmd(connector.getProtocol() == RotelProtocol.HEX, channel, command, - getMuteOnCommand(), getMuteOffCommand(), getMuteToggleCommand()); + } else if (model.hasVolumeControl()) { + handleMuteCmd(protocol == RotelProtocol.HEX, channel, command, getMuteOnCommand(), + getMuteOffCommand(), getMuteToggleCommand()); } else { success = false; logger.debug("Command {} from channel {} failed: unavailable feature", command, channel); @@ -785,7 +798,7 @@ public class RotelHandler extends BaseThingHandler implements RotelMessageEventL if (!powerZone2) { success = false; logger.debug("Command {} from channel {} ignored: zone 2 in standby", command, channel); - } else if (connector.getModel().hasVolumeControl() && connector.getModel().hasZone2Commands()) { + } else if (model.hasVolumeControl() && model.hasZone2Commands()) { handleMuteCmd(false, channel, command, RotelCommand.ZONE2_MUTE_ON, RotelCommand.ZONE2_MUTE_OFF, RotelCommand.ZONE2_MUTE_TOGGLE); } else { @@ -797,7 +810,7 @@ public class RotelHandler extends BaseThingHandler implements RotelMessageEventL if (!powerZone3) { success = false; logger.debug("Command {} from channel {} ignored: zone 3 in standby", command, channel); - } else if (connector.getModel().hasVolumeControl() && connector.getModel().hasZone3Commands()) { + } else if (model.hasVolumeControl() && model.hasZone3Commands()) { handleMuteCmd(false, channel, command, RotelCommand.ZONE3_MUTE_ON, RotelCommand.ZONE3_MUTE_OFF, RotelCommand.ZONE3_MUTE_TOGGLE); } else { @@ -809,7 +822,7 @@ public class RotelHandler extends BaseThingHandler implements RotelMessageEventL if (!powerZone4) { success = false; logger.debug("Command {} from channel {} ignored: zone 4 in standby", command, channel); - } else if (connector.getModel().hasVolumeControl() && connector.getModel().hasZone4Commands()) { + } else if (model.hasVolumeControl() && model.hasZone4Commands()) { handleMuteCmd(false, channel, command, RotelCommand.ZONE4_MUTE_ON, RotelCommand.ZONE4_MUTE_OFF, RotelCommand.ZONE4_MUTE_TOGGLE); } else { @@ -850,20 +863,18 @@ public class RotelHandler extends BaseThingHandler implements RotelMessageEventL success = false; logger.debug("Command {} from channel {} ignored: device in standby", command, channel); } else if (command instanceof PlayPauseType && command == PlayPauseType.PLAY) { - connector.sendCommand(RotelCommand.PLAY); + sendCommand(RotelCommand.PLAY); } else if (command instanceof PlayPauseType && command == PlayPauseType.PAUSE) { - connector.sendCommand(RotelCommand.PAUSE); - if (connector.getProtocol() == RotelProtocol.ASCII_V1 - && connector.getModel() != RotelModel.RCD1570 - && connector.getModel() != RotelModel.RCD1572 - && connector.getModel() != RotelModel.RCX1500) { + sendCommand(RotelCommand.PAUSE); + if (protocol == RotelProtocol.ASCII_V1 && model != RotelModel.RCD1570 + && model != RotelModel.RCD1572 && model != RotelModel.RCX1500) { Thread.sleep(SLEEP_INTV); - connector.sendCommand(RotelCommand.PLAY_STATUS); + sendCommand(RotelCommand.PLAY_STATUS); } } else if (command instanceof NextPreviousType && command == NextPreviousType.NEXT) { - connector.sendCommand(RotelCommand.TRACK_FORWARD); + sendCommand(RotelCommand.TRACK_FORWARD); } else if (command instanceof NextPreviousType && command == NextPreviousType.PREVIOUS) { - connector.sendCommand(RotelCommand.TRACK_BACKWORD); + sendCommand(RotelCommand.TRACK_BACKWORD); } else { success = false; logger.debug("Command {} from channel {} failed: invalid command value", command, channel); @@ -873,15 +884,14 @@ public class RotelHandler extends BaseThingHandler implements RotelMessageEventL if (!isPowerOn()) { success = false; logger.debug("Command {} from channel {} ignored: device in standby", command, channel); - } else if (!connector.getModel().hasDimmerControl()) { + } else if (!model.hasDimmerControl()) { success = false; logger.debug("Command {} from channel {} failed: unavailable feature", command, channel); } else if (command instanceof PercentType) { int dimmer = (int) Math.round(((PercentType) command).doubleValue() / 100.0 - * (connector.getModel().getDimmerLevelMax() - - connector.getModel().getDimmerLevelMin())) - + connector.getModel().getDimmerLevelMin(); - connector.sendCommand(RotelCommand.DIMMER_LEVEL_SET, dimmer); + * (model.getDimmerLevelMax() - model.getDimmerLevelMin())) + + model.getDimmerLevelMin(); + sendCommand(RotelCommand.DIMMER_LEVEL_SET, dimmer); } else { success = false; logger.debug("Command {} from channel {} failed: invalid command value", command, channel); @@ -891,15 +901,14 @@ public class RotelHandler extends BaseThingHandler implements RotelMessageEventL if (!isPowerOn()) { success = false; logger.debug("Command {} from channel {} ignored: device in standby", command, channel); - } else if (!connector.getModel().hasToneControl() - || connector.getProtocol() == RotelProtocol.HEX) { + } else if (!model.hasToneControl() || protocol == RotelProtocol.HEX) { success = false; logger.debug("Command {} from channel {} failed: unavailable feature", command, channel); } else { handleTcbypassCmd(channel, command, - connector.getProtocol() == RotelProtocol.ASCII_V1 ? RotelCommand.TONE_CONTROLS_OFF + protocol == RotelProtocol.ASCII_V1 ? RotelCommand.TONE_CONTROLS_OFF : RotelCommand.TCBYPASS_ON, - connector.getProtocol() == RotelProtocol.ASCII_V1 ? RotelCommand.TONE_CONTROLS_ON + protocol == RotelProtocol.ASCII_V1 ? RotelCommand.TONE_CONTROLS_ON : RotelCommand.TCBYPASS_OFF); } break; @@ -907,8 +916,7 @@ public class RotelHandler extends BaseThingHandler implements RotelMessageEventL if (!isPowerOn()) { success = false; logger.debug("Command {} from channel {} ignored: device in standby", command, channel); - } else if (!connector.getModel().hasBalanceControl() - || connector.getProtocol() == RotelProtocol.HEX) { + } else if (!model.hasBalanceControl() || protocol == RotelProtocol.HEX) { success = false; logger.debug("Command {} from channel {} failed: unavailable feature", command, channel); } else { @@ -921,9 +929,8 @@ public class RotelHandler extends BaseThingHandler implements RotelMessageEventL success = false; logger.debug("Command {} from channel {} ignored: device in standby", command, channel); } else { - handleSpeakerCmd(connector.getProtocol() == RotelProtocol.HEX, channel, command, - RotelCommand.SPEAKER_A_ON, RotelCommand.SPEAKER_A_OFF, - RotelCommand.SPEAKER_A_TOGGLE); + handleSpeakerCmd(protocol == RotelProtocol.HEX, channel, command, RotelCommand.SPEAKER_A_ON, + RotelCommand.SPEAKER_A_OFF, RotelCommand.SPEAKER_A_TOGGLE); } break; case CHANNEL_SPEAKER_B: @@ -931,9 +938,8 @@ public class RotelHandler extends BaseThingHandler implements RotelMessageEventL success = false; logger.debug("Command {} from channel {} ignored: device in standby", command, channel); } else { - handleSpeakerCmd(connector.getProtocol() == RotelProtocol.HEX, channel, command, - RotelCommand.SPEAKER_B_ON, RotelCommand.SPEAKER_B_OFF, - RotelCommand.SPEAKER_B_TOGGLE); + handleSpeakerCmd(protocol == RotelProtocol.HEX, channel, command, RotelCommand.SPEAKER_B_ON, + RotelCommand.SPEAKER_B_OFF, RotelCommand.SPEAKER_B_TOGGLE); } break; default: @@ -972,9 +978,9 @@ public class RotelHandler extends BaseThingHandler implements RotelMessageEventL private void handlePowerCmd(String channel, Command command, RotelCommand onCmd, RotelCommand offCmd) throws RotelException { if (command instanceof OnOffType && command == OnOffType.ON) { - connector.sendCommand(onCmd); + sendCommand(onCmd); } else if (command instanceof OnOffType && command == OnOffType.OFF) { - connector.sendCommand(offCmd); + sendCommand(offCmd); } else { logger.debug("Command {} from channel {} failed: invalid command value", command, channel); } @@ -995,22 +1001,22 @@ public class RotelHandler extends BaseThingHandler implements RotelMessageEventL private void handleVolumeCmd(int current, String channel, Command command, RotelCommand upCmd, RotelCommand downCmd, @Nullable RotelCommand setCmd) throws RotelException { if (command instanceof IncreaseDecreaseType && command == IncreaseDecreaseType.INCREASE) { - connector.sendCommand(upCmd); + sendCommand(upCmd); } else if (command instanceof IncreaseDecreaseType && command == IncreaseDecreaseType.DECREASE) { - connector.sendCommand(downCmd); + sendCommand(downCmd); } else if (command instanceof DecimalType && setCmd == null) { int value = ((DecimalType) command).intValue(); if (value >= minVolume && value <= maxVolume) { if (value > current) { - connector.sendCommand(upCmd); + sendCommand(upCmd); } else if (value < current) { - connector.sendCommand(downCmd); + sendCommand(downCmd); } } } else if (command instanceof PercentType && setCmd != null) { int value = (int) Math.round(((PercentType) command).doubleValue() / 100.0 * (maxVolume - minVolume)) + minVolume; - connector.sendCommand(setCmd, value); + sendCommand(setCmd, value); } else { logger.debug("Command {} from channel {} failed: invalid command value", command, channel); } @@ -1032,11 +1038,11 @@ public class RotelHandler extends BaseThingHandler implements RotelMessageEventL RotelCommand offCmd, RotelCommand toggleCmd) throws RotelException { if (command instanceof OnOffType) { if (onlyToggle) { - connector.sendCommand(toggleCmd); + sendCommand(toggleCmd); } else if (command == OnOffType.ON) { - connector.sendCommand(onCmd); + sendCommand(onCmd); } else if (command == OnOffType.OFF) { - connector.sendCommand(offCmd); + sendCommand(offCmd); } } else { logger.debug("Command {} from channel {} failed: invalid command value", command, channel); @@ -1061,21 +1067,21 @@ public class RotelHandler extends BaseThingHandler implements RotelMessageEventL RotelCommand downCmd, RotelCommand setCmd) throws RotelException, InterruptedException { if (command instanceof IncreaseDecreaseType && command == IncreaseDecreaseType.INCREASE) { selectToneControl(nbSelect); - connector.sendCommand(upCmd); + sendCommand(upCmd); } else if (command instanceof IncreaseDecreaseType && command == IncreaseDecreaseType.DECREASE) { selectToneControl(nbSelect); - connector.sendCommand(downCmd); + sendCommand(downCmd); } else if (command instanceof DecimalType) { int value = ((DecimalType) command).intValue(); if (value >= minToneLevel && value <= maxToneLevel) { - if (connector.getProtocol() != RotelProtocol.HEX) { - connector.sendCommand(setCmd, value); + if (protocol != RotelProtocol.HEX) { + sendCommand(setCmd, value); } else if (value > current) { selectToneControl(nbSelect); - connector.sendCommand(upCmd); + sendCommand(upCmd); } else if (value < current) { selectToneControl(nbSelect); - connector.sendCommand(downCmd); + sendCommand(downCmd); } } } else { @@ -1097,17 +1103,17 @@ public class RotelHandler extends BaseThingHandler implements RotelMessageEventL throws RotelException, InterruptedException { if (command instanceof OnOffType) { if (command == OnOffType.ON) { - connector.sendCommand(onCmd); + sendCommand(onCmd); bass = 0; treble = 0; updateChannelState(CHANNEL_BASS); updateChannelState(CHANNEL_TREBLE); } else if (command == OnOffType.OFF) { - connector.sendCommand(offCmd); + sendCommand(offCmd); Thread.sleep(200); - connector.sendCommand(RotelCommand.BASS); + sendCommand(RotelCommand.BASS); Thread.sleep(200); - connector.sendCommand(RotelCommand.TREBLE); + sendCommand(RotelCommand.TREBLE); } } else { logger.debug("Command {} from channel {} failed: invalid command value", command, channel); @@ -1130,11 +1136,11 @@ public class RotelHandler extends BaseThingHandler implements RotelMessageEventL RotelCommand offCmd, RotelCommand toggleCmd) throws RotelException { if (command instanceof OnOffType) { if (onlyToggle) { - connector.sendCommand(toggleCmd); + sendCommand(toggleCmd); } else if (command == OnOffType.ON) { - connector.sendCommand(onCmd); + sendCommand(onCmd); } else if (command == OnOffType.OFF) { - connector.sendCommand(offCmd); + sendCommand(offCmd); } } else { logger.debug("Command {} from channel {} failed: invalid command value", command, channel); @@ -1156,13 +1162,13 @@ public class RotelHandler extends BaseThingHandler implements RotelMessageEventL private void handleBalanceCmd(String channel, Command command, RotelCommand leftCmd, RotelCommand rightCmd, RotelCommand setCmd) throws RotelException, InterruptedException { if (command instanceof IncreaseDecreaseType && command == IncreaseDecreaseType.INCREASE) { - connector.sendCommand(rightCmd); + sendCommand(rightCmd); } else if (command instanceof IncreaseDecreaseType && command == IncreaseDecreaseType.DECREASE) { - connector.sendCommand(leftCmd); + sendCommand(leftCmd); } else if (command instanceof DecimalType) { int value = ((DecimalType) command).intValue(); if (value >= minBalanceLevel && value <= maxBalanceLevel) { - connector.sendCommand(setCmd, value); + sendCommand(setCmd, value); } } else { logger.debug("Command {} from channel {} failed: invalid command value", command, channel); @@ -1179,7 +1185,7 @@ public class RotelHandler extends BaseThingHandler implements RotelMessageEventL */ private void selectToneControl(int nbSelect) throws RotelException, InterruptedException { // No tone control select command for RSX-1065 - if (connector.getProtocol() == RotelProtocol.HEX && connector.getModel() != RotelModel.RSX1065) { + if (protocol == RotelProtocol.HEX && model != RotelModel.RSX1065) { selectFeature(nbSelect, RotelCommand.RECORD_FONCTION_SELECT, RotelCommand.TONE_CONTROL_SELECT); } } @@ -1195,11 +1201,11 @@ public class RotelHandler extends BaseThingHandler implements RotelMessageEventL */ private void selectZone(int zone, @Nullable RotelCommand selectCommand) throws RotelException, InterruptedException { - if (connector.getProtocol() == RotelProtocol.HEX && connector.getModel().getNbAdditionalZones() >= 1 - && zone >= 1 && zone != currentZone && selectCommand != null) { + if (protocol == RotelProtocol.HEX && model.getNbAdditionalZones() >= 1 && zone >= 1 && zone != currentZone + && selectCommand != null) { int nbSelect; if (zone < currentZone) { - nbSelect = zone + connector.getModel().getNbAdditionalZones() - currentZone; + nbSelect = zone + model.getNbAdditionalZones() - currentZone; if (isPowerOn() && selectCommand == RotelCommand.RECORD_FONCTION_SELECT) { nbSelect++; } @@ -1226,13 +1232,13 @@ public class RotelHandler extends BaseThingHandler implements RotelMessageEventL */ private void selectFeature(int nbSelect, @Nullable RotelCommand preCmd, RotelCommand selectCmd) throws RotelException, InterruptedException { - if (connector.getProtocol() == RotelProtocol.HEX) { + if (protocol == RotelProtocol.HEX) { if (preCmd != null) { - connector.sendCommand(preCmd); + sendCommand(preCmd); Thread.sleep(100); } for (int i = 1; i <= nbSelect; i++) { - connector.sendCommand(selectCmd); + sendCommand(selectCmd); Thread.sleep(200); } } @@ -1244,7 +1250,7 @@ public class RotelHandler extends BaseThingHandler implements RotelMessageEventL * @return true if the connection is opened successfully or flase if not */ private synchronized boolean openConnection() { - connector.addEventListener(this); + protocolHandler.addEventListener(this); try { connector.open(); } catch (RotelException e) { @@ -1259,7 +1265,7 @@ public class RotelHandler extends BaseThingHandler implements RotelMessageEventL */ private synchronized void closeConnection() { connector.close(); - connector.removeEventListener(this); + protocolHandler.removeEventListener(this); logger.debug("closeConnection(): disconnected"); } @@ -1272,87 +1278,87 @@ public class RotelHandler extends BaseThingHandler implements RotelMessageEventL String key = evt.getKey(); String value = evt.getValue().trim(); - if (!RotelConnector.KEY_ERROR.equals(key)) { + if (!KEY_ERROR.equals(key)) { updateStatus(ThingStatus.ONLINE); } try { switch (key) { - case RotelConnector.KEY_ERROR: + case KEY_ERROR: logger.debug("Reading feedback message failed"); updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "@text/offline.comm-error-reading-thread"); closeConnection(); break; - case RotelConnector.KEY_LINE1: + case KEY_LINE1: frontPanelLine1 = value; updateChannelState(CHANNEL_LINE1); break; - case RotelConnector.KEY_LINE2: + case KEY_LINE2: frontPanelLine2 = value; updateChannelState(CHANNEL_LINE2); break; - case RotelConnector.KEY_ZONE: + case KEY_ZONE: currentZone = Integer.parseInt(value); break; - case RotelConnector.KEY_RECORD_SEL: - selectingRecord = RotelConnector.MSG_VALUE_ON.equalsIgnoreCase(value); + case KEY_RECORD_SEL: + selectingRecord = MSG_VALUE_ON.equalsIgnoreCase(value); break; - case RotelConnector.KEY_POWER: - if (RotelConnector.POWER_ON.equalsIgnoreCase(value)) { + case KEY_POWER: + if (POWER_ON.equalsIgnoreCase(value)) { handlePowerOn(); - } else if (RotelConnector.STANDBY.equalsIgnoreCase(value)) { + } else if (STANDBY.equalsIgnoreCase(value)) { handlePowerOff(); - } else if (RotelConnector.POWER_OFF_DELAYED.equalsIgnoreCase(value)) { + } else if (POWER_OFF_DELAYED.equalsIgnoreCase(value)) { schedulePowerOffJob(false); } else { throw new RotelException("Invalid value"); } break; - case RotelConnector.KEY_POWER_ZONE2: - if (RotelConnector.POWER_ON.equalsIgnoreCase(value)) { + case KEY_POWER_ZONE2: + if (POWER_ON.equalsIgnoreCase(value)) { handlePowerOnZone2(); - } else if (RotelConnector.STANDBY.equalsIgnoreCase(value)) { + } else if (STANDBY.equalsIgnoreCase(value)) { handlePowerOffZone2(); } else { throw new RotelException("Invalid value"); } break; - case RotelConnector.KEY_POWER_ZONE3: - if (RotelConnector.POWER_ON.equalsIgnoreCase(value)) { + case KEY_POWER_ZONE3: + if (POWER_ON.equalsIgnoreCase(value)) { handlePowerOnZone3(); - } else if (RotelConnector.STANDBY.equalsIgnoreCase(value)) { + } else if (STANDBY.equalsIgnoreCase(value)) { handlePowerOffZone3(); } else { throw new RotelException("Invalid value"); } break; - case RotelConnector.KEY_POWER_ZONE4: - if (RotelConnector.POWER_ON.equalsIgnoreCase(value)) { + case KEY_POWER_ZONE4: + if (POWER_ON.equalsIgnoreCase(value)) { handlePowerOnZone4(); - } else if (RotelConnector.STANDBY.equalsIgnoreCase(value)) { + } else if (STANDBY.equalsIgnoreCase(value)) { handlePowerOffZone4(); } else { throw new RotelException("Invalid value"); } break; - case RotelConnector.KEY_VOLUME_MIN: + case KEY_VOLUME_MIN: minVolume = Integer.parseInt(value); - if (!connector.getModel().hasDirectVolumeControl()) { + if (!model.hasDirectVolumeControl()) { logger.info("Set minValue to {} for your sitemap widget attached to your volume item.", minVolume); } break; - case RotelConnector.KEY_VOLUME_MAX: + case KEY_VOLUME_MAX: maxVolume = Integer.parseInt(value); - if (!connector.getModel().hasDirectVolumeControl()) { + if (!model.hasDirectVolumeControl()) { logger.info("Set maxValue to {} for your sitemap widget attached to your volume item.", maxVolume); } break; - case RotelConnector.KEY_VOLUME: - if (RotelConnector.MSG_VALUE_MIN.equalsIgnoreCase(value)) { + case KEY_VOLUME: + if (MSG_VALUE_MIN.equalsIgnoreCase(value)) { volume = minVolume; - } else if (RotelConnector.MSG_VALUE_MAX.equalsIgnoreCase(value)) { + } else if (MSG_VALUE_MAX.equalsIgnoreCase(value)) { volume = maxVolume; } else { volume = Integer.parseInt(value); @@ -1361,12 +1367,12 @@ public class RotelHandler extends BaseThingHandler implements RotelMessageEventL updateChannelState(CHANNEL_MAIN_VOLUME); updateChannelState(CHANNEL_MAIN_VOLUME_UP_DOWN); break; - case RotelConnector.KEY_MUTE: - if (RotelConnector.MSG_VALUE_ON.equalsIgnoreCase(value)) { + case KEY_MUTE: + if (MSG_VALUE_ON.equalsIgnoreCase(value)) { mute = true; updateChannelState(CHANNEL_MUTE); updateChannelState(CHANNEL_MAIN_MUTE); - } else if (RotelConnector.MSG_VALUE_OFF.equalsIgnoreCase(value)) { + } else if (MSG_VALUE_OFF.equalsIgnoreCase(value)) { mute = false; updateChannelState(CHANNEL_MUTE); updateChannelState(CHANNEL_MAIN_MUTE); @@ -1374,13 +1380,13 @@ public class RotelHandler extends BaseThingHandler implements RotelMessageEventL throw new RotelException("Invalid value"); } break; - case RotelConnector.KEY_VOLUME_ZONE2: + case KEY_VOLUME_ZONE2: fixedVolumeZone2 = false; - if (RotelConnector.MSG_VALUE_FIX.equalsIgnoreCase(value)) { + if (MSG_VALUE_FIX.equalsIgnoreCase(value)) { fixedVolumeZone2 = true; - } else if (RotelConnector.MSG_VALUE_MIN.equalsIgnoreCase(value)) { + } else if (MSG_VALUE_MIN.equalsIgnoreCase(value)) { volumeZone2 = minVolume; - } else if (RotelConnector.MSG_VALUE_MAX.equalsIgnoreCase(value)) { + } else if (MSG_VALUE_MAX.equalsIgnoreCase(value)) { volumeZone2 = maxVolume; } else { volumeZone2 = Integer.parseInt(value); @@ -1388,76 +1394,76 @@ public class RotelHandler extends BaseThingHandler implements RotelMessageEventL updateChannelState(CHANNEL_ZONE2_VOLUME); updateChannelState(CHANNEL_ZONE2_VOLUME_UP_DOWN); break; - case RotelConnector.KEY_VOLUME_ZONE3: + case KEY_VOLUME_ZONE3: fixedVolumeZone3 = false; - if (RotelConnector.MSG_VALUE_FIX.equalsIgnoreCase(value)) { + if (MSG_VALUE_FIX.equalsIgnoreCase(value)) { fixedVolumeZone3 = true; - } else if (RotelConnector.MSG_VALUE_MIN.equalsIgnoreCase(value)) { + } else if (MSG_VALUE_MIN.equalsIgnoreCase(value)) { volumeZone3 = minVolume; - } else if (RotelConnector.MSG_VALUE_MAX.equalsIgnoreCase(value)) { + } else if (MSG_VALUE_MAX.equalsIgnoreCase(value)) { volumeZone3 = maxVolume; } else { volumeZone3 = Integer.parseInt(value); } updateChannelState(CHANNEL_ZONE3_VOLUME); break; - case RotelConnector.KEY_VOLUME_ZONE4: + case KEY_VOLUME_ZONE4: fixedVolumeZone4 = false; - if (RotelConnector.MSG_VALUE_FIX.equalsIgnoreCase(value)) { + if (MSG_VALUE_FIX.equalsIgnoreCase(value)) { fixedVolumeZone4 = true; - } else if (RotelConnector.MSG_VALUE_MIN.equalsIgnoreCase(value)) { + } else if (MSG_VALUE_MIN.equalsIgnoreCase(value)) { volumeZone4 = minVolume; - } else if (RotelConnector.MSG_VALUE_MAX.equalsIgnoreCase(value)) { + } else if (MSG_VALUE_MAX.equalsIgnoreCase(value)) { volumeZone4 = maxVolume; } else { volumeZone4 = Integer.parseInt(value); } updateChannelState(CHANNEL_ZONE4_VOLUME); break; - case RotelConnector.KEY_MUTE_ZONE2: - if (RotelConnector.MSG_VALUE_ON.equalsIgnoreCase(value)) { + case KEY_MUTE_ZONE2: + if (MSG_VALUE_ON.equalsIgnoreCase(value)) { muteZone2 = true; updateChannelState(CHANNEL_ZONE2_MUTE); - } else if (RotelConnector.MSG_VALUE_OFF.equalsIgnoreCase(value)) { + } else if (MSG_VALUE_OFF.equalsIgnoreCase(value)) { muteZone2 = false; updateChannelState(CHANNEL_ZONE2_MUTE); } else { throw new RotelException("Invalid value"); } break; - case RotelConnector.KEY_MUTE_ZONE3: - if (RotelConnector.MSG_VALUE_ON.equalsIgnoreCase(value)) { + case KEY_MUTE_ZONE3: + if (MSG_VALUE_ON.equalsIgnoreCase(value)) { muteZone3 = true; updateChannelState(CHANNEL_ZONE3_MUTE); - } else if (RotelConnector.MSG_VALUE_OFF.equalsIgnoreCase(value)) { + } else if (MSG_VALUE_OFF.equalsIgnoreCase(value)) { muteZone3 = false; updateChannelState(CHANNEL_ZONE3_MUTE); } else { throw new RotelException("Invalid value"); } break; - case RotelConnector.KEY_MUTE_ZONE4: - if (RotelConnector.MSG_VALUE_ON.equalsIgnoreCase(value)) { + case KEY_MUTE_ZONE4: + if (MSG_VALUE_ON.equalsIgnoreCase(value)) { muteZone4 = true; updateChannelState(CHANNEL_ZONE4_MUTE); - } else if (RotelConnector.MSG_VALUE_OFF.equalsIgnoreCase(value)) { + } else if (MSG_VALUE_OFF.equalsIgnoreCase(value)) { muteZone4 = false; updateChannelState(CHANNEL_ZONE4_MUTE); } else { throw new RotelException("Invalid value"); } break; - case RotelConnector.KEY_TONE_MAX: + case KEY_TONE_MAX: maxToneLevel = Integer.parseInt(value); minToneLevel = -maxToneLevel; logger.info( "Set minValue to {} and maxValue to {} for your sitemap widget attached to your bass or treble item.", minToneLevel, maxToneLevel); break; - case RotelConnector.KEY_BASS: - if (RotelConnector.MSG_VALUE_MIN.equalsIgnoreCase(value)) { + case KEY_BASS: + if (MSG_VALUE_MIN.equalsIgnoreCase(value)) { bass = minToneLevel; - } else if (RotelConnector.MSG_VALUE_MAX.equalsIgnoreCase(value)) { + } else if (MSG_VALUE_MAX.equalsIgnoreCase(value)) { bass = maxToneLevel; } else { bass = Integer.parseInt(value); @@ -1465,10 +1471,10 @@ public class RotelHandler extends BaseThingHandler implements RotelMessageEventL updateChannelState(CHANNEL_BASS); updateChannelState(CHANNEL_MAIN_BASS); break; - case RotelConnector.KEY_TREBLE: - if (RotelConnector.MSG_VALUE_MIN.equalsIgnoreCase(value)) { + case KEY_TREBLE: + if (MSG_VALUE_MIN.equalsIgnoreCase(value)) { treble = minToneLevel; - } else if (RotelConnector.MSG_VALUE_MAX.equalsIgnoreCase(value)) { + } else if (MSG_VALUE_MAX.equalsIgnoreCase(value)) { treble = maxToneLevel; } else { treble = Integer.parseInt(value); @@ -1476,32 +1482,28 @@ public class RotelHandler extends BaseThingHandler implements RotelMessageEventL updateChannelState(CHANNEL_TREBLE); updateChannelState(CHANNEL_MAIN_TREBLE); break; - case RotelConnector.KEY_SOURCE: - source = connector.getModel().getSourceFromCommand(RotelCommand.getFromAsciiCommand(value)); + case KEY_SOURCE: + source = model.getSourceFromCommand(RotelCommand.getFromAsciiCommand(value)); updateChannelState(CHANNEL_SOURCE); updateChannelState(CHANNEL_MAIN_SOURCE); break; - case RotelConnector.KEY_RECORD: - recordSource = connector.getModel() - .getRecordSourceFromCommand(RotelCommand.getFromAsciiCommand(value)); + case KEY_RECORD: + recordSource = model.getRecordSourceFromCommand(RotelCommand.getFromAsciiCommand(value)); updateChannelState(CHANNEL_MAIN_RECORD_SOURCE); break; - case RotelConnector.KEY_SOURCE_ZONE2: - sourceZone2 = connector.getModel() - .getZone2SourceFromCommand(RotelCommand.getFromAsciiCommand(value)); + case KEY_SOURCE_ZONE2: + sourceZone2 = model.getZone2SourceFromCommand(RotelCommand.getFromAsciiCommand(value)); updateChannelState(CHANNEL_ZONE2_SOURCE); break; - case RotelConnector.KEY_SOURCE_ZONE3: - sourceZone3 = connector.getModel() - .getZone3SourceFromCommand(RotelCommand.getFromAsciiCommand(value)); + case KEY_SOURCE_ZONE3: + sourceZone3 = model.getZone3SourceFromCommand(RotelCommand.getFromAsciiCommand(value)); updateChannelState(CHANNEL_ZONE3_SOURCE); break; - case RotelConnector.KEY_SOURCE_ZONE4: - sourceZone4 = connector.getModel() - .getZone4SourceFromCommand(RotelCommand.getFromAsciiCommand(value)); + case KEY_SOURCE_ZONE4: + sourceZone4 = model.getZone4SourceFromCommand(RotelCommand.getFromAsciiCommand(value)); updateChannelState(CHANNEL_ZONE4_SOURCE); break; - case RotelConnector.KEY_DSP_MODE: + case KEY_DSP_MODE: if ("dolby_pliix_movie".equals(value)) { value = "dolby_plii_movie"; } else if ("dolby_pliix_music".equals(value)) { @@ -1509,34 +1511,34 @@ public class RotelHandler extends BaseThingHandler implements RotelMessageEventL } else if ("dolby_pliix_game".equals(value)) { value = "dolby_plii_game"; } - dsp = connector.getModel().getDspFromFeedback(value); + dsp = model.getDspFromFeedback(value); logger.debug("DSP {}", dsp.getName()); updateChannelState(CHANNEL_DSP); updateChannelState(CHANNEL_MAIN_DSP); break; - case RotelConnector.KEY1_PLAY_STATUS: - case RotelConnector.KEY2_PLAY_STATUS: - if (RotelConnector.PLAY.equalsIgnoreCase(value)) { + case KEY1_PLAY_STATUS: + case KEY2_PLAY_STATUS: + if (PLAY.equalsIgnoreCase(value)) { playStatus = RotelPlayStatus.PLAYING; updateChannelState(CHANNEL_PLAY_CONTROL); - } else if (RotelConnector.PAUSE.equalsIgnoreCase(value)) { + } else if (PAUSE.equalsIgnoreCase(value)) { playStatus = RotelPlayStatus.PAUSED; updateChannelState(CHANNEL_PLAY_CONTROL); - } else if (RotelConnector.STOP.equalsIgnoreCase(value)) { + } else if (STOP.equalsIgnoreCase(value)) { playStatus = RotelPlayStatus.STOPPED; updateChannelState(CHANNEL_PLAY_CONTROL); } else { throw new RotelException("Invalid value"); } break; - case RotelConnector.KEY_TRACK: - if (source.getName().equals("CD") && !connector.getModel().hasSourceControl()) { + case KEY_TRACK: + if (source.getName().equals("CD") && !model.hasSourceControl()) { track = Integer.parseInt(value); updateChannelState(CHANNEL_TRACK); } break; - case RotelConnector.KEY_FREQ: - if (RotelConnector.MSG_VALUE_OFF.equalsIgnoreCase(value)) { + case KEY_FREQ: + if (MSG_VALUE_OFF.equalsIgnoreCase(value)) { frequency = 0.0; } else { // Suppress a potential ending "k" or "K" @@ -1547,39 +1549,39 @@ public class RotelHandler extends BaseThingHandler implements RotelMessageEventL } updateChannelState(CHANNEL_FREQUENCY); break; - case RotelConnector.KEY_DIMMER: + case KEY_DIMMER: brightness = Integer.parseInt(value); updateChannelState(CHANNEL_BRIGHTNESS); break; - case RotelConnector.KEY_UPDATE_MODE: - case RotelConnector.KEY_DISPLAY_UPDATE: + case KEY_UPDATE_MODE: + case KEY_DISPLAY_UPDATE: break; - case RotelConnector.KEY_TONE: - if (RotelConnector.MSG_VALUE_ON.equalsIgnoreCase(value)) { + case KEY_TONE: + if (MSG_VALUE_ON.equalsIgnoreCase(value)) { tcbypass = false; updateChannelState(CHANNEL_TCBYPASS); - } else if (RotelConnector.MSG_VALUE_OFF.equalsIgnoreCase(value)) { + } else if (MSG_VALUE_OFF.equalsIgnoreCase(value)) { tcbypass = true; updateChannelState(CHANNEL_TCBYPASS); } else { throw new RotelException("Invalid value"); } break; - case RotelConnector.KEY_TCBYPASS: - if (RotelConnector.MSG_VALUE_ON.equalsIgnoreCase(value)) { + case KEY_TCBYPASS: + if (MSG_VALUE_ON.equalsIgnoreCase(value)) { tcbypass = true; updateChannelState(CHANNEL_TCBYPASS); - } else if (RotelConnector.MSG_VALUE_OFF.equalsIgnoreCase(value)) { + } else if (MSG_VALUE_OFF.equalsIgnoreCase(value)) { tcbypass = false; updateChannelState(CHANNEL_TCBYPASS); } else { throw new RotelException("Invalid value"); } break; - case RotelConnector.KEY_BALANCE: - if (RotelConnector.MSG_VALUE_MIN.equalsIgnoreCase(value)) { + case KEY_BALANCE: + if (MSG_VALUE_MIN.equalsIgnoreCase(value)) { balance = minBalanceLevel; - } else if (RotelConnector.MSG_VALUE_MAX.equalsIgnoreCase(value)) { + } else if (MSG_VALUE_MAX.equalsIgnoreCase(value)) { balance = maxBalanceLevel; } else if (value.toUpperCase().startsWith("L")) { balance = -Integer.parseInt(value.substring(1)); @@ -1590,23 +1592,23 @@ public class RotelHandler extends BaseThingHandler implements RotelMessageEventL } updateChannelState(CHANNEL_BALANCE); break; - case RotelConnector.KEY_SPEAKER: - if (RotelConnector.MSG_VALUE_SPEAKER_A.equalsIgnoreCase(value)) { + case KEY_SPEAKER: + if (MSG_VALUE_SPEAKER_A.equalsIgnoreCase(value)) { speakera = true; speakerb = false; updateChannelState(CHANNEL_SPEAKER_A); updateChannelState(CHANNEL_SPEAKER_B); - } else if (RotelConnector.MSG_VALUE_SPEAKER_B.equalsIgnoreCase(value)) { + } else if (MSG_VALUE_SPEAKER_B.equalsIgnoreCase(value)) { speakera = false; speakerb = true; updateChannelState(CHANNEL_SPEAKER_A); updateChannelState(CHANNEL_SPEAKER_B); - } else if (RotelConnector.MSG_VALUE_SPEAKER_AB.equalsIgnoreCase(value)) { + } else if (MSG_VALUE_SPEAKER_AB.equalsIgnoreCase(value)) { speakera = true; speakerb = true; updateChannelState(CHANNEL_SPEAKER_A); updateChannelState(CHANNEL_SPEAKER_B); - } else if (RotelConnector.MSG_VALUE_OFF.equalsIgnoreCase(value)) { + } else if (MSG_VALUE_OFF.equalsIgnoreCase(value)) { speakera = false; speakerb = false; updateChannelState(CHANNEL_SPEAKER_A); @@ -1782,37 +1784,36 @@ public class RotelHandler extends BaseThingHandler implements RotelMessageEventL synchronized (sequenceLock) { logger.debug("Power ON job"); try { - switch (connector.getProtocol()) { + switch (protocol) { case HEX: - if (connector.getModel().getRespNbChars() <= 13 - && connector.getModel().hasVolumeControl()) { - connector.sendCommand(getVolumeDownCommand()); + if (model.getRespNbChars() <= 13 && model.hasVolumeControl()) { + sendCommand(getVolumeDownCommand()); Thread.sleep(100); - connector.sendCommand(getVolumeUpCommand()); + sendCommand(getVolumeUpCommand()); Thread.sleep(100); } - if (connector.getModel().getNbAdditionalZones() >= 1) { - if (currentZone != 1 && connector.getModel() - .getZoneSelectCmd() == RotelCommand.RECORD_FONCTION_SELECT) { - selectZone(1, connector.getModel().getZoneSelectCmd()); + if (model.getNbAdditionalZones() >= 1) { + if (currentZone != 1 + && model.getZoneSelectCmd() == RotelCommand.RECORD_FONCTION_SELECT) { + selectZone(1, model.getZoneSelectCmd()); } else if (!selectingRecord) { - connector.sendCommand(RotelCommand.RECORD_FONCTION_SELECT); + sendCommand(RotelCommand.RECORD_FONCTION_SELECT); Thread.sleep(100); } } else { - connector.sendCommand(RotelCommand.RECORD_FONCTION_SELECT); + sendCommand(RotelCommand.RECORD_FONCTION_SELECT); Thread.sleep(100); } - if (connector.getModel().hasToneControl()) { - if (connector.getModel() == RotelModel.RSX1065) { + if (model.hasToneControl()) { + if (model == RotelModel.RSX1065) { // No tone control select command - connector.sendCommand(RotelCommand.TREBLE_DOWN); + sendCommand(RotelCommand.TREBLE_DOWN); Thread.sleep(100); - connector.sendCommand(RotelCommand.TREBLE_UP); + sendCommand(RotelCommand.TREBLE_UP); Thread.sleep(100); - connector.sendCommand(RotelCommand.BASS_DOWN); + sendCommand(RotelCommand.BASS_DOWN); Thread.sleep(100); - connector.sendCommand(RotelCommand.BASS_UP); + sendCommand(RotelCommand.BASS_UP); Thread.sleep(100); } else { selectFeature(2, null, RotelCommand.TONE_CONTROL_SELECT); @@ -1820,131 +1821,125 @@ public class RotelHandler extends BaseThingHandler implements RotelMessageEventL } break; case ASCII_V1: - if (connector.getModel() != RotelModel.RAP1580 && connector.getModel() != RotelModel.RDD1580 - && connector.getModel() != RotelModel.RSP1576 - && connector.getModel() != RotelModel.RSP1582) { - connector.sendCommand(RotelCommand.UPDATE_AUTO); + if (model != RotelModel.RAP1580 && model != RotelModel.RDD1580 + && model != RotelModel.RSP1576 && model != RotelModel.RSP1582) { + sendCommand(RotelCommand.UPDATE_AUTO); Thread.sleep(SLEEP_INTV); } - if (connector.getModel().hasSourceControl()) { - connector.sendCommand(RotelCommand.SOURCE); + if (model.hasSourceControl()) { + sendCommand(RotelCommand.SOURCE); Thread.sleep(SLEEP_INTV); } - if (connector.getModel().hasVolumeControl() || connector.getModel().hasToneControl()) { - if (connector.getModel().hasVolumeControl() - && connector.getModel() != RotelModel.RAP1580 - && connector.getModel() != RotelModel.RSP1576 - && connector.getModel() != RotelModel.RSP1582) { - connector.sendCommand(RotelCommand.VOLUME_GET_MIN); + if (model.hasVolumeControl() || model.hasToneControl()) { + if (model.hasVolumeControl() && model != RotelModel.RAP1580 + && model != RotelModel.RSP1576 && model != RotelModel.RSP1582) { + sendCommand(RotelCommand.VOLUME_GET_MIN); Thread.sleep(SLEEP_INTV); - connector.sendCommand(RotelCommand.VOLUME_GET_MAX); + sendCommand(RotelCommand.VOLUME_GET_MAX); Thread.sleep(SLEEP_INTV); } - if (connector.getModel().hasToneControl()) { - connector.sendCommand(RotelCommand.TONE_MAX); + if (model.hasToneControl()) { + sendCommand(RotelCommand.TONE_MAX); Thread.sleep(SLEEP_INTV); } // Wait enough to be sure to get the min/max values requested just before Thread.sleep(250); - if (connector.getModel().hasVolumeControl()) { - connector.sendCommand(RotelCommand.VOLUME_GET); + if (model.hasVolumeControl()) { + sendCommand(RotelCommand.VOLUME_GET); Thread.sleep(SLEEP_INTV); - if (connector.getModel() != RotelModel.RA11 - && connector.getModel() != RotelModel.RA12 - && connector.getModel() != RotelModel.RCX1500) { - connector.sendCommand(RotelCommand.MUTE); + if (model != RotelModel.RA11 && model != RotelModel.RA12 + && model != RotelModel.RCX1500) { + sendCommand(RotelCommand.MUTE); Thread.sleep(SLEEP_INTV); } } - if (connector.getModel().hasToneControl()) { - connector.sendCommand(RotelCommand.BASS); + if (model.hasToneControl()) { + sendCommand(RotelCommand.BASS); Thread.sleep(SLEEP_INTV); - connector.sendCommand(RotelCommand.TREBLE); + sendCommand(RotelCommand.TREBLE); Thread.sleep(SLEEP_INTV); - connector.sendCommand(RotelCommand.TONE_CONTROLS); + sendCommand(RotelCommand.TONE_CONTROLS); Thread.sleep(SLEEP_INTV); } } - if (connector.getModel().hasBalanceControl()) { - connector.sendCommand(RotelCommand.BALANCE); + if (model.hasBalanceControl()) { + sendCommand(RotelCommand.BALANCE); Thread.sleep(SLEEP_INTV); } - if (connector.getModel().hasPlayControl()) { - if (connector.getModel() != RotelModel.RCD1570 - && connector.getModel() != RotelModel.RCD1572 - && (connector.getModel() != RotelModel.RCX1500 - || !source.getName().equals("CD"))) { - connector.sendCommand(RotelCommand.PLAY_STATUS); + if (model.hasPlayControl()) { + if (model != RotelModel.RCD1570 && model != RotelModel.RCD1572 + && (model != RotelModel.RCX1500 || !source.getName().equals("CD"))) { + sendCommand(RotelCommand.PLAY_STATUS); Thread.sleep(SLEEP_INTV); } else { - connector.sendCommand(RotelCommand.CD_PLAY_STATUS); + sendCommand(RotelCommand.CD_PLAY_STATUS); Thread.sleep(SLEEP_INTV); } } - if (connector.getModel().hasDspControl()) { - connector.sendCommand(RotelCommand.DSP_MODE); + if (model.hasDspControl()) { + sendCommand(RotelCommand.DSP_MODE); Thread.sleep(SLEEP_INTV); } - if (connector.getModel().canGetFrequency()) { - connector.sendCommand(RotelCommand.FREQUENCY); + if (model.canGetFrequency()) { + sendCommand(RotelCommand.FREQUENCY); Thread.sleep(SLEEP_INTV); } - if (connector.getModel().hasDimmerControl() && connector.getModel().canGetDimmerLevel()) { - connector.sendCommand(RotelCommand.DIMMER_LEVEL_GET); + if (model.hasDimmerControl() && model.canGetDimmerLevel()) { + sendCommand(RotelCommand.DIMMER_LEVEL_GET); Thread.sleep(SLEEP_INTV); } - if (connector.getModel().hasSpeakerGroups()) { - connector.sendCommand(RotelCommand.SPEAKER); + if (model.hasSpeakerGroups()) { + sendCommand(RotelCommand.SPEAKER); Thread.sleep(SLEEP_INTV); } break; case ASCII_V2: - connector.sendCommand(RotelCommand.UPDATE_AUTO); + sendCommand(RotelCommand.UPDATE_AUTO); Thread.sleep(SLEEP_INTV); - if (connector.getModel().hasSourceControl()) { - connector.sendCommand(RotelCommand.SOURCE); + if (model.hasSourceControl()) { + sendCommand(RotelCommand.SOURCE); Thread.sleep(SLEEP_INTV); } - if (connector.getModel().hasVolumeControl()) { - connector.sendCommand(RotelCommand.VOLUME_GET); + if (model.hasVolumeControl()) { + sendCommand(RotelCommand.VOLUME_GET); Thread.sleep(SLEEP_INTV); - connector.sendCommand(RotelCommand.MUTE); + sendCommand(RotelCommand.MUTE); Thread.sleep(SLEEP_INTV); } - if (connector.getModel().hasToneControl()) { - connector.sendCommand(RotelCommand.BASS); + if (model.hasToneControl()) { + sendCommand(RotelCommand.BASS); Thread.sleep(SLEEP_INTV); - connector.sendCommand(RotelCommand.TREBLE); + sendCommand(RotelCommand.TREBLE); Thread.sleep(SLEEP_INTV); - connector.sendCommand(RotelCommand.TCBYPASS); + sendCommand(RotelCommand.TCBYPASS); Thread.sleep(SLEEP_INTV); } - if (connector.getModel().hasBalanceControl()) { - connector.sendCommand(RotelCommand.BALANCE); + if (model.hasBalanceControl()) { + sendCommand(RotelCommand.BALANCE); Thread.sleep(SLEEP_INTV); } - if (connector.getModel().hasPlayControl()) { - connector.sendCommand(RotelCommand.PLAY_STATUS); + if (model.hasPlayControl()) { + sendCommand(RotelCommand.PLAY_STATUS); Thread.sleep(SLEEP_INTV); - if (source.getName().equals("CD") && !connector.getModel().hasSourceControl()) { - connector.sendCommand(RotelCommand.TRACK); + if (source.getName().equals("CD") && !model.hasSourceControl()) { + sendCommand(RotelCommand.TRACK); Thread.sleep(SLEEP_INTV); } } - if (connector.getModel().hasDspControl()) { - connector.sendCommand(RotelCommand.DSP_MODE); + if (model.hasDspControl()) { + sendCommand(RotelCommand.DSP_MODE); Thread.sleep(SLEEP_INTV); } - if (connector.getModel().canGetFrequency()) { - connector.sendCommand(RotelCommand.FREQUENCY); + if (model.canGetFrequency()) { + sendCommand(RotelCommand.FREQUENCY); Thread.sleep(SLEEP_INTV); } - if (connector.getModel().hasDimmerControl() && connector.getModel().canGetDimmerLevel()) { - connector.sendCommand(RotelCommand.DIMMER_LEVEL_GET); + if (model.hasDimmerControl() && model.canGetDimmerLevel()) { + sendCommand(RotelCommand.DIMMER_LEVEL_GET); Thread.sleep(SLEEP_INTV); } - if (connector.getModel().hasSpeakerGroups()) { - connector.sendCommand(RotelCommand.SPEAKER); + if (model.hasSpeakerGroups()) { + sendCommand(RotelCommand.SPEAKER); Thread.sleep(SLEEP_INTV); } break; @@ -1983,14 +1978,12 @@ public class RotelHandler extends BaseThingHandler implements RotelMessageEventL synchronized (sequenceLock) { logger.debug("Power ON zone 2 job"); try { - if (connector.getProtocol() == RotelProtocol.HEX - && connector.getModel().getNbAdditionalZones() >= 1) { - selectZone(2, connector.getModel().getZoneSelectCmd()); - connector.sendCommand(connector.getModel().hasZone2Commands() ? RotelCommand.ZONE2_VOLUME_DOWN - : RotelCommand.VOLUME_DOWN); + if (protocol == RotelProtocol.HEX && model.getNbAdditionalZones() >= 1) { + selectZone(2, model.getZoneSelectCmd()); + sendCommand( + model.hasZone2Commands() ? RotelCommand.ZONE2_VOLUME_DOWN : RotelCommand.VOLUME_DOWN); Thread.sleep(100); - connector.sendCommand(connector.getModel().hasZone2Commands() ? RotelCommand.ZONE2_VOLUME_UP - : RotelCommand.VOLUME_UP); + sendCommand(model.hasZone2Commands() ? RotelCommand.ZONE2_VOLUME_UP : RotelCommand.VOLUME_UP); Thread.sleep(100); } } catch (RotelException e) { @@ -2027,14 +2020,12 @@ public class RotelHandler extends BaseThingHandler implements RotelMessageEventL synchronized (sequenceLock) { logger.debug("Power ON zone 3 job"); try { - if (connector.getProtocol() == RotelProtocol.HEX - && connector.getModel().getNbAdditionalZones() >= 2) { - selectZone(3, connector.getModel().getZoneSelectCmd()); - connector.sendCommand(connector.getModel().hasZone3Commands() ? RotelCommand.ZONE3_VOLUME_DOWN - : RotelCommand.VOLUME_DOWN); + if (protocol == RotelProtocol.HEX && model.getNbAdditionalZones() >= 2) { + selectZone(3, model.getZoneSelectCmd()); + sendCommand( + model.hasZone3Commands() ? RotelCommand.ZONE3_VOLUME_DOWN : RotelCommand.VOLUME_DOWN); Thread.sleep(100); - connector.sendCommand(connector.getModel().hasZone3Commands() ? RotelCommand.ZONE3_VOLUME_UP - : RotelCommand.VOLUME_UP); + sendCommand(model.hasZone3Commands() ? RotelCommand.ZONE3_VOLUME_UP : RotelCommand.VOLUME_UP); Thread.sleep(100); } } catch (RotelException e) { @@ -2071,14 +2062,12 @@ public class RotelHandler extends BaseThingHandler implements RotelMessageEventL synchronized (sequenceLock) { logger.debug("Power ON zone 4 job"); try { - if (connector.getProtocol() == RotelProtocol.HEX - && connector.getModel().getNbAdditionalZones() >= 3) { - selectZone(4, connector.getModel().getZoneSelectCmd()); - connector.sendCommand(connector.getModel().hasZone4Commands() ? RotelCommand.ZONE4_VOLUME_DOWN - : RotelCommand.VOLUME_DOWN); + if (protocol == RotelProtocol.HEX && model.getNbAdditionalZones() >= 3) { + selectZone(4, model.getZoneSelectCmd()); + sendCommand( + model.hasZone4Commands() ? RotelCommand.ZONE4_VOLUME_DOWN : RotelCommand.VOLUME_DOWN); Thread.sleep(100); - connector.sendCommand(connector.getModel().hasZone4Commands() ? RotelCommand.ZONE4_VOLUME_UP - : RotelCommand.VOLUME_UP); + sendCommand(model.hasZone4Commands() ? RotelCommand.ZONE4_VOLUME_UP : RotelCommand.VOLUME_UP); Thread.sleep(100); } } catch (RotelException e) { @@ -2121,7 +2110,7 @@ public class RotelHandler extends BaseThingHandler implements RotelMessageEventL synchronized (sequenceLock) { schedulePowerOffJob(true); try { - connector.sendCommand(connector.getModel().getPowerStateCmd()); + sendCommand(model.getPowerStateCmd()); } catch (RotelException e) { error = "@text/offline.comm-error-first-command-after-reconnection"; logger.debug("First command after connection failed", e); @@ -2321,11 +2310,9 @@ public class RotelHandler extends BaseThingHandler implements RotelMessageEventL state = new StringType(frontPanelLine2); break; case CHANNEL_BRIGHTNESS: - if (isPowerOn() && connector.getModel().hasDimmerControl()) { - long dimmerPct = Math.round((double) (brightness - connector.getModel().getDimmerLevelMin()) - / (double) (connector.getModel().getDimmerLevelMax() - - connector.getModel().getDimmerLevelMin()) - * 100.0); + if (isPowerOn() && model.hasDimmerControl()) { + long dimmerPct = Math.round((double) (brightness - model.getDimmerLevelMin()) + / (double) (model.getDimmerLevelMax() - model.getDimmerLevelMin()) * 100.0); state = new PercentType(BigDecimal.valueOf(dimmerPct)); } break; @@ -2371,8 +2358,7 @@ public class RotelHandler extends BaseThingHandler implements RotelMessageEventL * @return the command */ private RotelCommand getPowerOnCommand() { - return connector.getModel().hasOtherThanPrimaryCommands() ? RotelCommand.MAIN_ZONE_POWER_ON - : RotelCommand.POWER_ON; + return model.hasOtherThanPrimaryCommands() ? RotelCommand.MAIN_ZONE_POWER_ON : RotelCommand.POWER_ON; } /** @@ -2381,8 +2367,7 @@ public class RotelHandler extends BaseThingHandler implements RotelMessageEventL * @return the command */ private RotelCommand getPowerOffCommand() { - return connector.getModel().hasOtherThanPrimaryCommands() ? RotelCommand.MAIN_ZONE_POWER_OFF - : RotelCommand.POWER_OFF; + return model.hasOtherThanPrimaryCommands() ? RotelCommand.MAIN_ZONE_POWER_OFF : RotelCommand.POWER_OFF; } /** @@ -2391,8 +2376,7 @@ public class RotelHandler extends BaseThingHandler implements RotelMessageEventL * @return the command */ private RotelCommand getVolumeUpCommand() { - return connector.getModel().hasOtherThanPrimaryCommands() ? RotelCommand.MAIN_ZONE_VOLUME_UP - : RotelCommand.VOLUME_UP; + return model.hasOtherThanPrimaryCommands() ? RotelCommand.MAIN_ZONE_VOLUME_UP : RotelCommand.VOLUME_UP; } /** @@ -2401,8 +2385,7 @@ public class RotelHandler extends BaseThingHandler implements RotelMessageEventL * @return the command */ private RotelCommand getVolumeDownCommand() { - return connector.getModel().hasOtherThanPrimaryCommands() ? RotelCommand.MAIN_ZONE_VOLUME_DOWN - : RotelCommand.VOLUME_DOWN; + return model.hasOtherThanPrimaryCommands() ? RotelCommand.MAIN_ZONE_VOLUME_DOWN : RotelCommand.VOLUME_DOWN; } /** @@ -2411,8 +2394,7 @@ public class RotelHandler extends BaseThingHandler implements RotelMessageEventL * @return the command */ private RotelCommand getMuteOnCommand() { - return connector.getModel().hasOtherThanPrimaryCommands() ? RotelCommand.MAIN_ZONE_MUTE_ON - : RotelCommand.MUTE_ON; + return model.hasOtherThanPrimaryCommands() ? RotelCommand.MAIN_ZONE_MUTE_ON : RotelCommand.MUTE_ON; } /** @@ -2421,8 +2403,7 @@ public class RotelHandler extends BaseThingHandler implements RotelMessageEventL * @return the command */ private RotelCommand getMuteOffCommand() { - return connector.getModel().hasOtherThanPrimaryCommands() ? RotelCommand.MAIN_ZONE_MUTE_OFF - : RotelCommand.MUTE_OFF; + return model.hasOtherThanPrimaryCommands() ? RotelCommand.MAIN_ZONE_MUTE_OFF : RotelCommand.MUTE_OFF; } /** @@ -2431,7 +2412,38 @@ public class RotelHandler extends BaseThingHandler implements RotelMessageEventL * @return the command */ private RotelCommand getMuteToggleCommand() { - return connector.getModel().hasOtherThanPrimaryCommands() ? RotelCommand.MAIN_ZONE_MUTE_TOGGLE - : RotelCommand.MUTE_TOGGLE; + return model.hasOtherThanPrimaryCommands() ? RotelCommand.MAIN_ZONE_MUTE_TOGGLE : RotelCommand.MUTE_TOGGLE; + } + + private void sendCommand(RotelCommand cmd) throws RotelException { + sendCommand(cmd, null); + } + + /** + * Request the Rotel device to execute a command + * + * @param cmd the command to execute + * @param value the integer value to consider for volume, bass or treble adjustment + * + * @throws RotelException - In case of any problem + */ + private void sendCommand(RotelCommand cmd, @Nullable Integer value) throws RotelException { + byte[] message; + try { + message = protocolHandler.buildCommandMessage(cmd, value); + } catch (RotelException e) { + // Command not supported + logger.debug("sendCommand: {}", e.getMessage()); + return; + } + connector.writeOutput(cmd.getName(), message); + + if (connector instanceof RotelSimuConnector) { + if ((protocol == RotelProtocol.HEX && cmd.getHexType() != 0) + || (protocol == RotelProtocol.ASCII_V1 && cmd.getAsciiCommandV1() != null) + || (protocol == RotelProtocol.ASCII_V2 && cmd.getAsciiCommandV2() != null)) { + ((RotelSimuConnector) connector).buildFeedbackMessage(cmd, value); + } + } } } diff --git a/bundles/org.openhab.binding.rotel/src/main/java/org/openhab/binding/rotel/internal/protocol/RotelAbstractProtocolHandler.java b/bundles/org.openhab.binding.rotel/src/main/java/org/openhab/binding/rotel/internal/protocol/RotelAbstractProtocolHandler.java new file mode 100644 index 000000000..db9024631 --- /dev/null +++ b/bundles/org.openhab.binding.rotel/src/main/java/org/openhab/binding/rotel/internal/protocol/RotelAbstractProtocolHandler.java @@ -0,0 +1,133 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.rotel.internal.protocol; + +import static org.openhab.binding.rotel.internal.RotelBindingConstants.*; + +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.rotel.internal.RotelException; +import org.openhab.binding.rotel.internal.RotelModel; +import org.openhab.binding.rotel.internal.communication.RotelCommand; +import org.openhab.core.util.HexUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Abstract class for handling a Rotel protocol (build of command messages, decoding of incoming data) + * + * @author Laurent Garnier - Initial contribution + */ +@NonNullByDefault +public abstract class RotelAbstractProtocolHandler { + + private final Logger logger = LoggerFactory.getLogger(RotelAbstractProtocolHandler.class); + + protected final RotelModel model; + + private final List listeners = new ArrayList<>(); + + /** + * Constructor + * + * @param model the Rotel model in use + */ + public RotelAbstractProtocolHandler(RotelModel model) { + this.model = model; + } + + public abstract RotelProtocol getProtocol(); + + /** + * Build the message associated to a Rotel command + * + * @param cmd the command to execute + * @param value the integer value to consider for volume, bass or treble adjustment + * + * @throws RotelException - In case the command is not supported by the protocol + */ + public abstract byte[] buildCommandMessage(RotelCommand cmd, @Nullable Integer value) throws RotelException; + + public abstract void handleIncomingData(byte[] inDataBuffer, int length); + + public void handleInIncomingError() { + dispatchKeyValue(KEY_ERROR, MSG_VALUE_ON); + } + + /** + * Analyze an incoming message and dispatch corresponding (key, value) to the event listeners + * + * @param incomingMessage the received message + */ + protected void handleIncomingMessage(byte[] incomingMessage) { + logger.debug("handleIncomingMessage: bytes {}", HexUtils.bytesToHex(incomingMessage)); + + try { + validateResponse(incomingMessage); + } catch (RotelException e) { + return; + } + + handleValidMessage(incomingMessage); + } + + /** + * Validate the content of a feedback message + * + * @param responseMessage the buffer containing the feedback message + * + * @throws RotelException - If the message has unexpected content + */ + protected abstract void validateResponse(byte[] responseMessage) throws RotelException; + + /** + * Analyze a valid HEX message and dispatch corresponding (key, value) to the event listeners + * + * @param incomingMessage the received message + */ + protected abstract void handleValidMessage(byte[] incomingMessage); + + /** + * Add a listener to the list of listeners to be notified with events + * + * @param listener the listener + */ + public void addEventListener(RotelMessageEventListener listener) { + listeners.add(listener); + } + + /** + * Remove a listener from the list of listeners to be notified with events + * + * @param listener the listener + */ + public void removeEventListener(RotelMessageEventListener listener) { + listeners.remove(listener); + } + + /** + * Dispatch an event (key, value) to the event listeners + * + * @param key the key + * @param value the value + */ + protected void dispatchKeyValue(String key, String value) { + RotelMessageEvent event = new RotelMessageEvent(this, key, value); + for (int i = 0; i < listeners.size(); i++) { + listeners.get(i).onNewMessageEvent(event); + } + } +} diff --git a/bundles/org.openhab.binding.rotel/src/main/java/org/openhab/binding/rotel/internal/communication/RotelMessageEvent.java b/bundles/org.openhab.binding.rotel/src/main/java/org/openhab/binding/rotel/internal/protocol/RotelMessageEvent.java similarity index 94% rename from bundles/org.openhab.binding.rotel/src/main/java/org/openhab/binding/rotel/internal/communication/RotelMessageEvent.java rename to bundles/org.openhab.binding.rotel/src/main/java/org/openhab/binding/rotel/internal/protocol/RotelMessageEvent.java index 6ba460d9a..5b33c8685 100644 --- a/bundles/org.openhab.binding.rotel/src/main/java/org/openhab/binding/rotel/internal/communication/RotelMessageEvent.java +++ b/bundles/org.openhab.binding.rotel/src/main/java/org/openhab/binding/rotel/internal/protocol/RotelMessageEvent.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.rotel.internal.communication; +package org.openhab.binding.rotel.internal.protocol; import java.util.EventObject; diff --git a/bundles/org.openhab.binding.rotel/src/main/java/org/openhab/binding/rotel/internal/communication/RotelMessageEventListener.java b/bundles/org.openhab.binding.rotel/src/main/java/org/openhab/binding/rotel/internal/protocol/RotelMessageEventListener.java similarity index 93% rename from bundles/org.openhab.binding.rotel/src/main/java/org/openhab/binding/rotel/internal/communication/RotelMessageEventListener.java rename to bundles/org.openhab.binding.rotel/src/main/java/org/openhab/binding/rotel/internal/protocol/RotelMessageEventListener.java index c710ae04e..93dae8446 100644 --- a/bundles/org.openhab.binding.rotel/src/main/java/org/openhab/binding/rotel/internal/communication/RotelMessageEventListener.java +++ b/bundles/org.openhab.binding.rotel/src/main/java/org/openhab/binding/rotel/internal/protocol/RotelMessageEventListener.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.rotel.internal.communication; +package org.openhab.binding.rotel.internal.protocol; import java.util.EventListener; import java.util.EventObject; diff --git a/bundles/org.openhab.binding.rotel/src/main/java/org/openhab/binding/rotel/internal/communication/RotelProtocol.java b/bundles/org.openhab.binding.rotel/src/main/java/org/openhab/binding/rotel/internal/protocol/RotelProtocol.java similarity index 96% rename from bundles/org.openhab.binding.rotel/src/main/java/org/openhab/binding/rotel/internal/communication/RotelProtocol.java rename to bundles/org.openhab.binding.rotel/src/main/java/org/openhab/binding/rotel/internal/protocol/RotelProtocol.java index 07c53d1ee..04454066f 100644 --- a/bundles/org.openhab.binding.rotel/src/main/java/org/openhab/binding/rotel/internal/communication/RotelProtocol.java +++ b/bundles/org.openhab.binding.rotel/src/main/java/org/openhab/binding/rotel/internal/protocol/RotelProtocol.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.rotel.internal.communication; +package org.openhab.binding.rotel.internal.protocol; import org.eclipse.jdt.annotation.NonNullByDefault; import org.openhab.binding.rotel.internal.RotelException; diff --git a/bundles/org.openhab.binding.rotel/src/main/java/org/openhab/binding/rotel/internal/protocol/ascii/RotelAbstractAsciiProtocolHandler.java b/bundles/org.openhab.binding.rotel/src/main/java/org/openhab/binding/rotel/internal/protocol/ascii/RotelAbstractAsciiProtocolHandler.java new file mode 100644 index 000000000..71cd0f8d9 --- /dev/null +++ b/bundles/org.openhab.binding.rotel/src/main/java/org/openhab/binding/rotel/internal/protocol/ascii/RotelAbstractAsciiProtocolHandler.java @@ -0,0 +1,195 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.rotel.internal.protocol.ascii; + +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.regex.PatternSyntaxException; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.rotel.internal.RotelException; +import org.openhab.binding.rotel.internal.RotelModel; +import org.openhab.binding.rotel.internal.protocol.RotelAbstractProtocolHandler; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Abstract class for handling a Rotel ASCII protocol (build of command messages, decoding of incoming data) + * + * @author Laurent Garnier - Initial contribution + */ +@NonNullByDefault +public abstract class RotelAbstractAsciiProtocolHandler extends RotelAbstractProtocolHandler { + + /** Special characters that can be found in the feedback messages for several devices using the ASCII protocol */ + public static final byte[][] SPECIAL_CHARACTERS = { { (byte) 0xEE, (byte) 0x82, (byte) 0x85 }, + { (byte) 0xEE, (byte) 0x82, (byte) 0x84 }, { (byte) 0xEE, (byte) 0x82, (byte) 0x92 }, + { (byte) 0xEE, (byte) 0x82, (byte) 0x87 }, { (byte) 0xEE, (byte) 0x82, (byte) 0x8E }, + { (byte) 0xEE, (byte) 0x82, (byte) 0x89 }, { (byte) 0xEE, (byte) 0x82, (byte) 0x93 }, + { (byte) 0xEE, (byte) 0x82, (byte) 0x8C }, { (byte) 0xEE, (byte) 0x82, (byte) 0x8F }, + { (byte) 0xEE, (byte) 0x82, (byte) 0x8A }, { (byte) 0xEE, (byte) 0x82, (byte) 0x8B }, + { (byte) 0xEE, (byte) 0x82, (byte) 0x81 }, { (byte) 0xEE, (byte) 0x82, (byte) 0x82 }, + { (byte) 0xEE, (byte) 0x82, (byte) 0x83 }, { (byte) 0xEE, (byte) 0x82, (byte) 0x94 }, + { (byte) 0xEE, (byte) 0x82, (byte) 0x97 }, { (byte) 0xEE, (byte) 0x82, (byte) 0x98 }, + { (byte) 0xEE, (byte) 0x82, (byte) 0x80 }, { (byte) 0xEE, (byte) 0x82, (byte) 0x99 }, + { (byte) 0xEE, (byte) 0x82, (byte) 0x9A }, { (byte) 0xEE, (byte) 0x82, (byte) 0x88 }, + { (byte) 0xEE, (byte) 0x82, (byte) 0x95 }, { (byte) 0xEE, (byte) 0x82, (byte) 0x96 }, + { (byte) 0xEE, (byte) 0x82, (byte) 0x90 }, { (byte) 0xEE, (byte) 0x82, (byte) 0x91 }, + { (byte) 0xEE, (byte) 0x82, (byte) 0x8D }, { (byte) 0xEE, (byte) 0x80, (byte) 0x80, (byte) 0xEE, + (byte) 0x80, (byte) 0x81, (byte) 0xEE, (byte) 0x80, (byte) 0x82 } }; + + /** Special characters that can be found in the feedback messages for the RCD-1572 */ + public static final byte[][] SPECIAL_CHARACTERS_RCD1572 = { { (byte) 0xC2, (byte) 0x8C }, + { (byte) 0xC2, (byte) 0x54 }, { (byte) 0xC2, (byte) 0x81 }, { (byte) 0xC2, (byte) 0x82 }, + { (byte) 0xC2, (byte) 0x83 } }; + + /** Empty table of special characters */ + public static final byte[][] NO_SPECIAL_CHARACTERS = {}; + + private final Logger logger = LoggerFactory.getLogger(RotelAbstractAsciiProtocolHandler.class); + + private final char terminatingChar; + private final int size; + private final byte[] dataBuffer; + + private int index; + + /** + * Constructor + * + * @param model the Rotel model in use + * @param protocol the protocol to be used + */ + public RotelAbstractAsciiProtocolHandler(RotelModel model, char terminatingChar) { + super(model); + this.terminatingChar = terminatingChar; + this.size = 64; + this.dataBuffer = new byte[size]; + this.index = 0; + } + + @Override + public void handleIncomingData(byte[] inDataBuffer, int length) { + for (int i = 0; i < length; i++) { + if (index < size) { + dataBuffer[index++] = inDataBuffer[i]; + } + if (inDataBuffer[i] == terminatingChar) { + if (index >= size) { + dataBuffer[index - 1] = (byte) terminatingChar; + } + byte[] msg = Arrays.copyOf(dataBuffer, index); + handleIncomingMessage(msg); + index = 0; + } + } + } + + /** + * Validate the content of a feedback message + * + * @param responseMessage the buffer containing the feedback message + * + * @throws RotelException - If the message has unexpected content + */ + @Override + protected void validateResponse(byte[] responseMessage) throws RotelException { + // Check minimum message length + if (responseMessage.length < 1) { + logger.debug("Unexpected message length: {}", responseMessage.length); + throw new RotelException("Unexpected message length"); + } + + if (responseMessage[responseMessage.length - 1] != '!' && responseMessage[responseMessage.length - 1] != '$') { + logger.debug("Unexpected ending character in response: {}", + Integer.toHexString(responseMessage[responseMessage.length - 1] & 0x000000FF)); + throw new RotelException("Unexpected ending character in response"); + } + } + + /** + * Analyze a valid ASCII message and dispatch corresponding (key, value) to the event listeners + * + * @param incomingMessage the received message + */ + @Override + protected void handleValidMessage(byte[] incomingMessage) { + byte[] message = filterMessage(incomingMessage, model.getSpecialCharacters()); + + // Replace characters with code < 32 by a space before converting to a string + for (int i = 0; i < message.length; i++) { + if (message[i] < 0x20) { + message[i] = 0x20; + } + } + + String value = new String(message, 0, message.length - 1, StandardCharsets.US_ASCII); + logger.debug("handleValidAsciiMessage: chars *{}*", value); + value = value.trim(); + if (value.isEmpty()) { + return; + } + try { + String[] splittedValue = value.split("="); + if (splittedValue.length != 2) { + logger.debug("handleValidAsciiMessage: ignored message {}", value); + } else { + dispatchKeyValue(splittedValue[0].trim().toLowerCase(), splittedValue[1]); + } + } catch (PatternSyntaxException e) { + logger.debug("handleValidAsciiMessage: ignored message {}", value); + } + } + + /** + * Suppress certain sequences of bytes from a message + * + * @param message the message as a table of bytes + * @param bytesSequences the table containing the sequence of bytes to be ignored + * + * @return the message without the unexpected sequence of bytes + */ + private byte[] filterMessage(byte[] message, byte[][] bytesSequences) { + if (bytesSequences.length == 0) { + return message; + } + byte[] filteredMsg = new byte[message.length]; + int srcIdx = 0; + int dstIdx = 0; + while (srcIdx < message.length) { + int ignoredLength = 0; + for (int i = 0; i < bytesSequences.length; i++) { + int size = bytesSequences[i].length; + if ((message.length - srcIdx) >= size) { + boolean match = true; + for (int j = 0; j < size; j++) { + if (message[srcIdx + j] != bytesSequences[i][j]) { + match = false; + break; + } + } + if (match) { + ignoredLength = size; + break; + } + } + } + if (ignoredLength > 0) { + srcIdx += ignoredLength; + } else { + filteredMsg[dstIdx++] = message[srcIdx++]; + } + } + return Arrays.copyOf(filteredMsg, dstIdx); + } +} diff --git a/bundles/org.openhab.binding.rotel/src/main/java/org/openhab/binding/rotel/internal/protocol/ascii/RotelAsciiV1ProtocolHandler.java b/bundles/org.openhab.binding.rotel/src/main/java/org/openhab/binding/rotel/internal/protocol/ascii/RotelAsciiV1ProtocolHandler.java new file mode 100644 index 000000000..5e47063cc --- /dev/null +++ b/bundles/org.openhab.binding.rotel/src/main/java/org/openhab/binding/rotel/internal/protocol/ascii/RotelAsciiV1ProtocolHandler.java @@ -0,0 +1,100 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.rotel.internal.protocol.ascii; + +import java.nio.charset.StandardCharsets; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.rotel.internal.RotelException; +import org.openhab.binding.rotel.internal.RotelModel; +import org.openhab.binding.rotel.internal.communication.RotelCommand; +import org.openhab.binding.rotel.internal.protocol.RotelProtocol; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Class for handling the Rotel ASCII V1 protocol (build of command messages, decoding of incoming data) + * + * @author Laurent Garnier - Initial contribution + */ +@NonNullByDefault +public class RotelAsciiV1ProtocolHandler extends RotelAbstractAsciiProtocolHandler { + + private static final char CHAR_END_RESPONSE = '!'; + + private final Logger logger = LoggerFactory.getLogger(RotelAsciiV1ProtocolHandler.class); + + /** + * Constructor + * + * @param model the Rotel model in use + */ + public RotelAsciiV1ProtocolHandler(RotelModel model) { + super(model, CHAR_END_RESPONSE); + } + + @Override + public RotelProtocol getProtocol() { + return RotelProtocol.ASCII_V1; + } + + @Override + public byte[] buildCommandMessage(RotelCommand cmd, @Nullable Integer value) throws RotelException { + String messageStr = cmd.getAsciiCommandV1(); + if (messageStr == null) { + throw new RotelException("Command \"" + cmd.getName() + "\" ignored: not available for ASCII V1 protocol"); + } + if (value != null) { + switch (cmd) { + case VOLUME_SET: + messageStr += String.format("%d", value); + break; + case BASS_SET: + case TREBLE_SET: + if (value == 0) { + messageStr += "000"; + } else if (value > 0) { + messageStr += String.format("+%02d", value); + } else { + messageStr += String.format("-%02d", -value); + } + break; + case BALANCE_SET: + if (value == 0) { + messageStr += "000"; + } else if (value > 0) { + messageStr += String.format("R%02d", value); + } else { + messageStr += String.format("L%02d", -value); + } + break; + case DIMMER_LEVEL_SET: + if (value > 0 && model.getDimmerLevelMin() < 0) { + messageStr += String.format("+%d", value); + } else { + messageStr += String.format("%d", value); + } + break; + default: + break; + } + } + if (!messageStr.endsWith("?")) { + messageStr += "!"; + } + byte[] message = messageStr.getBytes(StandardCharsets.US_ASCII); + logger.debug("Command \"{}\" => {}", cmd.getName(), messageStr); + return message; + } +} diff --git a/bundles/org.openhab.binding.rotel/src/main/java/org/openhab/binding/rotel/internal/protocol/ascii/RotelAsciiV2ProtocolHandler.java b/bundles/org.openhab.binding.rotel/src/main/java/org/openhab/binding/rotel/internal/protocol/ascii/RotelAsciiV2ProtocolHandler.java new file mode 100644 index 000000000..c90f5a8cd --- /dev/null +++ b/bundles/org.openhab.binding.rotel/src/main/java/org/openhab/binding/rotel/internal/protocol/ascii/RotelAsciiV2ProtocolHandler.java @@ -0,0 +1,100 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.rotel.internal.protocol.ascii; + +import java.nio.charset.StandardCharsets; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.rotel.internal.RotelException; +import org.openhab.binding.rotel.internal.RotelModel; +import org.openhab.binding.rotel.internal.communication.RotelCommand; +import org.openhab.binding.rotel.internal.protocol.RotelProtocol; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Class for handling the Rotel ASCII V2 protocol (build of command messages, decoding of incoming data) + * + * @author Laurent Garnier - Initial contribution + */ +@NonNullByDefault +public class RotelAsciiV2ProtocolHandler extends RotelAbstractAsciiProtocolHandler { + + private static final char CHAR_END_RESPONSE = '$'; + + private final Logger logger = LoggerFactory.getLogger(RotelAsciiV2ProtocolHandler.class); + + /** + * Constructor + * + * @param model the Rotel model in use + */ + public RotelAsciiV2ProtocolHandler(RotelModel model) { + super(model, CHAR_END_RESPONSE); + } + + @Override + public RotelProtocol getProtocol() { + return RotelProtocol.ASCII_V2; + } + + @Override + public byte[] buildCommandMessage(RotelCommand cmd, @Nullable Integer value) throws RotelException { + String messageStr = cmd.getAsciiCommandV2(); + if (messageStr == null) { + throw new RotelException("Command \"" + cmd.getName() + "\" ignored: not available for ASCII V2 protocol"); + } + if (value != null) { + switch (cmd) { + case VOLUME_SET: + messageStr += String.format("%02d", value); + break; + case BASS_SET: + case TREBLE_SET: + if (value == 0) { + messageStr += "000"; + } else if (value > 0) { + messageStr += String.format("+%02d", value); + } else { + messageStr += String.format("-%02d", -value); + } + break; + case BALANCE_SET: + if (value == 0) { + messageStr += "000"; + } else if (value > 0) { + messageStr += String.format("r%02d", value); + } else { + messageStr += String.format("l%02d", -value); + } + break; + case DIMMER_LEVEL_SET: + if (value > 0 && model.getDimmerLevelMin() < 0) { + messageStr += String.format("+%d", value); + } else { + messageStr += String.format("%d", value); + } + break; + default: + break; + } + } + if (!messageStr.endsWith("?")) { + messageStr += "!"; + } + byte[] message = messageStr.getBytes(StandardCharsets.US_ASCII); + logger.debug("Command \"{}\" => {}", cmd.getName(), messageStr); + return message; + } +} diff --git a/bundles/org.openhab.binding.rotel/src/main/java/org/openhab/binding/rotel/internal/protocol/hex/RotelHexProtocolHandler.java b/bundles/org.openhab.binding.rotel/src/main/java/org/openhab/binding/rotel/internal/protocol/hex/RotelHexProtocolHandler.java new file mode 100644 index 000000000..d37203347 --- /dev/null +++ b/bundles/org.openhab.binding.rotel/src/main/java/org/openhab/binding/rotel/internal/protocol/hex/RotelHexProtocolHandler.java @@ -0,0 +1,778 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.rotel.internal.protocol.hex; + +import static org.openhab.binding.rotel.internal.RotelBindingConstants.*; + +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.rotel.internal.RotelException; +import org.openhab.binding.rotel.internal.RotelModel; +import org.openhab.binding.rotel.internal.communication.RotelCommand; +import org.openhab.binding.rotel.internal.communication.RotelDsp; +import org.openhab.binding.rotel.internal.communication.RotelFlagsMapping; +import org.openhab.binding.rotel.internal.communication.RotelSource; +import org.openhab.binding.rotel.internal.protocol.RotelAbstractProtocolHandler; +import org.openhab.binding.rotel.internal.protocol.RotelProtocol; +import org.openhab.core.util.HexUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Class for handling the Rotel HEX protocol (build of command messages, decoding of incoming data) + * + * @author Laurent Garnier - Initial contribution + */ +@NonNullByDefault +public class RotelHexProtocolHandler extends RotelAbstractProtocolHandler { + + public static final byte START = (byte) 0xFE; + + private static final String KEY1_HEX_VOLUME = "volume "; + private static final String KEY2_HEX_VOLUME = "vol "; + private static final String KEY_HEX_MUTE = "mute "; + private static final String KEY1_HEX_BASS = "bass "; + private static final String KEY2_HEX_BASS = "lf "; + private static final String KEY1_HEX_TREBLE = "treble "; + private static final String KEY2_HEX_TREBLE = "hf "; + private static final String KEY_HEX_MULTI_IN = "multi in "; + private static final String KEY_HEX_STEREO = "stereo"; + private static final String KEY1_HEX_3CH = "3 stereo"; + private static final String KEY2_HEX_3CH = "dolby 3 stereo"; + private static final String KEY_HEX_5CH = "5ch stereo"; + private static final String KEY_HEX_7CH = "7ch stereo"; + private static final String KEY_HEX_MUSIC1 = "music 1"; + private static final String KEY_HEX_MUSIC2 = "music 2"; + private static final String KEY_HEX_MUSIC3 = "music 3"; + private static final String KEY_HEX_MUSIC4 = "music 4"; + private static final String KEY_HEX_DSP1 = "dsp 1"; + private static final String KEY_HEX_DSP2 = "dsp 2"; + private static final String KEY_HEX_DSP3 = "dsp 3"; + private static final String KEY_HEX_DSP4 = "dsp 4"; + private static final String KEY1_HEX_PROLOGIC = "prologic emu"; + private static final String KEY2_HEX_PROLOGIC = "dolby pro logic"; + private static final String KEY1_HEX_PLII_CINEMA = "prologic cin"; + private static final String KEY2_HEX_PLII_CINEMA = "dolby pl c"; + private static final String KEY1_HEX_PLII_MUSIC = "prologic mus"; + private static final String KEY2_HEX_PLII_MUSIC = "dolby pl m"; + private static final String KEY1_HEX_PLII_GAME = "prologic gam"; + private static final String KEY2_HEX_PLII_GAME = "dolby pl g"; + private static final String KEY1_HEX_PLIIX_CINEMA = "pl x cinema"; + private static final String KEY2_HEX_PLIIX_CINEMA = "dolby pl x c"; + private static final String KEY1_HEX_PLIIX_MUSIC = "pl x music"; + private static final String KEY2_HEX_PLIIX_MUSIC = "dolby pl x m"; + private static final String KEY1_HEX_PLIIX_GAME = "pl x game"; + private static final String KEY2_HEX_PLIIX_GAME = "dolby pl x g"; + private static final String KEY_HEX_PLIIZ = "dolby pl z"; + private static final String KEY1_HEX_DTS_NEO6_CINEMA = "neo 6 cinema"; + private static final String KEY2_HEX_DTS_NEO6_CINEMA = "dts neo:6 c"; + private static final String KEY1_HEX_DTS_NEO6_MUSIC = "neo 6 music"; + private static final String KEY2_HEX_DTS_NEO6_MUSIC = "dts neo:6 m"; + private static final String KEY_HEX_DTS = "dts"; + private static final String KEY_HEX_DTS_ES = "dts-es"; + private static final String KEY_HEX_DTS_96 = "dts 96"; + private static final String KEY_HEX_DD = "dolby digital"; + private static final String KEY_HEX_DD_EX = "dolby d ex"; + private static final String KEY_HEX_PCM = "pcm"; + private static final String KEY_HEX_LPCM = "lpcm"; + private static final String KEY_HEX_MPEG = "mpeg"; + private static final String KEY_HEX_BYPASS = "bypass"; + private static final String KEY1_HEX_ZONE2 = "zone "; + private static final String KEY2_HEX_ZONE2 = "zone2 "; + private static final String KEY_HEX_ZONE3 = "zone3 "; + private static final String KEY_HEX_ZONE4 = "zone4 "; + private static final String KEY_HEX_RECORD = "rec "; + private static final String SOURCE = "source"; + + private final Logger logger = LoggerFactory.getLogger(RotelHexProtocolHandler.class); + + private final Map sourcesLabels; + + private final int size; + private final byte[] dataBuffer; + + private boolean startCodeReached; + private int count; + private int index; + + /** + * Constructor + * + * @param model the Rotel model in use + * @param sourcesLabels the custom labels for sources + */ + public RotelHexProtocolHandler(RotelModel model, Map sourcesLabels) { + super(model); + this.sourcesLabels = sourcesLabels; + this.size = (6 + model.getRespNbChars() + model.getRespNbFlags()); + this.dataBuffer = new byte[size]; + this.startCodeReached = false; + this.count = 0; + this.index = 0; + } + + @Override + public RotelProtocol getProtocol() { + return RotelProtocol.HEX; + } + + @Override + public byte[] buildCommandMessage(RotelCommand cmd, @Nullable Integer value) throws RotelException { + if (cmd.getHexType() == 0) { + throw new RotelException("Command \"" + cmd.getName() + "\" ignored: not available for HEX protocol"); + } + final int size = 6; + byte[] message = new byte[size]; + int idx = 0; + message[idx++] = START; + message[idx++] = 3; + message[idx++] = model.getDeviceId(); + message[idx++] = cmd.getHexType(); + message[idx++] = (value == null) ? cmd.getHexKey() : (byte) (value & 0x000000FF); + final byte checksum = computeCheckSum(message, idx - 1); + if ((checksum & 0x000000FF) == 0x000000FD || (checksum & 0x000000FF) == 0x000000FE) { + message = Arrays.copyOf(message, size + 1); + message[idx++] = (byte) 0xFD; + message[idx++] = ((checksum & 0x000000FF) == 0x000000FD) ? (byte) 0 : (byte) 1; + } else { + message[idx++] = checksum; + } + logger.debug("Command \"{}\" => {}", cmd.getName(), HexUtils.bytesToHex(message)); + return message; + } + + @Override + public void handleIncomingData(byte[] inDataBuffer, int length) { + for (int i = 0; i < length; i++) { + if (inDataBuffer[i] == RotelHexProtocolHandler.START) { + startCodeReached = true; + count = 0; + index = 0; + } + if (startCodeReached) { + if (index < size) { + dataBuffer[index++] = inDataBuffer[i]; + } + if (index == 2) { + count = inDataBuffer[i]; + } else if ((count > 0) && (index == (count + 3))) { + if ((inDataBuffer[i] & 0x000000FF) == 0x000000FD) { + count++; + } else { + byte[] msg = Arrays.copyOf(dataBuffer, index); + handleIncomingMessage(msg); + startCodeReached = false; + } + } + } + } + } + + /** + * Validate the content of a feedback message + * + * @param responseMessage the buffer containing the feedback message + * + * @throws RotelException - If the message has unexpected content + */ + @Override + protected void validateResponse(byte[] responseMessage) throws RotelException { + // Check minimum message length + if (responseMessage.length < 6) { + logger.debug("Unexpected message length: {}", responseMessage.length); + throw new RotelException("Unexpected message length"); + } + + // Check START + if (responseMessage[0] != START) { + logger.debug("Unexpected START in response: {} rather than {}", + Integer.toHexString(responseMessage[0] & 0x000000FF), Integer.toHexString(START & 0x000000FF)); + throw new RotelException("Unexpected START in response"); + } + + // Check ID + if (responseMessage[2] != model.getDeviceId()) { + logger.debug("Unexpected ID in response: {} rather than {}", + Integer.toHexString(responseMessage[2] & 0x000000FF), + Integer.toHexString(model.getDeviceId() & 0x000000FF)); + throw new RotelException("Unexpected ID in response"); + } + + // Check TYPE + if (responseMessage[3] != STANDARD_RESPONSE && responseMessage[3] != TRIGGER_STATUS + && responseMessage[3] != SMART_DISPLAY_DATA_1 && responseMessage[3] != SMART_DISPLAY_DATA_2 + && responseMessage[3] != PRIMARY_CMD && responseMessage[3] != MAIN_ZONE_CMD + && responseMessage[3] != RECORD_SRC_CMD && responseMessage[3] != ZONE2_CMD + && responseMessage[3] != ZONE3_CMD && responseMessage[3] != ZONE4_CMD + && responseMessage[3] != VOLUME_CMD && responseMessage[3] != ZONE2_VOLUME_CMD + && responseMessage[3] != ZONE3_VOLUME_CMD && responseMessage[3] != ZONE4_VOLUME_CMD + && responseMessage[3] != TRIGGER_CMD) { + logger.debug("Unexpected TYPE in response: {}", Integer.toHexString(responseMessage[3] & 0x000000FF)); + throw new RotelException("Unexpected TYPE in response"); + } + + int expectedLen = (responseMessage[3] == STANDARD_RESPONSE) + ? (5 + model.getRespNbChars() + model.getRespNbFlags()) + : responseMessage.length; + + // Check COUNT + if (responseMessage[1] != (expectedLen - 3)) { + logger.debug("Unexpected COUNT in response: {} rather than {}", + Integer.toHexString(responseMessage[1] & 0x000000FF), + Integer.toHexString((expectedLen - 3) & 0x000000FF)); + throw new RotelException("Unexpected COUNT in response"); + } + + final byte checksum = computeCheckSum(responseMessage, expectedLen - 2); + if ((checksum & 0x000000FF) == 0x000000FD || (checksum & 0x000000FF) == 0x000000FE) { + expectedLen++; + } + + // Check message length + if (responseMessage.length != expectedLen) { + logger.debug("Unexpected message length: {} rather than {}", responseMessage.length, expectedLen); + throw new RotelException("Unexpected message length"); + } + + // Check sum + if ((checksum & 0x000000FF) == 0x000000FD) { + if ((responseMessage[responseMessage.length - 2] & 0x000000FF) != 0x000000FD + || (responseMessage[responseMessage.length - 1] & 0x000000FF) != 0) { + logger.debug("Invalid check sum in response: {} rather than FD00", HexUtils.bytesToHex( + Arrays.copyOfRange(responseMessage, responseMessage.length - 2, responseMessage.length))); + throw new RotelException("Invalid check sum in response"); + } + } else if ((checksum & 0x000000FF) == 0x000000FE) { + if ((responseMessage[responseMessage.length - 2] & 0x000000FF) != 0x000000FD + || (responseMessage[responseMessage.length - 1] & 0x000000FF) != 1) { + logger.debug("Invalid check sum in response: {} rather than FD01", HexUtils.bytesToHex( + Arrays.copyOfRange(responseMessage, responseMessage.length - 2, responseMessage.length))); + throw new RotelException("Invalid check sum in response"); + } + } else if ((checksum & 0x000000FF) != (responseMessage[responseMessage.length - 1] & 0x000000FF)) { + logger.debug("Invalid check sum in response: {} rather than {}", + Integer.toHexString(responseMessage[responseMessage.length - 1] & 0x000000FF), + Integer.toHexString(checksum & 0x000000FF)); + throw new RotelException("Invalid check sum in response"); + } + } + + /** + * Compute the checksum of a message + * + * @param message the buffer containing the message + * @param maxIdx the position in the buffer at which the sum has to be stopped + * + * @return the checksum as a byte + */ + public static byte computeCheckSum(byte[] message, int maxIdx) { + int result = 0; + for (int i = 1; i <= maxIdx; i++) { + result += (message[i] & 0x000000FF); + } + return (byte) (result & 0x000000FF); + } + + /** + * Analyze a valid HEX message and dispatch corresponding (key, value) to the event listeners + * + * @param incomingMessage the received message + */ + @Override + protected void handleValidMessage(byte[] incomingMessage) { + if (incomingMessage[3] != STANDARD_RESPONSE) { + return; + } + + final int idxChars = model.isCharsBeforeFlags() ? 4 : (4 + model.getRespNbFlags()); + + // Replace characters with code < 32 by a space before converting to a string + for (int i = idxChars; i < (idxChars + model.getRespNbChars()); i++) { + if (incomingMessage[i] < 0x20) { + incomingMessage[i] = 0x20; + } + } + + String value = new String(incomingMessage, idxChars, model.getRespNbChars(), StandardCharsets.US_ASCII); + logger.debug("handleValidHexMessage: chars *{}*", value); + + final int idxFlags = model.isCharsBeforeFlags() ? (4 + model.getRespNbChars()) : 4; + final byte[] flags = Arrays.copyOfRange(incomingMessage, idxFlags, idxFlags + model.getRespNbFlags()); + if (logger.isTraceEnabled()) { + for (int i = 1; i <= flags.length; i++) { + try { + logger.trace("handleValidHexMessage: Flag {} = {} bits 7-0 = {} {} {} {} {} {} {} {}", i, + Integer.toHexString(flags[i - 1] & 0x000000FF), RotelFlagsMapping.isBitFlagOn(flags, i, 7), + RotelFlagsMapping.isBitFlagOn(flags, i, 6), RotelFlagsMapping.isBitFlagOn(flags, i, 5), + RotelFlagsMapping.isBitFlagOn(flags, i, 4), RotelFlagsMapping.isBitFlagOn(flags, i, 3), + RotelFlagsMapping.isBitFlagOn(flags, i, 2), RotelFlagsMapping.isBitFlagOn(flags, i, 1), + RotelFlagsMapping.isBitFlagOn(flags, i, 0)); + } catch (RotelException e1) { + } + } + } + try { + dispatchKeyValue(KEY_POWER_ZONE2, model.isZone2On(flags) ? POWER_ON : STANDBY); + } catch (RotelException e1) { + // Can't get zone power information from flags data, so we just do not notify of this information that way + } + try { + dispatchKeyValue(KEY_POWER_ZONE3, model.isZone3On(flags) ? POWER_ON : STANDBY); + } catch (RotelException e1) { + // Can't get zone power information from flags data, so we just do not notify of this information that way + } + try { + dispatchKeyValue(KEY_POWER_ZONE4, model.isZone4On(flags) ? POWER_ON : STANDBY); + } catch (RotelException e1) { + // Can't get zone power information from flags data, so we just do not notify of this information that way + } + boolean checkMultiIn = false; + boolean checkSource = true; + try { + if (model.isMultiInputOn(flags)) { + checkSource = false; + try { + RotelSource source = model.getSourceFromName(RotelSource.CAT1_MULTI.getName()); + RotelCommand cmd = source.getCommand(); + if (cmd != null) { + String value2 = cmd.getAsciiCommandV2(); + if (value2 != null) { + dispatchKeyValue(KEY_SOURCE, value2); + } + } + } catch (RotelException e1) { + // MULTI source not declared for the model (should not happen), we do not notify of this source + } + } + } catch (RotelException e1) { + // Can't get status of multiple input source from flags data, checkMultiIn is set to true to get this + // information in another way + checkMultiIn = true; + } + boolean checkStereo = true; + try { + checkStereo = !model.isMoreThan2Channels(flags); + } catch (RotelException e1) { + // Can't get stereo information from flags data, checkStereo is set to true to get this information in + // another way + } + + String valueLowerCase = value.trim().toLowerCase(); + if (!valueLowerCase.isEmpty() && !valueLowerCase.startsWith(KEY1_HEX_ZONE2) + && !valueLowerCase.startsWith(KEY2_HEX_ZONE2) && !valueLowerCase.startsWith(KEY_HEX_ZONE3) + && !valueLowerCase.startsWith(KEY_HEX_ZONE4)) { + dispatchKeyValue(KEY_POWER, POWER_ON); + } + + if (model.getRespNbChars() == 42) { + // 2 lines of 21 characters with a left part and a right part + + // Line 1 left + value = new String(incomingMessage, idxChars, 14, StandardCharsets.US_ASCII); + logger.debug("handleValidHexMessage: line 1 left *{}*", value); + parseText(value, checkSource, checkMultiIn, false, false, false, false, false, true); + + // Line 1 right + value = new String(incomingMessage, idxChars + 14, 7, StandardCharsets.US_ASCII); + logger.debug("handleValidHexMessage: line 1 right *{}*", value); + parseText(value, false, false, false, false, false, false, false, true); + + // Full line 1 + value = new String(incomingMessage, idxChars, 21, StandardCharsets.US_ASCII); + dispatchKeyValue(KEY_LINE1, value); + + // Line 2 right + value = new String(incomingMessage, idxChars + 35, 7, StandardCharsets.US_ASCII); + logger.debug("handleValidHexMessage: line 2 right *{}*", value); + parseText(value, false, false, false, false, false, false, false, true); + + // Full line 2 + value = new String(incomingMessage, idxChars + 21, 21, StandardCharsets.US_ASCII); + logger.debug("handleValidHexMessage: line 2 *{}*", value); + parseText(value, false, false, true, true, false, true, true, true); + dispatchKeyValue(KEY_LINE2, value); + } else { + value = new String(incomingMessage, idxChars, model.getRespNbChars(), StandardCharsets.US_ASCII); + parseText(value, checkSource, checkMultiIn, true, false, true, true, checkStereo, false); + dispatchKeyValue(KEY_LINE1, value); + } + + if (valueLowerCase.isEmpty()) { + dispatchKeyValue(KEY_POWER, POWER_OFF_DELAYED); + } + } + + /** + * Parse a text and dispatch appropriate (key, value) to the event listeners for found information + * + * @param text the text to be parsed + * @param searchSource true if a source has to be searched in the text + * @param searchMultiIn true if MULTI IN indication has to be searched in the text + * @param searchZone true if a zone information has to be searched in the text + * @param searchRecord true if a record source has to be searched in the text + * @param searchRecordAfterSource true if a record source has to be searched in the text after the a found source + * @param searchDsp true if a DSP mode has to be searched in the text + * @param searchStereo true if a STEREO has to be considered in the search + * @param multipleInfo true if source and volume/mute are provided separately + */ + private void parseText(String text, boolean searchSource, boolean searchMultiIn, boolean searchZone, + boolean searchRecord, boolean searchRecordAfterSource, boolean searchDsp, boolean searchStereo, + boolean multipleInfo) { + String value = text.trim(); + String valueLowerCase = value.toLowerCase(); + if (searchRecord) { + dispatchKeyValue(KEY_RECORD_SEL, valueLowerCase.startsWith(KEY_HEX_RECORD) ? MSG_VALUE_ON : MSG_VALUE_OFF); + } + if (searchZone) { + if (valueLowerCase.startsWith(KEY1_HEX_ZONE2) || valueLowerCase.startsWith(KEY2_HEX_ZONE2)) { + dispatchKeyValue(KEY_ZONE, "2"); + } else if (valueLowerCase.startsWith(KEY_HEX_ZONE3)) { + dispatchKeyValue(KEY_ZONE, "3"); + } else if (valueLowerCase.startsWith(KEY_HEX_ZONE4)) { + dispatchKeyValue(KEY_ZONE, "4"); + } else { + dispatchKeyValue(KEY_ZONE, "1"); + } + } + if (valueLowerCase.startsWith(KEY1_HEX_VOLUME) || valueLowerCase.startsWith(KEY2_HEX_VOLUME)) { + value = extractNumber(value, + valueLowerCase.startsWith(KEY1_HEX_VOLUME) ? KEY1_HEX_VOLUME.length() : KEY2_HEX_VOLUME.length()); + dispatchKeyValue(KEY_VOLUME, value); + dispatchKeyValue(KEY_MUTE, MSG_VALUE_OFF); + } else if (valueLowerCase.startsWith(KEY_HEX_MUTE)) { + value = value.substring(KEY_HEX_MUTE.length()).trim(); + if (MSG_VALUE_ON.equalsIgnoreCase(value)) { + dispatchKeyValue(KEY_MUTE, MSG_VALUE_ON); + } else { + logger.debug("Invalid value {} for zone mute", value); + } + } else if (valueLowerCase.startsWith(KEY1_HEX_BASS) || valueLowerCase.startsWith(KEY2_HEX_BASS)) { + value = extractNumber(value, + valueLowerCase.startsWith(KEY1_HEX_BASS) ? KEY1_HEX_BASS.length() : KEY2_HEX_BASS.length()); + dispatchKeyValue(KEY_BASS, value); + } else if (valueLowerCase.startsWith(KEY1_HEX_TREBLE) || valueLowerCase.startsWith(KEY2_HEX_TREBLE)) { + value = extractNumber(value, + valueLowerCase.startsWith(KEY1_HEX_TREBLE) ? KEY1_HEX_TREBLE.length() : KEY2_HEX_TREBLE.length()); + dispatchKeyValue(KEY_TREBLE, value); + } else if (searchMultiIn && valueLowerCase.startsWith(KEY_HEX_MULTI_IN)) { + value = value.substring(KEY_HEX_MULTI_IN.length()).trim(); + if (MSG_VALUE_ON.equalsIgnoreCase(value)) { + try { + RotelSource source = model.getSourceFromName(RotelSource.CAT1_MULTI.getName()); + RotelCommand cmd = source.getCommand(); + if (cmd != null) { + String value2 = cmd.getAsciiCommandV2(); + if (value2 != null) { + dispatchKeyValue(KEY_SOURCE, value2); + } + } + } catch (RotelException e1) { + // MULTI source not declared for the model (should not happen), we do not notify of this source + } + } else if (!MSG_VALUE_OFF.equalsIgnoreCase(value)) { + logger.debug("Invalid value {} for MULTI IN", value); + } + } else if (searchDsp && valueLowerCase.startsWith(KEY_HEX_BYPASS)) { + dispatchKeyValue(KEY_DSP_MODE, RotelDsp.CAT4_BYPASS.getFeedback()); + } else if (searchDsp && searchStereo && valueLowerCase.startsWith(KEY_HEX_STEREO)) { + dispatchKeyValue(KEY_DSP_MODE, RotelDsp.CAT4_NONE.getFeedback()); + } else if (searchDsp && (valueLowerCase.startsWith(KEY1_HEX_3CH) || valueLowerCase.startsWith(KEY2_HEX_3CH))) { + dispatchKeyValue(KEY_DSP_MODE, RotelDsp.CAT4_STEREO3.getFeedback()); + } else if (searchDsp && valueLowerCase.startsWith(KEY_HEX_5CH)) { + dispatchKeyValue(KEY_DSP_MODE, RotelDsp.CAT4_STEREO5.getFeedback()); + } else if (searchDsp && valueLowerCase.startsWith(KEY_HEX_7CH)) { + dispatchKeyValue(KEY_DSP_MODE, RotelDsp.CAT4_STEREO7.getFeedback()); + } else if (searchDsp + && (valueLowerCase.startsWith(KEY_HEX_MUSIC1) || valueLowerCase.startsWith(KEY_HEX_DSP1))) { + dispatchKeyValue(KEY_DSP_MODE, RotelDsp.CAT4_DSP1.getFeedback()); + } else if (searchDsp + && (valueLowerCase.startsWith(KEY_HEX_MUSIC2) || valueLowerCase.startsWith(KEY_HEX_DSP2))) { + dispatchKeyValue(KEY_DSP_MODE, RotelDsp.CAT4_DSP2.getFeedback()); + } else if (searchDsp + && (valueLowerCase.startsWith(KEY_HEX_MUSIC3) || valueLowerCase.startsWith(KEY_HEX_DSP3))) { + dispatchKeyValue(KEY_DSP_MODE, RotelDsp.CAT4_DSP3.getFeedback()); + } else if (searchDsp + && (valueLowerCase.startsWith(KEY_HEX_MUSIC4) || valueLowerCase.startsWith(KEY_HEX_DSP4))) { + dispatchKeyValue(KEY_DSP_MODE, RotelDsp.CAT4_DSP4.getFeedback()); + } else if (searchDsp && (valueLowerCase.startsWith(KEY1_HEX_PLII_CINEMA) + || valueLowerCase.startsWith(KEY2_HEX_PLII_CINEMA) || valueLowerCase.startsWith(KEY1_HEX_PLIIX_CINEMA) + || searchDsp && valueLowerCase.startsWith(KEY2_HEX_PLIIX_CINEMA))) { + dispatchKeyValue(KEY_DSP_MODE, RotelDsp.CAT2_PLII_CINEMA.getFeedback()); + } else if (searchDsp && (valueLowerCase.startsWith(KEY1_HEX_PLII_MUSIC) + || valueLowerCase.startsWith(KEY2_HEX_PLII_MUSIC) || valueLowerCase.startsWith(KEY1_HEX_PLIIX_MUSIC) + || valueLowerCase.startsWith(KEY2_HEX_PLIIX_MUSIC))) { + dispatchKeyValue(KEY_DSP_MODE, RotelDsp.CAT2_PLII_MUSIC.getFeedback()); + } else if (searchDsp && (valueLowerCase.startsWith(KEY1_HEX_PLII_GAME) + || valueLowerCase.startsWith(KEY2_HEX_PLII_GAME) || valueLowerCase.startsWith(KEY1_HEX_PLIIX_GAME) + || valueLowerCase.startsWith(KEY2_HEX_PLIIX_GAME))) { + dispatchKeyValue(KEY_DSP_MODE, RotelDsp.CAT2_PLII_GAME.getFeedback()); + } else if (searchDsp && valueLowerCase.startsWith(KEY_HEX_PLIIZ)) { + dispatchKeyValue(KEY_DSP_MODE, RotelDsp.CAT4_PLIIZ.getFeedback()); + } else if (searchDsp + && (valueLowerCase.startsWith(KEY1_HEX_PROLOGIC) || valueLowerCase.startsWith(KEY2_HEX_PROLOGIC))) { + dispatchKeyValue(KEY_DSP_MODE, RotelDsp.CAT4_PROLOGIC.getFeedback()); + } else if (searchDsp && (valueLowerCase.startsWith(KEY1_HEX_DTS_NEO6_CINEMA) + || valueLowerCase.startsWith(KEY2_HEX_DTS_NEO6_CINEMA))) { + dispatchKeyValue(KEY_DSP_MODE, RotelDsp.CAT4_NEO6_CINEMA.getFeedback()); + } else if (searchDsp && (valueLowerCase.startsWith(KEY1_HEX_DTS_NEO6_MUSIC) + || valueLowerCase.startsWith(KEY2_HEX_DTS_NEO6_MUSIC))) { + dispatchKeyValue(KEY_DSP_MODE, RotelDsp.CAT4_NEO6_MUSIC.getFeedback()); + } else if (searchDsp && valueLowerCase.startsWith(KEY_HEX_DTS_ES)) { + logger.debug("DTS-ES"); + dispatchKeyValue(KEY_DSP_MODE, RotelDsp.CAT4_NONE.getFeedback()); + } else if (searchDsp && valueLowerCase.startsWith(KEY_HEX_DTS_96)) { + logger.debug("DTS 96"); + dispatchKeyValue(KEY_DSP_MODE, RotelDsp.CAT4_NONE.getFeedback()); + } else if (searchDsp && valueLowerCase.startsWith(KEY_HEX_DTS)) { + logger.debug("DTS"); + dispatchKeyValue(KEY_DSP_MODE, RotelDsp.CAT4_NONE.getFeedback()); + } else if (searchDsp && valueLowerCase.startsWith(KEY_HEX_DD_EX)) { + logger.debug("DD-EX"); + dispatchKeyValue(KEY_DSP_MODE, RotelDsp.CAT4_NONE.getFeedback()); + } else if (searchDsp && valueLowerCase.startsWith(KEY_HEX_DD)) { + logger.debug("DD"); + dispatchKeyValue(KEY_DSP_MODE, RotelDsp.CAT4_NONE.getFeedback()); + } else if (searchDsp && valueLowerCase.startsWith(KEY_HEX_LPCM)) { + logger.debug("LPCM"); + dispatchKeyValue(KEY_DSP_MODE, RotelDsp.CAT4_NONE.getFeedback()); + } else if (searchDsp && valueLowerCase.startsWith(KEY_HEX_PCM)) { + logger.debug("PCM"); + dispatchKeyValue(KEY_DSP_MODE, RotelDsp.CAT4_NONE.getFeedback()); + } else if (searchDsp && valueLowerCase.startsWith(KEY_HEX_MPEG)) { + logger.debug("MPEG"); + dispatchKeyValue(KEY_DSP_MODE, RotelDsp.CAT4_NONE.getFeedback()); + } else if (searchZone + && (valueLowerCase.startsWith(KEY1_HEX_ZONE2) || valueLowerCase.startsWith(KEY2_HEX_ZONE2))) { + value = value.substring( + valueLowerCase.startsWith(KEY1_HEX_ZONE2) ? KEY1_HEX_ZONE2.length() : KEY2_HEX_ZONE2.length()); + parseZone2(value, multipleInfo); + } else if (searchZone && valueLowerCase.startsWith(KEY_HEX_ZONE3)) { + parseZone3(value.substring(KEY_HEX_ZONE3.length()), multipleInfo); + } else if (searchZone && valueLowerCase.startsWith(KEY_HEX_ZONE4)) { + parseZone4(value.substring(KEY_HEX_ZONE4.length()), multipleInfo); + } else if (searchRecord && valueLowerCase.startsWith(KEY_HEX_RECORD)) { + parseRecord(value.substring(KEY_HEX_RECORD.length())); + } else if (searchSource || searchRecordAfterSource) { + parseSourceAndRecord(value, searchSource, searchRecordAfterSource, multipleInfo); + } + } + + /** + * Parse a text to identify a source + * + * @param text the text to be parsed + * @param acceptFollowMain true if follow main has to be considered in the search + * + * @return the identified source or null if no source is identified in the text + */ + private @Nullable RotelSource parseSource(String text, boolean acceptFollowMain) { + String value = text.trim(); + RotelSource source = null; + if (!value.isEmpty()) { + if (acceptFollowMain && SOURCE.equalsIgnoreCase(value)) { + try { + source = model.getSourceFromName(RotelSource.CAT1_FOLLOW_MAIN.getName()); + } catch (RotelException e) { + // MAIN (follow main zone source) source not declared for the model, we return null + } + } else { + for (RotelSource src : sourcesLabels.keySet()) { + String label = sourcesLabels.get(src); + if (label != null && value.startsWith(label)) { + if (source == null || sourcesLabels.get(source).length() < label.length()) { + source = src; + } + } + } + } + } + return source; + } + + private void parseSourceAndRecord(String text, boolean searchSource, boolean searchRecordAfterSource, + boolean multipleInfo) { + RotelSource source = parseSource(text, false); + if (source != null) { + if (searchSource) { + RotelCommand cmd = source.getCommand(); + if (cmd != null) { + String value2 = cmd.getAsciiCommandV2(); + if (value2 != null) { + dispatchKeyValue(KEY_SOURCE, value2); + if (!multipleInfo) { + dispatchKeyValue(KEY_MUTE, MSG_VALUE_OFF); + } + } + } + } + + if (searchRecordAfterSource) { + String value = text.substring(getSourceLabel(source).length()).trim(); + source = parseSource(value, true); + if (source != null) { + RotelCommand cmd = source.getRecordCommand(); + if (cmd != null) { + value = cmd.getAsciiCommandV2(); + if (value != null) { + dispatchKeyValue(KEY_RECORD, value); + } + } + } + } + } + } + + private String getSourceLabel(RotelSource source) { + String label = sourcesLabels.get(source); + return (label == null) ? source.getLabel() : label; + } + + private void parseRecord(String text) { + String value = text.trim(); + RotelSource source = parseSource(value, true); + if (source != null) { + RotelCommand cmd = source.getRecordCommand(); + if (cmd != null) { + value = cmd.getAsciiCommandV2(); + if (value != null) { + dispatchKeyValue(KEY_RECORD, value); + } + } + } else { + logger.debug("Invalid value {} for record source", value); + } + } + + private void parseZone2(String text, boolean multipleInfo) { + String value = text.trim(); + String valueLowerCase = value.toLowerCase(); + if (valueLowerCase.startsWith(KEY1_HEX_VOLUME) || valueLowerCase.startsWith(KEY2_HEX_VOLUME)) { + value = extractNumber(value, + valueLowerCase.startsWith(KEY1_HEX_VOLUME) ? KEY1_HEX_VOLUME.length() : KEY2_HEX_VOLUME.length()); + dispatchKeyValue(KEY_VOLUME_ZONE2, value); + dispatchKeyValue(KEY_MUTE_ZONE2, MSG_VALUE_OFF); + } else if (valueLowerCase.startsWith(KEY_HEX_MUTE)) { + value = value.substring(KEY_HEX_MUTE.length()).trim(); + if (MSG_VALUE_ON.equalsIgnoreCase(value)) { + dispatchKeyValue(KEY_MUTE_ZONE2, MSG_VALUE_ON); + } else { + logger.debug("Invalid value {} for zone mute", value); + } + } else if (!MSG_VALUE_OFF.equalsIgnoreCase(value)) { + RotelSource source = parseSource(value, true); + if (source != null) { + RotelCommand cmd = source.getZone2Command(); + if (cmd != null) { + value = cmd.getAsciiCommandV2(); + if (value != null) { + dispatchKeyValue(KEY_SOURCE_ZONE2, value); + if (!multipleInfo) { + dispatchKeyValue(KEY_MUTE_ZONE2, MSG_VALUE_OFF); + } + } + } + } else { + logger.debug("Invalid value {} for zone 2 source", value); + } + } + } + + private void parseZone3(String text, boolean multipleInfo) { + String value = text.trim(); + String valueLowerCase = value.toLowerCase(); + if (valueLowerCase.startsWith(KEY1_HEX_VOLUME) || valueLowerCase.startsWith(KEY2_HEX_VOLUME)) { + value = extractNumber(value, + valueLowerCase.startsWith(KEY1_HEX_VOLUME) ? KEY1_HEX_VOLUME.length() : KEY2_HEX_VOLUME.length()); + dispatchKeyValue(KEY_VOLUME_ZONE3, value); + dispatchKeyValue(KEY_MUTE_ZONE3, MSG_VALUE_OFF); + } else if (valueLowerCase.startsWith(KEY_HEX_MUTE)) { + value = value.substring(KEY_HEX_MUTE.length()).trim(); + if (MSG_VALUE_ON.equalsIgnoreCase(value)) { + dispatchKeyValue(KEY_MUTE_ZONE3, MSG_VALUE_ON); + } else { + logger.debug("Invalid value {} for zone mute", value); + } + } else if (!MSG_VALUE_OFF.equalsIgnoreCase(value)) { + RotelSource source = parseSource(value, true); + if (source != null) { + RotelCommand cmd = source.getZone3Command(); + if (cmd != null) { + value = cmd.getAsciiCommandV2(); + if (value != null) { + dispatchKeyValue(KEY_SOURCE_ZONE3, value); + if (!multipleInfo) { + dispatchKeyValue(KEY_MUTE_ZONE3, MSG_VALUE_OFF); + } + } + } + } else { + logger.debug("Invalid value {} for zone 3 source", value); + } + } + } + + private void parseZone4(String text, boolean multipleInfo) { + String value = text.trim(); + String valueLowerCase = value.toLowerCase(); + if (valueLowerCase.startsWith(KEY1_HEX_VOLUME) || valueLowerCase.startsWith(KEY2_HEX_VOLUME)) { + value = extractNumber(value, + valueLowerCase.startsWith(KEY1_HEX_VOLUME) ? KEY1_HEX_VOLUME.length() : KEY2_HEX_VOLUME.length()); + dispatchKeyValue(KEY_VOLUME_ZONE4, value); + dispatchKeyValue(KEY_MUTE_ZONE4, MSG_VALUE_OFF); + } else if (valueLowerCase.startsWith(KEY_HEX_MUTE)) { + value = value.substring(KEY_HEX_MUTE.length()).trim(); + if (MSG_VALUE_ON.equalsIgnoreCase(value)) { + dispatchKeyValue(KEY_MUTE_ZONE4, MSG_VALUE_ON); + } else { + logger.debug("Invalid value {} for zone mute", value); + } + } else if (!MSG_VALUE_OFF.equalsIgnoreCase(value)) { + RotelSource source = parseSource(value, true); + if (source != null) { + RotelCommand cmd = source.getZone4Command(); + if (cmd != null) { + value = cmd.getAsciiCommandV2(); + if (value != null) { + dispatchKeyValue(KEY_SOURCE_ZONE4, value); + if (!multipleInfo) { + dispatchKeyValue(KEY_MUTE_ZONE4, MSG_VALUE_OFF); + } + } + } + } else { + logger.debug("Invalid value {} for zone 4 source", value); + } + } + } + + /** + * Extract from a string a number + * + * @param value the string + * @param startIndex the index in the string at which the integer has to be extracted + * + * @return the number as a string with its sign and no blank between the sign and the digits + */ + private String extractNumber(String value, int startIndex) { + String result = value.substring(startIndex).trim(); + // Delete possible blank(s) between the sign and the number + if (result.startsWith("+") || result.startsWith("-")) { + result = result.substring(0, 1) + result.substring(1, result.length()).trim(); + } + return result; + } +}