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 313199381..8f2169fbe 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 @@ -68,6 +68,7 @@ public class RotelBindingConstants { public static final String THING_TYPE_ID_RT1570 = "rt1570"; public static final String THING_TYPE_ID_T11 = "t11"; public static final String THING_TYPE_ID_T14 = "t14"; + public static final String THING_TYPE_ID_C8 = "c8"; public static final String THING_TYPE_ID_M8 = "m8"; public static final String THING_TYPE_ID_P5 = "p5"; public static final String THING_TYPE_ID_S5 = "s5"; @@ -116,6 +117,7 @@ public class RotelBindingConstants { public static final ThingTypeUID THING_TYPE_RT1570 = new ThingTypeUID(BINDING_ID, THING_TYPE_ID_RT1570); public static final ThingTypeUID THING_TYPE_T11 = new ThingTypeUID(BINDING_ID, THING_TYPE_ID_T11); public static final ThingTypeUID THING_TYPE_T14 = new ThingTypeUID(BINDING_ID, THING_TYPE_ID_T14); + public static final ThingTypeUID THING_TYPE_C8 = new ThingTypeUID(BINDING_ID, THING_TYPE_ID_C8); public static final ThingTypeUID THING_TYPE_M8 = new ThingTypeUID(BINDING_ID, THING_TYPE_ID_M8); public static final ThingTypeUID THING_TYPE_P5 = new ThingTypeUID(BINDING_ID, THING_TYPE_ID_P5); public static final ThingTypeUID THING_TYPE_S5 = new ThingTypeUID(BINDING_ID, THING_TYPE_ID_S5); @@ -124,45 +126,80 @@ public class RotelBindingConstants { // List of all Channel ids public static final String CHANNEL_POWER = "power"; - public static final String CHANNEL_MAIN_POWER = "mainZone#power"; public static final String CHANNEL_SOURCE = "source"; - public static final String CHANNEL_MAIN_SOURCE = "mainZone#source"; - public static final String CHANNEL_MAIN_RECORD_SOURCE = "mainZone#recordSource"; + public static final String CHANNEL_RECORD_SOURCE = "recordSource"; public static final String CHANNEL_DSP = "dsp"; - public static final String CHANNEL_MAIN_DSP = "mainZone#dsp"; public static final String CHANNEL_VOLUME = "volume"; - public static final String CHANNEL_MAIN_VOLUME = "mainZone#volume"; - public static final String CHANNEL_MAIN_VOLUME_UP_DOWN = "mainZone#volumeUpDown"; + public static final String CHANNEL_VOLUME_UP_DOWN = "volumeUpDown"; public static final String CHANNEL_MUTE = "mute"; - public static final String CHANNEL_MAIN_MUTE = "mainZone#mute"; public static final String CHANNEL_BASS = "bass"; - public static final String CHANNEL_MAIN_BASS = "mainZone#bass"; public static final String CHANNEL_TREBLE = "treble"; - public static final String CHANNEL_MAIN_TREBLE = "mainZone#treble"; public static final String CHANNEL_PLAY_CONTROL = "playControl"; public static final String CHANNEL_TRACK = "track"; public static final String CHANNEL_FREQUENCY = "frequency"; public static final String CHANNEL_LINE1 = "mainZone#line1"; public static final String CHANNEL_LINE2 = "mainZone#line2"; public static final String CHANNEL_BRIGHTNESS = "brightness"; - public static final String CHANNEL_ZONE2_POWER = "zone2#power"; - public static final String CHANNEL_ZONE2_SOURCE = "zone2#source"; - public static final String CHANNEL_ZONE2_VOLUME = "zone2#volume"; - public static final String CHANNEL_ZONE2_VOLUME_UP_DOWN = "zone2#volumeUpDown"; - public static final String CHANNEL_ZONE2_MUTE = "zone2#mute"; - public static final String CHANNEL_ZONE3_POWER = "zone3#power"; - public static final String CHANNEL_ZONE3_SOURCE = "zone3#source"; - public static final String CHANNEL_ZONE3_VOLUME = "zone3#volume"; - public static final String CHANNEL_ZONE3_MUTE = "zone3#mute"; - public static final String CHANNEL_ZONE4_POWER = "zone4#power"; - public static final String CHANNEL_ZONE4_SOURCE = "zone4#source"; - public static final String CHANNEL_ZONE4_VOLUME = "zone4#volume"; - public static final String CHANNEL_ZONE4_MUTE = "zone4#mute"; public static final String CHANNEL_TCBYPASS = "tcbypass"; public static final String CHANNEL_BALANCE = "balance"; public static final String CHANNEL_SPEAKER_A = "speakera"; public static final String CHANNEL_SPEAKER_B = "speakerb"; + public static final String CHANNEL_GROUP_ALL_ZONES = "allZones"; + public static final String CHANNEL_ALL_POWER = CHANNEL_GROUP_ALL_ZONES + "#" + CHANNEL_POWER; + public static final String CHANNEL_ALL_BRIGHTNESS = CHANNEL_GROUP_ALL_ZONES + "#" + CHANNEL_BRIGHTNESS; + + public static final String CHANNEL_GROUP_MAIN_ZONE = "mainZone"; + public static final String CHANNEL_MAIN_POWER = CHANNEL_GROUP_MAIN_ZONE + "#" + CHANNEL_POWER; + public static final String CHANNEL_MAIN_SOURCE = CHANNEL_GROUP_MAIN_ZONE + "#" + CHANNEL_SOURCE; + public static final String CHANNEL_MAIN_RECORD_SOURCE = CHANNEL_GROUP_MAIN_ZONE + "#" + CHANNEL_RECORD_SOURCE; + public static final String CHANNEL_MAIN_DSP = CHANNEL_GROUP_MAIN_ZONE + "#" + CHANNEL_DSP; + public static final String CHANNEL_MAIN_VOLUME = CHANNEL_GROUP_MAIN_ZONE + "#" + CHANNEL_VOLUME; + public static final String CHANNEL_MAIN_VOLUME_UP_DOWN = CHANNEL_GROUP_MAIN_ZONE + "#" + CHANNEL_VOLUME_UP_DOWN; + public static final String CHANNEL_MAIN_MUTE = CHANNEL_GROUP_MAIN_ZONE + "#" + CHANNEL_MUTE; + public static final String CHANNEL_MAIN_BASS = CHANNEL_GROUP_MAIN_ZONE + "#" + CHANNEL_BASS; + public static final String CHANNEL_MAIN_TREBLE = CHANNEL_GROUP_MAIN_ZONE + "#" + CHANNEL_TREBLE; + + public static final String CHANNEL_GROUP_ZONE1 = "zone1"; + public static final String CHANNEL_ZONE1_SOURCE = CHANNEL_GROUP_ZONE1 + "#" + CHANNEL_SOURCE; + public static final String CHANNEL_ZONE1_VOLUME = CHANNEL_GROUP_ZONE1 + "#" + CHANNEL_VOLUME; + public static final String CHANNEL_ZONE1_MUTE = CHANNEL_GROUP_ZONE1 + "#" + CHANNEL_MUTE; + public static final String CHANNEL_ZONE1_BASS = CHANNEL_GROUP_ZONE1 + "#" + CHANNEL_BASS; + public static final String CHANNEL_ZONE1_TREBLE = CHANNEL_GROUP_ZONE1 + "#" + CHANNEL_TREBLE; + public static final String CHANNEL_ZONE1_BALANCE = CHANNEL_GROUP_ZONE1 + "#" + CHANNEL_BALANCE; + public static final String CHANNEL_ZONE1_FREQUENCY = CHANNEL_GROUP_ZONE1 + "#" + CHANNEL_FREQUENCY; + + public static final String CHANNEL_GROUP_ZONE2 = "zone2"; + public static final String CHANNEL_ZONE2_POWER = CHANNEL_GROUP_ZONE2 + "#" + CHANNEL_POWER; + public static final String CHANNEL_ZONE2_SOURCE = CHANNEL_GROUP_ZONE2 + "#" + CHANNEL_SOURCE; + public static final String CHANNEL_ZONE2_VOLUME = CHANNEL_GROUP_ZONE2 + "#" + CHANNEL_VOLUME; + public static final String CHANNEL_ZONE2_VOLUME_UP_DOWN = CHANNEL_GROUP_ZONE2 + "#" + CHANNEL_VOLUME_UP_DOWN; + public static final String CHANNEL_ZONE2_MUTE = CHANNEL_GROUP_ZONE2 + "#" + CHANNEL_MUTE; + public static final String CHANNEL_ZONE2_BASS = CHANNEL_GROUP_ZONE2 + "#" + CHANNEL_BASS; + public static final String CHANNEL_ZONE2_TREBLE = CHANNEL_GROUP_ZONE2 + "#" + CHANNEL_TREBLE; + public static final String CHANNEL_ZONE2_BALANCE = CHANNEL_GROUP_ZONE2 + "#" + CHANNEL_BALANCE; + public static final String CHANNEL_ZONE2_FREQUENCY = CHANNEL_GROUP_ZONE2 + "#" + CHANNEL_FREQUENCY; + + public static final String CHANNEL_GROUP_ZONE3 = "zone3"; + public static final String CHANNEL_ZONE3_POWER = CHANNEL_GROUP_ZONE3 + "#" + CHANNEL_POWER; + public static final String CHANNEL_ZONE3_SOURCE = CHANNEL_GROUP_ZONE3 + "#" + CHANNEL_SOURCE; + public static final String CHANNEL_ZONE3_VOLUME = CHANNEL_GROUP_ZONE3 + "#" + CHANNEL_VOLUME; + public static final String CHANNEL_ZONE3_MUTE = CHANNEL_GROUP_ZONE3 + "#" + CHANNEL_MUTE; + public static final String CHANNEL_ZONE3_BASS = CHANNEL_GROUP_ZONE3 + "#" + CHANNEL_BASS; + public static final String CHANNEL_ZONE3_TREBLE = CHANNEL_GROUP_ZONE3 + "#" + CHANNEL_TREBLE; + public static final String CHANNEL_ZONE3_BALANCE = CHANNEL_GROUP_ZONE3 + "#" + CHANNEL_BALANCE; + public static final String CHANNEL_ZONE3_FREQUENCY = CHANNEL_GROUP_ZONE3 + "#" + CHANNEL_FREQUENCY; + + public static final String CHANNEL_GROUP_ZONE4 = "zone4"; + public static final String CHANNEL_ZONE4_POWER = CHANNEL_GROUP_ZONE4 + "#" + CHANNEL_POWER; + public static final String CHANNEL_ZONE4_SOURCE = CHANNEL_GROUP_ZONE4 + "#" + CHANNEL_SOURCE; + public static final String CHANNEL_ZONE4_VOLUME = CHANNEL_GROUP_ZONE4 + "#" + CHANNEL_VOLUME; + public static final String CHANNEL_ZONE4_MUTE = CHANNEL_GROUP_ZONE4 + "#" + CHANNEL_MUTE; + public static final String CHANNEL_ZONE4_BASS = CHANNEL_GROUP_ZONE4 + "#" + CHANNEL_BASS; + public static final String CHANNEL_ZONE4_TREBLE = CHANNEL_GROUP_ZONE4 + "#" + CHANNEL_TREBLE; + public static final String CHANNEL_ZONE4_BALANCE = CHANNEL_GROUP_ZONE4 + "#" + CHANNEL_BALANCE; + public static final String CHANNEL_ZONE4_FREQUENCY = CHANNEL_GROUP_ZONE4 + "#" + CHANNEL_FREQUENCY; + // List of all properties public static final String PROPERTY_PROTOCOL = "protocol"; @@ -186,13 +223,34 @@ public class RotelBindingConstants { // 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_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 = "mute"; + 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_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_INPUT = "input"; + public static final String KEY_INPUT_ZONE1 = "input_zone1"; + public static final String KEY_INPUT_ZONE2 = "input_zone2"; + public static final String KEY_INPUT_ZONE3 = "input_zone3"; + public static final String KEY_INPUT_ZONE4 = "input_zone4"; + public static final String KEY_VOLUME_ZONE1 = "volume_zone1"; + public static final String KEY_MUTE_ZONE1 = "mute_zone1"; + public static final String KEY_BASS_ZONE1 = "bass_zone1"; + public static final String KEY_BASS_ZONE2 = "bass_zone2"; + public static final String KEY_BASS_ZONE3 = "bass_zone3"; + public static final String KEY_BASS_ZONE4 = "bass_zone4"; + public static final String KEY_TREBLE_ZONE1 = "treble_zone1"; + public static final String KEY_TREBLE_ZONE2 = "treble_zone2"; + public static final String KEY_TREBLE_ZONE3 = "treble_zone3"; + public static final String KEY_TREBLE_ZONE4 = "treble_zone4"; 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"; @@ -203,10 +261,20 @@ public class RotelBindingConstants { 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_FREQ_ZONE1 = "freq_zone1"; + public static final String KEY_FREQ_ZONE2 = "freq_zone2"; + public static final String KEY_FREQ_ZONE3 = "freq_zone3"; + public static final String KEY_FREQ_ZONE4 = "freq_zone4"; 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_BALANCE_ZONE1 = "balance_zone1"; + public static final String KEY_BALANCE_ZONE2 = "balance_zone2"; + public static final String KEY_BALANCE_ZONE3 = "balance_zone3"; + public static final String KEY_BALANCE_ZONE4 = "balance_zone4"; public static final String KEY_SPEAKER = "speaker"; + public static final String KEY_MODEL = "model"; + public static final String KEY_VERSION = "version"; // Output keys only used by the HEX protocol public static final String KEY_LINE1 = "line1"; public static final String KEY_LINE2 = "line2"; @@ -219,16 +287,11 @@ public class RotelBindingConstants { 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 MSG_VALUE_NONE = "none"; public static final String POWER_ON = "on"; public static final String STANDBY = "standby"; public static final String POWER_OFF_DELAYED = "off_delayed"; @@ -243,4 +306,6 @@ public class RotelBindingConstants { public static final String PLAY = "play"; public static final String PAUSE = "pause"; public static final String STOP = "stop"; + + public static final int MAX_NUMBER_OF_ZONES = 4; } diff --git a/bundles/org.openhab.binding.rotel/src/main/java/org/openhab/binding/rotel/internal/RotelHandlerFactory.java b/bundles/org.openhab.binding.rotel/src/main/java/org/openhab/binding/rotel/internal/RotelHandlerFactory.java index 56b7ecd1f..f97da711b 100644 --- a/bundles/org.openhab.binding.rotel/src/main/java/org/openhab/binding/rotel/internal/RotelHandlerFactory.java +++ b/bundles/org.openhab.binding.rotel/src/main/java/org/openhab/binding/rotel/internal/RotelHandlerFactory.java @@ -50,8 +50,8 @@ public class RotelHandlerFactory extends BaseThingHandlerFactory { THING_TYPE_RA12, THING_TYPE_RA1570, THING_TYPE_RA1572, THING_TYPE_RA1592, THING_TYPE_RAP1580, THING_TYPE_RC1570, THING_TYPE_RC1572, THING_TYPE_RC1590, THING_TYPE_RCD1570, THING_TYPE_RCD1572, THING_TYPE_RCX1500, THING_TYPE_RDD1580, THING_TYPE_RDG1520, THING_TYPE_RSP1576, THING_TYPE_RSP1582, - THING_TYPE_RT09, THING_TYPE_RT11, THING_TYPE_RT1570, THING_TYPE_T11, THING_TYPE_T14, THING_TYPE_M8, - THING_TYPE_P5, THING_TYPE_S5, THING_TYPE_X3, THING_TYPE_X5) + THING_TYPE_RT09, THING_TYPE_RT11, THING_TYPE_RT1570, THING_TYPE_T11, THING_TYPE_T14, THING_TYPE_C8, + THING_TYPE_M8, THING_TYPE_P5, THING_TYPE_S5, THING_TYPE_X3, THING_TYPE_X5) .collect(Collectors.toSet())); private final SerialPortManager serialPortManager; 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 38bf2063d..d1656b3cb 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,6 +12,7 @@ */ package org.openhab.binding.rotel.internal; +import static org.openhab.binding.rotel.internal.RotelBindingConstants.MAX_NUMBER_OF_ZONES; import static org.openhab.binding.rotel.internal.communication.RotelCommand.*; import static org.openhab.binding.rotel.internal.protocol.ascii.RotelAbstractAsciiProtocolHandler.*; @@ -89,6 +90,8 @@ public enum RotelModel { 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), + C8("C8", 115200, POWER, 21, 3, true, false, 96, true, 10, false, 10, false, null, -1, true, false, true, 4, 0, + (byte) 0, 0, 0, false, RotelFlagsMapping.NO_MAPPING, NO_SPECIAL_CHARACTERS), M8("M8", 115200, 0, null, false, null, false, -1, false, true, 4, 0, NO_SPECIAL_CHARACTERS), P5("P5", 115200, 20, 96, true, 10, 10, false, -1, true, false, true, 4, 0, NO_SPECIAL_CHARACTERS), S5("S5", 115200, 0, null, false, null, false, -1, false, true, 4, 0, NO_SPECIAL_CHARACTERS), @@ -101,9 +104,11 @@ public enum RotelModel { private int sourceCategory; private int nbAdditionalZones; private boolean additionalCommands; + private boolean powerControlPerZone; private @Nullable Integer volumeMax; private boolean directVolume; private @Nullable Integer toneLevelMax; + private boolean getBypassStatusAvailable; private boolean playControl; private @Nullable RotelCommand zoneSelectCmd; private int dspCategory; @@ -144,9 +149,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, 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); + this(name, baudRate, DISPLAY_REFRESH, sourceCategory, nbAdditionalZones, additionalCommands, true, volumeMax, + directVolume, toneLevelMax, false, null, playControl, zoneSelectCmd, dspCategory, false, false, false, + null, null, deviceId, respNbChars, respNbFlags, charsBeforeFlags, flagsMapping, NO_SPECIAL_CHARACTERS); } /** @@ -170,9 +175,10 @@ 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, 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); + this(name, baudRate, POWER, sourceCategory, 0, false, false, volumeMax, directVolume, toneLevelMax, + toneLevelMax != null, null, playControl, null, dspCategory, getFrequencyAvailable, false, + getDimmerLevelAvailable, diummerLevelMin, diummerLevelMax, (byte) 0, 0, 0, false, + RotelFlagsMapping.NO_MAPPING, specialCharacters); } /** @@ -198,10 +204,10 @@ 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, 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); + this(name, baudRate, POWER, sourceCategory, 0, false, false, volumeMax, directVolume, toneLevelMax, + toneLevelMax != null, balanceLevelMax, playControl, null, dspCategory, getFrequencyAvailable, + getSpeakerGroupsAvailable, getDimmerLevelAvailable, diummerLevelMin, diummerLevelMax, (byte) 0, 0, 0, + false, RotelFlagsMapping.NO_MAPPING, specialCharacters); } /** @@ -213,9 +219,11 @@ public enum RotelModel { * @param sourceCategory the category from {@link RotelSource} * @param nbAdditionalZones the number of additional zones * @param additionalCommands true if other than primary commands are available + * @param powerControlPerZone true if device supports power control per zone * @param volumeMax the maximum volume or null if no volume management is available * @param directVolume true if a command to set the volume with a value is available * @param toneLevelMax the maximum tone level or null if no bass/treble management is available + * @param getBypassStatusAvailable true if the command to get the bypass status for tone control is available * @param balanceLevelMax the maximum balance level or null if no balance management is available * @param playControl true if control of source playback is available * @param zoneSelectCmd the command to be used to select a zone @@ -233,9 +241,9 @@ public enum RotelModel { * @param specialCharacters the table of special characters that can be found in the standard response message */ private RotelModel(String name, int baudRate, RotelCommand powerStateCmd, int sourceCategory, int nbAdditionalZones, - boolean additionalCommands, @Nullable Integer volumeMax, boolean directVolume, - @Nullable Integer toneLevelMax, @Nullable Integer balanceLevelMax, boolean playControl, - @Nullable RotelCommand zoneSelectCmd, int dspCategory, boolean getFrequencyAvailable, + boolean additionalCommands, boolean powerControlPerZone, @Nullable Integer volumeMax, boolean directVolume, + @Nullable Integer toneLevelMax, boolean getBypassStatusAvailable, @Nullable Integer balanceLevelMax, + boolean playControl, @Nullable RotelCommand zoneSelectCmd, int dspCategory, boolean getFrequencyAvailable, boolean getSpeakerGroupsAvailable, boolean getDimmerLevelAvailable, @Nullable Integer diummerLevelMin, @Nullable Integer diummerLevelMax, byte deviceId, int respNbChars, int respNbFlags, boolean charsBeforeFlags, RotelFlagsMapping flagsMapping, byte[][] specialCharacters) { @@ -245,9 +253,11 @@ public enum RotelModel { this.sourceCategory = sourceCategory; this.nbAdditionalZones = nbAdditionalZones; this.additionalCommands = additionalCommands; + this.powerControlPerZone = powerControlPerZone; this.volumeMax = volumeMax; this.directVolume = directVolume; this.toneLevelMax = toneLevelMax; + this.getBypassStatusAvailable = getBypassStatusAvailable; this.balanceLevelMax = balanceLevelMax; this.playControl = playControl; this.zoneSelectCmd = zoneSelectCmd; @@ -302,12 +312,16 @@ public enum RotelModel { } /** - * Get the number of additional zones + * Get the number of zones * - * @return the number of additional zones + * @return the number of zones */ - public int getNbAdditionalZones() { - return nbAdditionalZones; + public int getNumberOfZones() { + return nbAdditionalZones + 1; + } + + private boolean isZoneAvailable(int numZone) { + return numZone >= 1 && numZone <= getNumberOfZones(); } /** @@ -320,57 +334,40 @@ public enum RotelModel { } /** - * Inform whether zone 2 commands are available + * Inform whether zone N commands are available * - * @return true if zone 2 commands are available + * @param numZone the zone number, 1 for for zone 1 until 4 for zone 4 + * + * @return true if zone N commands are available */ - public boolean hasZone2Commands() { - return nbAdditionalZones >= 1 && additionalCommands; + public boolean hasZoneCommands(int numZone) { + if (numZone < 1 || numZone > MAX_NUMBER_OF_ZONES) { + throw new IllegalArgumentException("numZone must be in range 1-" + MAX_NUMBER_OF_ZONES); + } + return additionalCommands && isZoneAvailable(numZone); } /** - * Inform whether zone 3 commands are available + * Inform whether source control is available in a zone * - * @return true if zone 3 commands are available - */ - public boolean hasZone3Commands() { - return nbAdditionalZones >= 2 && additionalCommands; - } - - /** - * Inform whether zone 4 commands are available - * - * @return true if zone 4 commands are available - */ - public boolean hasZone4Commands() { - return nbAdditionalZones >= 3 && additionalCommands; - } - - /** - * Inform whether source control is available in the zone 2 + * @param numZone the zone number, 1 for zone 1 until 4 for zone 4 * * @return true if source control is available */ - public boolean hasZone2SourceControl() { - return sourceCategory >= 1 && nbAdditionalZones >= 1; + public boolean hasZoneSourceControl(int numZone) { + if (numZone < 1 || numZone > MAX_NUMBER_OF_ZONES) { + throw new IllegalArgumentException("numZone must be in range 1-" + MAX_NUMBER_OF_ZONES); + } + return hasSourceControl() && isZoneAvailable(numZone); } /** - * Inform whether source control is available in the zone 3 + * Inform whether device supports power control per zone * - * @return true if source control is available + * @return true if device supports power control per zone */ - public boolean hasZone3SourceControl() { - return sourceCategory >= 1 && nbAdditionalZones >= 2; - } - - /** - * Inform whether source control is available in the zone 4 - * - * @return true if source control is available - */ - public boolean hasZone4SourceControl() { - return sourceCategory >= 1 && nbAdditionalZones >= 3; + public boolean hasPowerControlPerZone() { + return powerControlPerZone; } /** @@ -410,6 +407,15 @@ public enum RotelModel { return toneLevelMax != null; } + /** + * Inform whether the command to get the current bypass status for tone control is available + * + * @return true if the command is available + */ + public boolean canGetBypassStatus() { + return getBypassStatusAvailable; + } + /** * Get the maximum tone level * @@ -577,40 +583,17 @@ public enum RotelModel { } /** - * Get the list of available {@link RotelSource} in the main zone + * Get the list of available {@link RotelSource} in a zone * - * @return the list of available {@link RotelSource} in the main zone - */ - public List getMainZoneSources() { - return (hasSourceControl() && hasOtherThanPrimaryCommands()) ? RotelSource.getSources(sourceCategory, 1) - : new ArrayList<>(); - } - - /** - * Get the list of available {@link RotelSource} in the zone 2 + * @param numZone the zone number, 1 for zone 1 until 4 for zone 4 * * @return the list of available {@link RotelSource} in the zone 2 */ - public List getZone2Sources() { - return hasZone2SourceControl() ? RotelSource.getSources(sourceCategory, 2) : new ArrayList<>(); - } - - /** - * Get the list of available {@link RotelSource} in the zone 3 - * - * @return the list of available {@link RotelSource} in the zone 3 - */ - public List getZone3Sources() { - return hasZone3SourceControl() ? RotelSource.getSources(sourceCategory, 3) : new ArrayList<>(); - } - - /** - * Get the list of available {@link RotelSource} in the zone 4 - * - * @return the list of available {@link RotelSource} in the zone 4 - */ - public List getZone4Sources() { - return hasZone4SourceControl() ? RotelSource.getSources(sourceCategory, 4) : new ArrayList<>(); + public List getZoneSources(int numZone) { + if (numZone < 1 || numZone > MAX_NUMBER_OF_ZONES) { + throw new IllegalArgumentException("numZone must be in range 1-" + MAX_NUMBER_OF_ZONES); + } + return hasZoneSourceControl(numZone) ? RotelSource.getSources(sourceCategory, numZone) : new ArrayList<>(); } /** @@ -649,55 +632,20 @@ public enum RotelModel { } /** - * Get the main zone source associated to a command + * Get the zone N source associated to a command * - * @param command the command used to identify the main zone source + * @param command the command used to identify the zone N source + * @param numZone the zone number, 1 for zone 1 until 4 for zone 4 * - * @return the main zone source associated to the searched command + * @return the zone N source associated to the searched command * - * @throws RotelException - If no main zone source is associated to the searched command + * @throws RotelException - If no zone N source is associated to the searched command */ - public RotelSource getMainZoneSourceFromCommand(RotelCommand command) throws RotelException { - return RotelSource.getFromCommand(sourceCategory, command, 1); - } - - /** - * Get the zone 2 source associated to a command - * - * @param command the command used to identify the zone 2 source - * - * @return the zone 2 source associated to the searched command - * - * @throws RotelException - If no zone 2 source is associated to the searched command - */ - public RotelSource getZone2SourceFromCommand(RotelCommand command) throws RotelException { - return RotelSource.getFromCommand(sourceCategory, command, 2); - } - - /** - * Get the zone 3 source associated to a command - * - * @param command the command used to identify the zone 3 source - * - * @return the zone 3 source associated to the searched command - * - * @throws RotelException - If no zone 3 source is associated to the searched command - */ - public RotelSource getZone3SourceFromCommand(RotelCommand command) throws RotelException { - return RotelSource.getFromCommand(sourceCategory, command, 3); - } - - /** - * Get the zone 4 source associated to a command - * - * @param command the command used to identify the zone 4 source - * - * @return the zone 4 source associated to the searched command - * - * @throws RotelException - If no zone 4 source is associated to the searched command - */ - public RotelSource getZone4SourceFromCommand(RotelCommand command) throws RotelException { - return RotelSource.getFromCommand(sourceCategory, command, 4); + public RotelSource getZoneSourceFromCommand(RotelCommand command, int numZone) throws RotelException { + if (numZone < 1 || numZone > MAX_NUMBER_OF_ZONES) { + throw new IllegalArgumentException("numZone must be in range 1-" + MAX_NUMBER_OF_ZONES); + } + return RotelSource.getFromCommand(sourceCategory, command, numZone); } /** 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 6d2a561c2..48cce171f 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 @@ -58,24 +58,30 @@ public enum RotelCommand { 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), + ZONE1_VOLUME_UP("Zone 1 Volume Up", null, "z1:vol_up"), + ZONE1_VOLUME_DOWN("Zone 1 Volume Down", null, "z1:vol_dwn"), + ZONE1_VOLUME_SET("Set Zone 1 Volume to level", null, "z1:vol_"), + ZONE1_MUTE_TOGGLE("Zone 1 Mute Toggle", null, "z1:mute"), + ZONE1_MUTE_ON("Zone 1 Mute On", null, "z1:mute_on"), + ZONE1_MUTE_OFF("Zone 1 Mute Off", null, "z1:mute_off"), + ZONE2_VOLUME_UP("Zone 2 Volume Up", ZONE2_CMD, (byte) 0, null, "z2:vol_up"), + ZONE2_VOLUME_DOWN("Zone 2 Volume Down", ZONE2_CMD, (byte) 1, null, "z2:vol_dwn"), + ZONE2_VOLUME_SET("Set Zone 2 Volume to level", ZONE2_VOLUME_CMD, (byte) 0, null, "z2:vol_"), + ZONE2_MUTE_TOGGLE("Zone 2 Mute Toggle", ZONE2_CMD, (byte) 0x1E, null, "z2:mute"), + ZONE2_MUTE_ON("Zone 2 Mute On", ZONE2_CMD, (byte) 0x6C, null, "z2:mute_on"), + ZONE2_MUTE_OFF("Zone 2 Mute Off", ZONE2_CMD, (byte) 0x6D, null, "z2:mute_off"), + ZONE3_VOLUME_UP("Zone 3 Volume Up", ZONE3_CMD, (byte) 0, null, "z3:vol_up"), + ZONE3_VOLUME_DOWN("Zone 3 Volume Down", ZONE3_CMD, (byte) 1, null, "z3:vol_dwn"), + ZONE3_VOLUME_SET("Set Zone 3 Volume to level", ZONE3_VOLUME_CMD, (byte) 0, null, "z3:vol_"), + ZONE3_MUTE_TOGGLE("Zone 3 Mute Toggle", ZONE3_CMD, (byte) 0x1E, null, "z3:mute"), + ZONE3_MUTE_ON("Zone 3 Mute On", ZONE3_CMD, (byte) 0x6C, null, "z3:mute_on"), + ZONE3_MUTE_OFF("Zone 3 Mute Off", ZONE3_CMD, (byte) 0x6D, null, "z3:mute_off"), + ZONE4_VOLUME_UP("Zone 4 Volume Up", ZONE4_CMD, (byte) 0, null, "z4:vol_up"), + ZONE4_VOLUME_DOWN("Zone 4 Volume Down", ZONE4_CMD, (byte) 1, null, "z4:vol_dwn"), + ZONE4_VOLUME_SET("Set Zone 4 Volume to level", ZONE4_VOLUME_CMD, (byte) 0, null, "z4:vol_"), + ZONE4_MUTE_TOGGLE("Zone 4 Mute Toggle", ZONE4_CMD, (byte) 0x1E, null, "z4:mute"), + ZONE4_MUTE_ON("Zone 4 Mute On", ZONE4_CMD, (byte) 0x6C, null, "z4:mute_on"), + ZONE4_MUTE_OFF("Zone 4 Mute Off", ZONE4_CMD, (byte) 0x6D, null, "z4:mute_off"), 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"), @@ -112,7 +118,12 @@ public enum RotelCommand { SOURCE_PLAYFI("Source PlayFi", "playfi", "playfi"), SOURCE_IRADIO("Source iRadio", "iradio", "iradio"), SOURCE_NETWORK("Source Network", "network", "network"), + SOURCE_INPUT_A("Source Input A", null, "input_a"), + SOURCE_INPUT_B("Source Input B", null, "input_b"), + SOURCE_INPUT_C("Source Input C", null, "input_c"), + SOURCE_INPUT_D("Source Input D", null, "input_d"), SOURCE("Request current source", "get_current_source", "source?"), + INPUT("Request current source", null, "input?"), 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"), @@ -131,6 +142,10 @@ public enum RotelCommand { 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"), + ZONE1_SOURCE_INPUT_A("Zone 1 Source Input A", null, "z1:input_a"), + ZONE1_SOURCE_INPUT_B("Zone 1 Source Input B", null, "z1:input_b"), + ZONE1_SOURCE_INPUT_C("Zone 1 Source Input C", null, "z1:input_c"), + ZONE1_SOURCE_INPUT_D("Zone 1 Source Input D", null, "z1:input_d"), 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"), @@ -155,6 +170,10 @@ public enum RotelCommand { 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"), + ZONE2_SOURCE_INPUT_A("Zone 2 Source Input A", null, "z2:input_a"), + ZONE2_SOURCE_INPUT_B("Zone 2 Source Input B", null, "z2:input_b"), + ZONE2_SOURCE_INPUT_C("Zone 2 Source Input C", null, "z2:input_c"), + ZONE2_SOURCE_INPUT_D("Zone 2 Source Input D", null, "z2:input_d"), 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"), @@ -167,6 +186,10 @@ public enum RotelCommand { 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"), + ZONE3_SOURCE_INPUT_A("Zone 3 Source Input A", null, "z3:input_a"), + ZONE3_SOURCE_INPUT_B("Zone 3 Source Input B", null, "z3:input_b"), + ZONE3_SOURCE_INPUT_C("Zone 3 Source Input C", null, "z3:input_c"), + ZONE3_SOURCE_INPUT_D("Zone 3 Source Input D", null, "z3:input_d"), 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"), @@ -179,6 +202,10 @@ public enum RotelCommand { 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"), + ZONE4_SOURCE_INPUT_A("Zone 4 Source Input A", null, "z4:input_a"), + ZONE4_SOURCE_INPUT_B("Zone 4 Source Input B", null, "z4:input_b"), + ZONE4_SOURCE_INPUT_C("Zone 4 Source Input C", null, "z4:input_c"), + ZONE4_SOURCE_INPUT_D("Zone 4 Source Input D", null, "z4:input_d"), 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"), @@ -210,6 +237,30 @@ public enum RotelCommand { 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?"), + ZONE1_TREBLE_UP("Zone 1 Treble Up", null, "z1:treble_up"), + ZONE1_TREBLE_DOWN("Zone 1 Treble Down", null, "z1:treble_down"), + ZONE1_TREBLE_SET("Set Zone 1 Treble to level", null, "z1:treble_"), + ZONE1_BASS_UP("Zone 1 Bass Up", null, "z1:bass_up"), + ZONE1_BASS_DOWN("Zone 1 Bass Down", null, "z1:bass_down"), + ZONE1_BASS_SET("Set Zone 1 Bass to level", null, "z1:bass_"), + ZONE2_TREBLE_UP("Zone 2 Treble Up", null, "z2:treble_up"), + ZONE2_TREBLE_DOWN("Zone 2 Treble Down", null, "z2:treble_down"), + ZONE2_TREBLE_SET("Set Zone 2 Treble to level", null, "z2:treble_"), + ZONE2_BASS_UP("Zone 2 Bass Up", null, "z2:bass_up"), + ZONE2_BASS_DOWN("Zone 2 Bass Down", null, "z2:bass_down"), + ZONE2_BASS_SET("Set Zone 2 Bass to level", null, "z2:bass_"), + ZONE3_TREBLE_UP("Zone 3 Treble Up", null, "z3:treble_up"), + ZONE3_TREBLE_DOWN("Zone 3 Treble Down", null, "z3:treble_down"), + ZONE3_TREBLE_SET("Set Zone 3 Treble to level", null, "z3:treble_"), + ZONE3_BASS_UP("Zone 3 Bass Up", null, "z3:bass_up"), + ZONE3_BASS_DOWN("Zone 3 Bass Down", null, "z3:bass_down"), + ZONE3_BASS_SET("Set Zone 3 Bass to level", null, "z3:bass_"), + ZONE4_TREBLE_UP("Zone 4 Treble Up", null, "z4:treble_up"), + ZONE4_TREBLE_DOWN("Zone 4 Treble Down", null, "z4:treble_down"), + ZONE4_TREBLE_SET("Set Zone 4 Treble to level", null, "z4:treble_"), + ZONE4_BASS_UP("Zone 4 Bass Up", null, "z4:bass_up"), + ZONE4_BASS_DOWN("Zone 4 Bass Down", null, "z4:bass_down"), + ZONE4_BASS_SET("Set Zone 4 Bass to level", null, "z4:bass_"), 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"), @@ -234,6 +285,18 @@ public enum RotelCommand { BALANCE_RIGHT("Balance Right", "balance_right", "balance_r"), BALANCE_LEFT("Balance Left", "balance_left", "balance_l"), BALANCE_SET("Set Balance to level", "balance_", "balance_"), + ZONE1_BALANCE_RIGHT("Zone 1 Balance Right", null, "z1:balance_r"), + ZONE1_BALANCE_LEFT("Zone 1 Balance Left", null, "z1:balance_l"), + ZONE1_BALANCE_SET("Set Zone 1 Balance to level", null, "z1:balance_"), + ZONE2_BALANCE_RIGHT("Zone 2 Balance Right", null, "z2:balance_r"), + ZONE2_BALANCE_LEFT("Zone 2 Balance Left", null, "z2:balance_l"), + ZONE2_BALANCE_SET("Set Zone 2 Balance to level", null, "z2:balance_"), + ZONE3_BALANCE_RIGHT("Zone 3 Balance Right", null, "z3:balance_r"), + ZONE3_BALANCE_LEFT("Zone 3 Balance Left", null, "z3:balance_l"), + ZONE3_BALANCE_SET("Set Zone 3 Balance to level", null, "z3:balance_"), + ZONE4_BALANCE_RIGHT("Zone 4 Balance Right", null, "z4:balance_r"), + ZONE4_BALANCE_LEFT("Zone 4 Balance Left", null, "z4:balance_l"), + ZONE4_BALANCE_SET("Set Zone 4 Balance to level", null, "z4:balance_"), BALANCE("Request current balance setting", "get_balance", "balance?"), 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"), @@ -241,7 +304,9 @@ public enum RotelCommand { 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?"); + SPEAKER("Request current active speaker outputs", "get_current_speaker", "speaker?"), + MODEL("Request the model number", null, "model?"), + VERSION("Request the main CPU software version", null, "version?"); public static final byte PRIMARY_COMMAND = (byte) 0x10; 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 2062aceed..1821d0294 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 @@ -18,8 +18,10 @@ import static org.openhab.binding.rotel.internal.protocol.hex.RotelHexProtocolHa import java.io.InterruptedIOException; import java.nio.charset.StandardCharsets; import java.util.Arrays; +import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.StringJoiner; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; @@ -53,28 +55,20 @@ public class RotelSimuConnector extends RotelConnector { private byte[] feedbackMsg = new byte[1]; private int idxInFeedbackMsg = feedbackMsg.length; - private boolean power; - private boolean powerZone2; - private boolean powerZone3; - private boolean powerZone4; - private RotelSource source = RotelSource.CAT0_CD; - private RotelSource recordSource = RotelSource.CAT1_CD; - private RotelSource sourceZone2 = RotelSource.CAT1_CD; - private RotelSource sourceZone3 = RotelSource.CAT1_CD; - private RotelSource sourceZone4 = RotelSource.CAT1_CD; + private boolean[] powers = { false, false, false, false, false }; + private RotelSource[] sources; + private RotelSource recordSource; private boolean multiinput; private RotelDsp dsp = RotelDsp.CAT4_NONE; - private int volume = 50; - private boolean mute; - private int volumeZone2 = 20; - private boolean muteZone2; - private int volumeZone3 = 30; - private boolean muteZone3; - private int volumeZone4 = 40; - private boolean muteZone4; - private int bass; - private int treble; + private int[] volumes = { 50, 10, 20, 30, 40 }; + private boolean[] mutes = { false, false, false, false, false }; + private boolean tcbypass; + private int[] basses = { 0, 0, 0, 0, 0 }; + private int[] trebles = { 0, 0, 0, 0, 0 }; + private int[] balances = { 0, 0, 0, 0, 0 }; private boolean showTreble; + private boolean speakerA = true; + private boolean speakerB = false; private RotelPlayStatus playStatus = RotelPlayStatus.STOPPED; private int track = 1; private boolean selectingRecord; @@ -85,6 +79,8 @@ public class RotelSimuConnector extends RotelConnector { private int maxVolume; private int minToneLevel; private int maxToneLevel; + private int minBalance; + private int maxBalance; /** * Constructor @@ -104,6 +100,12 @@ public class RotelSimuConnector extends RotelConnector { this.maxVolume = model.hasVolumeControl() ? model.getVolumeMax() : 0; this.maxToneLevel = model.hasToneControl() ? model.getToneLevelMax() : 0; this.minToneLevel = -this.maxToneLevel; + this.maxBalance = model.hasBalanceControl() ? model.getBalanceLevelMax() : 0; + this.minBalance = -this.maxBalance; + List modelSources = model.getSources(); + RotelSource source = modelSources.isEmpty() ? RotelSource.CAT0_CD : modelSources.get(0); + sources = new RotelSource[] { source, source, source, source, source }; + recordSource = source; } @Override @@ -158,12 +160,96 @@ public class RotelSimuConnector extends RotelConnector { String textAscii = ""; boolean accepted = true; boolean resetZone = true; + int numZone = 0; + switch (cmd) { + case ZONE1_VOLUME_UP: + case ZONE1_VOLUME_DOWN: + case ZONE1_VOLUME_SET: + case ZONE1_MUTE_TOGGLE: + case ZONE1_MUTE_ON: + case ZONE1_MUTE_OFF: + case ZONE1_BASS_UP: + case ZONE1_BASS_DOWN: + case ZONE1_BASS_SET: + case ZONE1_TREBLE_UP: + case ZONE1_TREBLE_DOWN: + case ZONE1_TREBLE_SET: + case ZONE1_BALANCE_LEFT: + case ZONE1_BALANCE_RIGHT: + case ZONE1_BALANCE_SET: + numZone = 1; + break; + case ZONE2_POWER_OFF: + case ZONE2_POWER_ON: + case ZONE2_VOLUME_UP: + case ZONE2_VOLUME_DOWN: + case ZONE2_VOLUME_SET: + case ZONE2_MUTE_TOGGLE: + case ZONE2_MUTE_ON: + case ZONE2_MUTE_OFF: + case ZONE2_BASS_UP: + case ZONE2_BASS_DOWN: + case ZONE2_BASS_SET: + case ZONE2_TREBLE_UP: + case ZONE2_TREBLE_DOWN: + case ZONE2_TREBLE_SET: + case ZONE2_BALANCE_LEFT: + case ZONE2_BALANCE_RIGHT: + case ZONE2_BALANCE_SET: + numZone = 2; + break; + case ZONE3_POWER_OFF: + case ZONE3_POWER_ON: + case ZONE3_VOLUME_UP: + case ZONE3_VOLUME_DOWN: + case ZONE3_VOLUME_SET: + case ZONE3_MUTE_TOGGLE: + case ZONE3_MUTE_ON: + case ZONE3_MUTE_OFF: + case ZONE3_BASS_UP: + case ZONE3_BASS_DOWN: + case ZONE3_BASS_SET: + case ZONE3_TREBLE_UP: + case ZONE3_TREBLE_DOWN: + case ZONE3_TREBLE_SET: + case ZONE3_BALANCE_LEFT: + case ZONE3_BALANCE_RIGHT: + case ZONE3_BALANCE_SET: + numZone = 3; + break; + case ZONE4_POWER_OFF: + case ZONE4_POWER_ON: + case ZONE4_VOLUME_UP: + case ZONE4_VOLUME_DOWN: + case ZONE4_VOLUME_SET: + case ZONE4_MUTE_TOGGLE: + case ZONE4_MUTE_ON: + case ZONE4_MUTE_OFF: + case ZONE4_BASS_UP: + case ZONE4_BASS_DOWN: + case ZONE4_BASS_SET: + case ZONE4_TREBLE_UP: + case ZONE4_TREBLE_DOWN: + case ZONE4_TREBLE_SET: + case ZONE4_BALANCE_LEFT: + case ZONE4_BALANCE_RIGHT: + case ZONE4_BALANCE_SET: + numZone = 4; + break; + default: + break; + } switch (cmd) { case DISPLAY_REFRESH: break; case POWER_OFF: case MAIN_ZONE_POWER_OFF: - power = false; + powers[0] = false; + if (model.getNumberOfZones() > 1 && !model.hasPowerControlPerZone()) { + for (int zone = 1; zone <= model.getNumberOfZones(); zone++) { + powers[zone] = false; + } + } text = buildSourceLine1Response(); textLine1Left = buildSourceLine1LeftResponse(); textLine1Right = buildVolumeLine1RightResponse(); @@ -171,7 +257,12 @@ public class RotelSimuConnector extends RotelConnector { break; case POWER_ON: case MAIN_ZONE_POWER_ON: - power = true; + powers[0] = true; + if (model.getNumberOfZones() > 1 && !model.hasPowerControlPerZone()) { + for (int zone = 1; zone <= model.getNumberOfZones(); zone++) { + powers[zone] = true; + } + } text = buildSourceLine1Response(); textLine1Left = buildSourceLine1LeftResponse(); textLine1Right = buildVolumeLine1RightResponse(); @@ -181,49 +272,27 @@ public class RotelSimuConnector extends RotelConnector { textAscii = buildPowerAsciiResponse(); break; case ZONE2_POWER_OFF: - powerZone2 = false; - text = textLine2 = buildZonePowerResponse(model.getNbAdditionalZones() > 1 ? "ZONE2" : "ZONE", - powerZone2, sourceZone2); - showZone = 2; + case ZONE3_POWER_OFF: + case ZONE4_POWER_OFF: + powers[numZone] = false; + text = textLine2 = buildZonePowerResponse(numZone); + showZone = numZone; resetZone = false; break; case ZONE2_POWER_ON: - powerZone2 = true; - text = textLine2 = buildZonePowerResponse(model.getNbAdditionalZones() > 1 ? "ZONE2" : "ZONE", - powerZone2, sourceZone2); - showZone = 2; - resetZone = false; - break; - case ZONE3_POWER_OFF: - powerZone3 = false; - text = textLine2 = buildZonePowerResponse("ZONE3", powerZone3, sourceZone3); - showZone = 3; - resetZone = false; - break; case ZONE3_POWER_ON: - powerZone3 = true; - text = textLine2 = buildZonePowerResponse("ZONE3", powerZone3, sourceZone3); - showZone = 3; - resetZone = false; - break; - case ZONE4_POWER_OFF: - powerZone4 = false; - text = textLine2 = buildZonePowerResponse("ZONE4", powerZone4, sourceZone4); - showZone = 4; - resetZone = false; - break; case ZONE4_POWER_ON: - powerZone4 = true; - text = textLine2 = buildZonePowerResponse("ZONE4", powerZone4, sourceZone4); - showZone = 4; + powers[numZone] = true; + text = textLine2 = buildZonePowerResponse(numZone); + showZone = numZone; resetZone = false; break; case RECORD_FONCTION_SELECT: - if (model.getNbAdditionalZones() >= 1 && model.getZoneSelectCmd() == cmd) { + if (model.getNumberOfZones() > 1 && model.getZoneSelectCmd() == cmd) { showZone++; - if (showZone > model.getNbAdditionalZones()) { + if (showZone >= model.getNumberOfZones()) { showZone = 1; - if (!power) { + if (!powers[0]) { showZone++; } } @@ -231,53 +300,34 @@ public class RotelSimuConnector extends RotelConnector { showZone = 1; } if (showZone == 1) { - selectingRecord = power; + selectingRecord = powers[0]; showTreble = false; textLine2 = buildRecordResponse(); - } else if (showZone == 2) { + } else if (showZone >= 2 && showZone <= 4) { selectingRecord = false; - text = textLine2 = buildZonePowerResponse(model.getNbAdditionalZones() > 1 ? "ZONE2" : "ZONE", - powerZone2, sourceZone2); - } else if (showZone == 3) { - selectingRecord = false; - text = textLine2 = buildZonePowerResponse("ZONE3", powerZone3, sourceZone3); - } else if (showZone == 4) { - selectingRecord = false; - text = textLine2 = buildZonePowerResponse("ZONE4", powerZone4, sourceZone4); + text = textLine2 = buildZonePowerResponse(showZone); } resetZone = false; break; case ZONE_SELECT: - if (model.getNbAdditionalZones() == 0 - || (model.getNbAdditionalZones() > 1 && model.getZoneSelectCmd() == cmd) + if (model.getNumberOfZones() == 1 || (model.getNumberOfZones() > 2 && model.getZoneSelectCmd() == cmd) || (showZone == 1 && model.getZoneSelectCmd() != cmd)) { accepted = false; } else { if (model.getZoneSelectCmd() == cmd) { - if (!power && !powerZone2) { + if (!powers[0] && !powers[2]) { showZone = 2; - powerZone2 = true; + powers[2] = true; } else if (showZone == 2) { - powerZone2 = !powerZone2; + powers[2] = !powers[2]; } else { showZone = 2; } - } else { - if (showZone == 2) { - powerZone2 = !powerZone2; - } else if (showZone == 3) { - powerZone3 = !powerZone3; - } else if (showZone == 4) { - powerZone4 = !powerZone4; - } + } else if (showZone >= 2 && showZone <= 4) { + powers[showZone] = !powers[showZone]; } - if (showZone == 2) { - text = textLine2 = buildZonePowerResponse(model.getNbAdditionalZones() > 1 ? "ZONE2" : "ZONE", - powerZone2, sourceZone2); - } else if (showZone == 3) { - text = textLine2 = buildZonePowerResponse("ZONE3", powerZone3, sourceZone3); - } else if (showZone == 4) { - text = textLine2 = buildZonePowerResponse("ZONE4", powerZone4, sourceZone4); + if (showZone >= 2 && showZone <= 4) { + text = textLine2 = buildZonePowerResponse(showZone); } resetZone = false; } @@ -286,193 +336,190 @@ public class RotelSimuConnector extends RotelConnector { accepted = false; break; } - if (!accepted && powerZone2) { + if (!accepted && numZone > 0 && powers[numZone]) { accepted = true; switch (cmd) { + case ZONE1_VOLUME_UP: case ZONE2_VOLUME_UP: - if (volumeZone2 < maxVolume) { - volumeZone2++; - } - text = textLine2 = buildZoneVolumeResponse(model.getNbAdditionalZones() > 1 ? "ZONE2" : "ZONE", - muteZone2, volumeZone2); - break; - case ZONE2_VOLUME_DOWN: - if (volumeZone2 > minVolume) { - volumeZone2--; - } - text = textLine2 = buildZoneVolumeResponse(model.getNbAdditionalZones() > 1 ? "ZONE2" : "ZONE", - muteZone2, volumeZone2); - break; - case ZONE2_VOLUME_SET: - if (value != null) { - volumeZone2 = value; - } - text = textLine2 = buildZoneVolumeResponse(model.getNbAdditionalZones() > 1 ? "ZONE2" : "ZONE", - muteZone2, volumeZone2); - break; - case VOLUME_UP: - if (!model.hasZone2Commands() && model.getNbAdditionalZones() >= 1 && showZone == 2) { - if (volumeZone2 < maxVolume) { - volumeZone2++; - } - text = textLine2 = buildZoneVolumeResponse(model.getNbAdditionalZones() > 1 ? "ZONE2" : "ZONE", - muteZone2, volumeZone2); - resetZone = false; - } else { - accepted = false; - } - break; - case VOLUME_DOWN: - if (!model.hasZone2Commands() && model.getNbAdditionalZones() >= 1 && showZone == 2) { - if (volumeZone2 > minVolume) { - volumeZone2--; - } - text = textLine2 = buildZoneVolumeResponse(model.getNbAdditionalZones() > 1 ? "ZONE2" : "ZONE", - muteZone2, volumeZone2); - resetZone = false; - } else { - accepted = false; - } - break; - case VOLUME_SET: - if (!model.hasZone2Commands() && model.getNbAdditionalZones() >= 1 && showZone == 2) { - if (value != null) { - volumeZone2 = value; - } - text = textLine2 = buildZoneVolumeResponse(model.getNbAdditionalZones() > 1 ? "ZONE2" : "ZONE", - muteZone2, volumeZone2); - resetZone = false; - } else { - accepted = false; - } - break; - case ZONE2_MUTE_TOGGLE: - muteZone2 = !muteZone2; - text = textLine2 = buildZoneVolumeResponse(model.getNbAdditionalZones() > 1 ? "ZONE2" : "ZONE", - muteZone2, volumeZone2); - break; - case ZONE2_MUTE_ON: - muteZone2 = true; - text = textLine2 = buildZoneVolumeResponse(model.getNbAdditionalZones() > 1 ? "ZONE2" : "ZONE", - muteZone2, volumeZone2); - break; - case ZONE2_MUTE_OFF: - muteZone2 = false; - text = textLine2 = buildZoneVolumeResponse(model.getNbAdditionalZones() > 1 ? "ZONE2" : "ZONE", - muteZone2, volumeZone2); - break; - default: - accepted = false; - break; - } - if (!accepted) { - try { - sourceZone2 = model.getZone2SourceFromCommand(cmd); - powerZone2 = true; - text = textLine2 = buildZonePowerResponse(model.getNbAdditionalZones() > 1 ? "ZONE2" : "ZONE", - powerZone2, sourceZone2); - muteZone2 = false; - accepted = true; - showZone = 2; - resetZone = false; - } catch (RotelException e) { - } - } - if (!accepted && !model.hasZone2Commands() && model.getNbAdditionalZones() >= 1 && showZone == 2) { - try { - sourceZone2 = model.getSourceFromCommand(cmd); - powerZone2 = true; - text = textLine2 = buildZonePowerResponse(model.getNbAdditionalZones() > 1 ? "ZONE2" : "ZONE", - powerZone2, sourceZone2); - muteZone2 = false; - accepted = true; - resetZone = false; - } catch (RotelException e) { - } - } - } - if (!accepted && powerZone3) { - accepted = true; - switch (cmd) { case ZONE3_VOLUME_UP: - if (volumeZone3 < maxVolume) { - volumeZone3++; - } - text = textLine2 = buildZoneVolumeResponse("ZONE3", muteZone3, volumeZone3); - break; - case ZONE3_VOLUME_DOWN: - if (volumeZone3 > minVolume) { - volumeZone3--; - } - text = textLine2 = buildZoneVolumeResponse("ZONE3", muteZone3, volumeZone3); - break; - case ZONE3_VOLUME_SET: - if (value != null) { - volumeZone3 = value; - } - text = textLine2 = buildZoneVolumeResponse("ZONE3", muteZone3, volumeZone3); - break; - case ZONE3_MUTE_TOGGLE: - muteZone3 = !muteZone3; - text = textLine2 = buildZoneVolumeResponse("ZONE3", muteZone3, volumeZone3); - break; - case ZONE3_MUTE_ON: - muteZone3 = true; - text = textLine2 = buildZoneVolumeResponse("ZONE3", muteZone3, volumeZone3); - break; - case ZONE3_MUTE_OFF: - muteZone3 = false; - text = textLine2 = buildZoneVolumeResponse("ZONE3", muteZone3, volumeZone3); - break; - default: - accepted = false; - break; - } - if (!accepted) { - try { - sourceZone3 = model.getZone3SourceFromCommand(cmd); - powerZone3 = true; - text = textLine2 = buildZonePowerResponse("ZONE3", powerZone3, sourceZone3); - muteZone3 = false; - accepted = true; - showZone = 3; - resetZone = false; - } catch (RotelException e) { - } - } - } - if (!accepted && powerZone4) { - accepted = true; - switch (cmd) { case ZONE4_VOLUME_UP: - if (volumeZone4 < maxVolume) { - volumeZone4++; + if (volumes[numZone] < maxVolume) { + volumes[numZone]++; } - text = textLine2 = buildZoneVolumeResponse("ZONE4", muteZone4, volumeZone4); + text = textLine2 = buildZoneVolumeResponse(numZone); + textAscii = buildVolumeAsciiResponse(); break; + case ZONE1_VOLUME_DOWN: + case ZONE2_VOLUME_DOWN: + case ZONE3_VOLUME_DOWN: case ZONE4_VOLUME_DOWN: - if (volumeZone4 > minVolume) { - volumeZone4--; + if (volumes[numZone] > minVolume) { + volumes[numZone]--; } - text = textLine2 = buildZoneVolumeResponse("ZONE4", muteZone4, volumeZone4); + text = textLine2 = buildZoneVolumeResponse(numZone); + textAscii = buildVolumeAsciiResponse(); break; + case ZONE1_VOLUME_SET: + case ZONE2_VOLUME_SET: + case ZONE3_VOLUME_SET: case ZONE4_VOLUME_SET: if (value != null) { - volumeZone4 = value; + volumes[numZone] = value; } - text = textLine2 = buildZoneVolumeResponse("ZONE4", muteZone4, volumeZone4); + text = textLine2 = buildZoneVolumeResponse(numZone); + textAscii = buildVolumeAsciiResponse(); break; + case ZONE1_MUTE_TOGGLE: + case ZONE2_MUTE_TOGGLE: + case ZONE3_MUTE_TOGGLE: case ZONE4_MUTE_TOGGLE: - muteZone4 = !muteZone4; - text = textLine2 = buildZoneVolumeResponse("ZONE4", muteZone4, volumeZone4); + mutes[numZone] = !mutes[numZone]; + text = textLine2 = buildZoneVolumeResponse(numZone); + textAscii = buildMuteAsciiResponse(); break; + case ZONE1_MUTE_ON: + case ZONE2_MUTE_ON: + case ZONE3_MUTE_ON: case ZONE4_MUTE_ON: - muteZone4 = true; - text = textLine2 = buildZoneVolumeResponse("ZONE4", muteZone4, volumeZone4); + mutes[numZone] = true; + text = textLine2 = buildZoneVolumeResponse(numZone); + textAscii = buildMuteAsciiResponse(); break; + case ZONE1_MUTE_OFF: + case ZONE2_MUTE_OFF: + case ZONE3_MUTE_OFF: case ZONE4_MUTE_OFF: - muteZone4 = false; - text = textLine2 = buildZoneVolumeResponse("ZONE4", muteZone4, volumeZone4); + mutes[numZone] = false; + text = textLine2 = buildZoneVolumeResponse(numZone); + textAscii = buildMuteAsciiResponse(); + break; + case ZONE1_BASS_UP: + case ZONE2_BASS_UP: + case ZONE3_BASS_UP: + case ZONE4_BASS_UP: + if (!tcbypass && basses[numZone] < maxToneLevel) { + basses[numZone] += STEP_TONE_LEVEL; + } + textAscii = buildBassAsciiResponse(); + break; + case ZONE1_BASS_DOWN: + case ZONE2_BASS_DOWN: + case ZONE3_BASS_DOWN: + case ZONE4_BASS_DOWN: + if (!tcbypass && basses[numZone] > minToneLevel) { + basses[numZone] -= STEP_TONE_LEVEL; + } + textAscii = buildBassAsciiResponse(); + break; + case ZONE1_BASS_SET: + case ZONE2_BASS_SET: + case ZONE3_BASS_SET: + case ZONE4_BASS_SET: + if (!tcbypass && value != null) { + basses[numZone] = value; + } + textAscii = buildBassAsciiResponse(); + break; + case ZONE1_TREBLE_UP: + case ZONE2_TREBLE_UP: + case ZONE3_TREBLE_UP: + case ZONE4_TREBLE_UP: + if (!tcbypass && trebles[numZone] < maxToneLevel) { + trebles[numZone] += STEP_TONE_LEVEL; + } + textAscii = buildTrebleAsciiResponse(); + break; + case ZONE1_TREBLE_DOWN: + case ZONE2_TREBLE_DOWN: + case ZONE3_TREBLE_DOWN: + case ZONE4_TREBLE_DOWN: + if (!tcbypass && trebles[numZone] > minToneLevel) { + trebles[numZone] -= STEP_TONE_LEVEL; + } + textAscii = buildTrebleAsciiResponse(); + break; + case ZONE1_TREBLE_SET: + case ZONE2_TREBLE_SET: + case ZONE3_TREBLE_SET: + case ZONE4_TREBLE_SET: + if (!tcbypass && value != null) { + trebles[numZone] = value; + } + textAscii = buildTrebleAsciiResponse(); + break; + case ZONE1_BALANCE_LEFT: + case ZONE2_BALANCE_LEFT: + case ZONE3_BALANCE_LEFT: + case ZONE4_BALANCE_LEFT: + if (balances[numZone] > minBalance) { + balances[numZone]--; + } + textAscii = buildBalanceAsciiResponse(); + break; + case ZONE1_BALANCE_RIGHT: + case ZONE2_BALANCE_RIGHT: + case ZONE3_BALANCE_RIGHT: + case ZONE4_BALANCE_RIGHT: + if (balances[numZone] < maxBalance) { + balances[numZone]++; + } + textAscii = buildBalanceAsciiResponse(); + break; + case ZONE1_BALANCE_SET: + case ZONE2_BALANCE_SET: + case ZONE3_BALANCE_SET: + case ZONE4_BALANCE_SET: + if (value != null) { + balances[numZone] = value; + } + textAscii = buildBalanceAsciiResponse(); + break; + default: + accepted = false; + break; + } + } + if (!accepted) { + // Check if command is a change of source input for a zone + for (int zone = 1; zone <= model.getNumberOfZones(); zone++) { + if (powers[zone]) { + try { + sources[zone] = model.getZoneSourceFromCommand(cmd, zone); + text = textLine2 = buildZonePowerResponse(zone); + textAscii = buildSourceAsciiResponse(); + mutes[zone] = false; + accepted = true; + showZone = zone; + resetZone = false; + break; + } catch (RotelException e) { + } + } + } + } + if (!accepted && powers[2] && !model.hasZoneCommands(2) && model.getNumberOfZones() > 1 && showZone == 2) { + accepted = true; + switch (cmd) { + case VOLUME_UP: + if (volumes[2] < maxVolume) { + volumes[2]++; + } + text = textLine2 = buildZoneVolumeResponse(2); + resetZone = false; + break; + case VOLUME_DOWN: + if (volumes[2] > minVolume) { + volumes[2]--; + } + text = textLine2 = buildZoneVolumeResponse(2); + resetZone = false; + break; + case VOLUME_SET: + if (value != null) { + volumes[2] = value; + } + text = textLine2 = buildZoneVolumeResponse(2); + resetZone = false; break; default: accepted = false; @@ -480,18 +527,16 @@ public class RotelSimuConnector extends RotelConnector { } if (!accepted) { try { - sourceZone4 = model.getZone4SourceFromCommand(cmd); - powerZone4 = true; - text = textLine2 = buildZonePowerResponse("ZONE4", powerZone4, sourceZone4); - muteZone4 = false; + sources[2] = model.getSourceFromCommand(cmd); + text = textLine2 = buildZonePowerResponse(2); + mutes[2] = false; accepted = true; - showZone = 4; resetZone = false; } catch (RotelException e) { } } } - if (!accepted && power) { + if (!accepted && powers[0]) { accepted = true; switch (cmd) { case UPDATE_AUTO: @@ -510,8 +555,8 @@ public class RotelSimuConnector extends RotelConnector { break; case VOLUME_UP: case MAIN_ZONE_VOLUME_UP: - if (volume < maxVolume) { - volume++; + if (volumes[0] < maxVolume) { + volumes[0]++; } text = buildVolumeLine1Response(); textLine1Right = buildVolumeLine1RightResponse(); @@ -519,8 +564,8 @@ public class RotelSimuConnector extends RotelConnector { break; case VOLUME_DOWN: case MAIN_ZONE_VOLUME_DOWN: - if (volume > minVolume) { - volume--; + if (volumes[0] > minVolume) { + volumes[0]--; } text = buildVolumeLine1Response(); textLine1Right = buildVolumeLine1RightResponse(); @@ -528,7 +573,7 @@ public class RotelSimuConnector extends RotelConnector { break; case VOLUME_SET: if (value != null) { - volume = value; + volumes[0] = value; } text = buildVolumeLine1Response(); textLine1Right = buildVolumeLine1RightResponse(); @@ -539,21 +584,21 @@ public class RotelSimuConnector extends RotelConnector { break; case MUTE_TOGGLE: case MAIN_ZONE_MUTE_TOGGLE: - mute = !mute; + mutes[0] = !mutes[0]; text = buildSourceLine1Response(); textLine1Right = buildVolumeLine1RightResponse(); textAscii = buildMuteAsciiResponse(); break; case MUTE_ON: case MAIN_ZONE_MUTE_ON: - mute = true; + mutes[0] = true; text = buildSourceLine1Response(); textLine1Right = buildVolumeLine1RightResponse(); textAscii = buildMuteAsciiResponse(); break; case MUTE_OFF: case MAIN_ZONE_MUTE_OFF: - mute = false; + mutes[0] = false; text = buildSourceLine1Response(); textLine1Right = buildVolumeLine1RightResponse(); textAscii = buildMuteAsciiResponse(); @@ -562,27 +607,49 @@ public class RotelSimuConnector extends RotelConnector { textAscii = buildMuteAsciiResponse(); break; case TONE_MAX: - textAscii = buildAsciiResponse(KEY_TONE_MAX, "%02d", maxToneLevel); + textAscii = buildAsciiResponse(KEY_TONE_MAX, String.format("%02d", maxToneLevel)); + break; + case TONE_CONTROLS_ON: + tcbypass = false; + textAscii = buildAsciiResponse(KEY_TONE, !tcbypass); + break; + case TONE_CONTROLS_OFF: + tcbypass = true; + textAscii = buildAsciiResponse(KEY_TONE, !tcbypass); + break; + case TONE_CONTROLS: + textAscii = buildAsciiResponse(KEY_TONE, !tcbypass); + break; + case TCBYPASS_ON: + tcbypass = true; + textAscii = buildAsciiResponse(KEY_TCBYPASS, tcbypass); + break; + case TCBYPASS_OFF: + tcbypass = false; + textAscii = buildAsciiResponse(KEY_TCBYPASS, tcbypass); + break; + case TCBYPASS: + textAscii = buildAsciiResponse(KEY_TCBYPASS, tcbypass); break; case BASS_UP: - if (bass < maxToneLevel) { - bass += STEP_TONE_LEVEL; + if (!tcbypass && basses[0] < maxToneLevel) { + basses[0] += STEP_TONE_LEVEL; } text = buildBassLine1Response(); textLine1Right = buildBassLine1RightResponse(); textAscii = buildBassAsciiResponse(); break; case BASS_DOWN: - if (bass > minToneLevel) { - bass -= STEP_TONE_LEVEL; + if (!tcbypass && basses[0] > minToneLevel) { + basses[0] -= STEP_TONE_LEVEL; } text = buildBassLine1Response(); textLine1Right = buildBassLine1RightResponse(); textAscii = buildBassAsciiResponse(); break; case BASS_SET: - if (value != null) { - bass = value; + if (!tcbypass && value != null) { + basses[0] = value; } text = buildBassLine1Response(); textLine1Right = buildBassLine1RightResponse(); @@ -592,24 +659,24 @@ public class RotelSimuConnector extends RotelConnector { textAscii = buildBassAsciiResponse(); break; case TREBLE_UP: - if (treble < maxToneLevel) { - treble += STEP_TONE_LEVEL; + if (!tcbypass && trebles[0] < maxToneLevel) { + trebles[0] += STEP_TONE_LEVEL; } text = buildTrebleLine1Response(); textLine1Right = buildTrebleLine1RightResponse(); textAscii = buildTrebleAsciiResponse(); break; case TREBLE_DOWN: - if (treble > minToneLevel) { - treble -= STEP_TONE_LEVEL; + if (!tcbypass && trebles[0] > minToneLevel) { + trebles[0] -= STEP_TONE_LEVEL; } text = buildTrebleLine1Response(); textLine1Right = buildTrebleLine1RightResponse(); textAscii = buildTrebleAsciiResponse(); break; case TREBLE_SET: - if (value != null) { - treble = value; + if (!tcbypass && value != null) { + trebles[0] = value; } text = buildTrebleLine1Response(); textLine1Right = buildTrebleLine1RightResponse(); @@ -628,6 +695,54 @@ public class RotelSimuConnector extends RotelConnector { textLine1Right = buildBassLine1RightResponse(); } break; + case BALANCE_LEFT: + if (balances[0] > minBalance) { + balances[0]--; + } + textAscii = buildBalanceAsciiResponse(); + break; + case BALANCE_RIGHT: + if (balances[0] < maxBalance) { + balances[0]++; + } + textAscii = buildBalanceAsciiResponse(); + break; + case BALANCE_SET: + if (value != null) { + balances[0] = value; + } + textAscii = buildBalanceAsciiResponse(); + break; + case BALANCE: + textAscii = buildBalanceAsciiResponse(); + break; + case SPEAKER_A_TOGGLE: + speakerA = !speakerA; + textAscii = buildSpeakerAsciiResponse(); + break; + case SPEAKER_A_ON: + speakerA = true; + textAscii = buildSpeakerAsciiResponse(); + break; + case SPEAKER_A_OFF: + speakerA = false; + textAscii = buildSpeakerAsciiResponse(); + break; + case SPEAKER_B_TOGGLE: + speakerB = !speakerB; + textAscii = buildSpeakerAsciiResponse(); + break; + case SPEAKER_B_ON: + speakerB = true; + textAscii = buildSpeakerAsciiResponse(); + break; + case SPEAKER_B_OFF: + speakerB = false; + textAscii = buildSpeakerAsciiResponse(); + break; + case SPEAKER: + textAscii = buildSpeakerAsciiResponse(); + break; case PLAY: playStatus = RotelPlayStatus.PLAYING; textAscii = buildPlayStatusAsciiResponse(); @@ -669,14 +784,15 @@ public class RotelSimuConnector extends RotelConnector { multiinput = !multiinput; text = "MULTI IN " + (multiinput ? "ON" : "OFF"); try { - source = model.getSourceFromCommand(cmd); + sources[0] = model.getSourceFromCommand(cmd); textLine1Left = buildSourceLine1LeftResponse(); textAscii = buildSourceAsciiResponse(); - mute = false; + mutes[0] = false; } catch (RotelException e) { } break; case SOURCE: + case INPUT: textAscii = buildSourceAsciiResponse(); break; case STEREO: @@ -779,7 +895,8 @@ public class RotelSimuConnector extends RotelConnector { textAscii = buildDspAsciiResponse(); break; case FREQUENCY: - textAscii = buildAsciiResponse(KEY_FREQ, "44.1"); + textAscii = model.getNumberOfZones() > 1 ? buildAsciiResponse(KEY_FREQ, "44.1,48,none,176.4") + : buildAsciiResponse(KEY_FREQ, "44.1"); break; case DIMMER_LEVEL_SET: if (value != null) { @@ -790,13 +907,20 @@ public class RotelSimuConnector extends RotelConnector { case DIMMER_LEVEL_GET: textAscii = buildAsciiResponse(KEY_DIMMER, dimmer); break; + case MODEL: + textAscii = buildAsciiResponse(KEY_MODEL, model.getName()); + break; + case VERSION: + textAscii = buildAsciiResponse(KEY_VERSION, "1.00"); + break; default: accepted = false; break; } if (!accepted) { + // Check if command is a change of source input for the main zone try { - source = model.getMainZoneSourceFromCommand(cmd); + sources[0] = model.getZoneSourceFromCommand(cmd, 1); text = buildSourceLine1Response(); textLine1Left = buildSourceLine1LeftResponse(); textAscii = buildSourceAsciiResponse(); @@ -805,21 +929,23 @@ public class RotelSimuConnector extends RotelConnector { } } if (!accepted) { + // Check if command is a change of source input try { if (selectingRecord && !model.hasOtherThanPrimaryCommands()) { recordSource = model.getSourceFromCommand(cmd); } else { - source = model.getSourceFromCommand(cmd); + sources[0] = model.getSourceFromCommand(cmd); } text = buildSourceLine1Response(); textLine1Left = buildSourceLine1LeftResponse(); textAscii = buildSourceAsciiResponse(); - mute = false; + mutes[0] = false; accepted = true; } catch (RotelException e) { } } if (!accepted) { + // Check if command is a change of record source try { recordSource = model.getRecordSourceFromCommand(cmd); text = buildSourceLine1Response(); @@ -862,15 +988,15 @@ public class RotelSimuConnector extends RotelConnector { } catch (RotelException e) { } try { - model.setZone2(flags, powerZone2); + model.setZone2(flags, powers[2]); } catch (RotelException e) { } try { - model.setZone3(flags, powerZone3); + model.setZone3(flags, powers[3]); } catch (RotelException e) { } try { - model.setZone4(flags, powerZone4); + model.setZone4(flags, powers[4]); } catch (RotelException e) { } int size = 6 + model.getRespNbChars() + model.getRespNbFlags(); @@ -919,51 +1045,113 @@ public class RotelSimuConnector extends RotelConnector { } private String buildAsciiResponse(String key, int value) { - return buildAsciiResponse(key, "%d", value); - } - - private String buildAsciiResponse(String key, String format, int value) { - return String.format("%s=" + format, key, value); + return String.format("%s=%d", key, value); } private String buildAsciiResponse(String key, boolean value) { - return buildAsciiResponse(key, value ? MSG_VALUE_ON : MSG_VALUE_OFF); + return buildAsciiResponse(key, buildOnOffValue(value)); + } + + private String buildOnOffValue(boolean on) { + return on ? MSG_VALUE_ON : MSG_VALUE_OFF; } private String buildPowerAsciiResponse() { - return buildAsciiResponse(KEY_POWER, power ? POWER_ON : STANDBY); + return buildAsciiResponse(KEY_POWER, powers[0] ? POWER_ON : STANDBY); } private String buildVolumeAsciiResponse() { - return buildAsciiResponse(KEY_VOLUME, "%02d", volume); + if (model.getNumberOfZones() > 1) { + StringJoiner sj = new StringJoiner(","); + for (int zone = 1; zone <= model.getNumberOfZones(); zone++) { + sj.add(String.format("%02d", volumes[zone])); + } + return buildAsciiResponse(KEY_VOLUME, sj.toString()); + } else { + return buildAsciiResponse(KEY_VOLUME, String.format("%02d", volumes[0])); + } } private String buildMuteAsciiResponse() { - return buildAsciiResponse(KEY_MUTE, mute); + if (model.getNumberOfZones() > 1) { + StringJoiner sj = new StringJoiner(","); + for (int zone = 1; zone <= model.getNumberOfZones(); zone++) { + sj.add(buildOnOffValue(mutes[zone])); + } + return buildAsciiResponse(KEY_MUTE, sj.toString()); + } else { + return buildAsciiResponse(KEY_MUTE, mutes[0]); + } } private String buildBassAsciiResponse() { - String result; - if (bass == 0) { - result = buildAsciiResponse(KEY_BASS, "000"); - } else if (bass > 0) { - result = buildAsciiResponse(KEY_BASS, "+%02d", bass); + if (model.getNumberOfZones() > 1) { + StringJoiner sj = new StringJoiner(","); + for (int zone = 1; zone <= model.getNumberOfZones(); zone++) { + sj.add(buildBassTrebleValue(basses[zone])); + } + return buildAsciiResponse(KEY_BASS, sj.toString()); } else { - result = buildAsciiResponse(KEY_BASS, "-%02d", -bass); + return buildAsciiResponse(KEY_BASS, buildBassTrebleValue(basses[0])); } - return result; } private String buildTrebleAsciiResponse() { - String result; - if (treble == 0) { - result = buildAsciiResponse(KEY_TREBLE, "000"); - } else if (treble > 0) { - result = buildAsciiResponse(KEY_TREBLE, "+%02d", treble); + if (model.getNumberOfZones() > 1) { + StringJoiner sj = new StringJoiner(","); + for (int zone = 1; zone <= model.getNumberOfZones(); zone++) { + sj.add(buildBassTrebleValue(trebles[zone])); + } + return buildAsciiResponse(KEY_TREBLE, sj.toString()); } else { - result = buildAsciiResponse(KEY_TREBLE, "-%02d", -treble); + return buildAsciiResponse(KEY_TREBLE, buildBassTrebleValue(trebles[0])); } - return result; + } + + private String buildBassTrebleValue(int value) { + if (tcbypass || value == 0) { + return "000"; + } else if (value > 0) { + return String.format("+%02d", value); + } else { + return String.format("-%02d", -value); + } + } + + private String buildBalanceAsciiResponse() { + if (model.getNumberOfZones() > 1) { + StringJoiner sj = new StringJoiner(","); + for (int zone = 1; zone <= model.getNumberOfZones(); zone++) { + sj.add(buildBalanceValue(balances[zone])); + } + return buildAsciiResponse(KEY_BALANCE, sj.toString()); + } else { + return buildAsciiResponse(KEY_BALANCE, buildBalanceValue(balances[0])); + } + } + + private String buildBalanceValue(int value) { + if (value == 0) { + return "000"; + } else if (value > 0) { + return String.format("r%02d", value); + } else { + return String.format("l%02d", -value); + } + } + + private String buildSpeakerAsciiResponse() { + String value; + if (speakerA && speakerB) { + value = MSG_VALUE_SPEAKER_AB; + } else if (speakerA && !speakerB) { + value = MSG_VALUE_SPEAKER_A; + } else if (!speakerA && speakerB) { + value = MSG_VALUE_SPEAKER_B; + } else { + value = MSG_VALUE_OFF; + } + return buildAsciiResponse(KEY_SPEAKER, value); } private String buildPlayStatusAsciiResponse() { @@ -983,16 +1171,34 @@ public class RotelSimuConnector extends RotelConnector { } private String buildTrackAsciiResponse() { - return buildAsciiResponse(KEY_TRACK, "%03d", track); + return buildAsciiResponse(KEY_TRACK, String.format("%03d", track)); } private String buildSourceAsciiResponse() { + if (model.getNumberOfZones() > 1) { + StringJoiner sj = new StringJoiner(","); + for (int zone = 1; zone <= model.getNumberOfZones(); zone++) { + sj.add(buildZoneSourceValue(sources[zone])); + } + return buildAsciiResponse(KEY_INPUT, sj.toString()); + } else { + return buildAsciiResponse(KEY_SOURCE, buildSourceValue(sources[0])); + } + } + + private String buildSourceValue(RotelSource source) { String str = null; RotelCommand command = source.getCommand(); if (command != null) { - str = command.getAsciiCommandV2(); + str = protocol == RotelProtocol.ASCII_V1 ? command.getAsciiCommandV1() : command.getAsciiCommandV2(); } - return buildAsciiResponse(KEY_SOURCE, (str == null) ? "" : str); + return str == null ? "" : str; + } + + private String buildZoneSourceValue(RotelSource source) { + String str = buildSourceValue(source); + int idx = str.indexOf("input_"); + return idx < 0 ? str : str.substring(idx + 6); } private String buildDspAsciiResponse() { @@ -1001,29 +1207,29 @@ public class RotelSimuConnector extends RotelConnector { private String buildSourceLine1Response() { String text; - if (!power) { + if (!powers[0]) { text = ""; - } else if (mute) { + } else if (mutes[0]) { text = "MUTE ON"; } else { - text = getSourceLabel(source, false) + " " + getSourceLabel(recordSource, true); + text = getSourceLabel(sources[0], false) + " " + getSourceLabel(recordSource, true); } return text; } private String buildSourceLine1LeftResponse() { String text; - if (!power) { + if (!powers[0]) { text = ""; } else { - text = getSourceLabel(source, false); + text = getSourceLabel(sources[0], false); } return text; } private String buildRecordResponse() { String text; - if (!power) { + if (!powers[0]) { text = ""; } else { text = "REC " + getSourceLabel(recordSource, true); @@ -1031,113 +1237,125 @@ public class RotelSimuConnector extends RotelConnector { return text; } - private String buildZonePowerResponse(String zone, boolean powerZone, RotelSource sourceZone) { - String state = powerZone ? getSourceLabel(sourceZone, true) : "OFF"; + private String buildZonePowerResponse(int numZone) { + String zone; + if (numZone == 2) { + zone = model.getNumberOfZones() > 2 ? "ZONE2" : "ZONE"; + } else { + zone = String.format("ZONE%d", numZone); + } + String state = powers[numZone] ? getSourceLabel(sources[numZone], true) : "OFF"; return zone + " " + state; } private String buildVolumeLine1Response() { String text; - if (volume == minVolume) { + if (volumes[0] == minVolume) { text = " VOLUME MIN "; - } else if (volume == maxVolume) { + } else if (volumes[0] == maxVolume) { text = " VOLUME MAX "; } else { - text = String.format(" VOLUME %02d ", volume); + text = String.format(" VOLUME %02d ", volumes[0]); } return text; } private String buildVolumeLine1RightResponse() { String text; - if (!power) { + if (!powers[0]) { text = ""; - } else if (mute) { + } else if (mutes[0]) { text = "MUTE ON"; - } else if (volume == minVolume) { + } else if (volumes[0] == minVolume) { text = "VOL MIN"; - } else if (volume == maxVolume) { + } else if (volumes[0] == maxVolume) { text = "VOL MAX"; } else { - text = String.format("VOL %02d", volume); + text = String.format("VOL %02d", volumes[0]); } return text; } - private String buildZoneVolumeResponse(String zone, boolean muted, int vol) { + private String buildZoneVolumeResponse(int numZone) { + String zone; + if (numZone == 2) { + zone = model.getNumberOfZones() > 2 ? "ZONE2" : "ZONE"; + } else { + zone = String.format("ZONE%d", numZone); + } String text; - if (muted) { + if (mutes[numZone]) { text = zone + " MUTE ON"; - } else if (vol == minVolume) { + } else if (volumes[numZone] == minVolume) { text = zone + " VOL MIN"; - } else if (vol == maxVolume) { + } else if (volumes[numZone] == maxVolume) { text = zone + " VOL MAX"; } else { - text = String.format("%s VOL %02d", zone, vol); + text = String.format("%s VOL %02d", zone, volumes[numZone]); } return text; } private String buildBassLine1Response() { String text; - if (bass == minToneLevel) { + if (basses[0] == minToneLevel) { text = " BASS MIN "; - } else if (bass == maxToneLevel) { + } else if (basses[0] == maxToneLevel) { text = " BASS MAX "; - } else if (bass == 0) { + } else if (basses[0] == 0) { text = " BASS 0 "; - } else if (bass > 0) { - text = String.format(" BASS +%02d ", bass); + } else if (basses[0] > 0) { + text = String.format(" BASS +%02d ", basses[0]); } else { - text = String.format(" BASS -%02d ", -bass); + text = String.format(" BASS -%02d ", -basses[0]); } return text; } private String buildBassLine1RightResponse() { String text; - if (bass == minToneLevel) { + if (basses[0] == minToneLevel) { text = "LF MIN"; - } else if (bass == maxToneLevel) { + } else if (basses[0] == maxToneLevel) { text = "LF MAX"; - } else if (bass == 0) { + } else if (basses[0] == 0) { text = "LF 0"; - } else if (bass > 0) { - text = String.format("LF + %02d", bass); + } else if (basses[0] > 0) { + text = String.format("LF + %02d", basses[0]); } else { - text = String.format("LF - %02d", -bass); + text = String.format("LF - %02d", -basses[0]); } return text; } private String buildTrebleLine1Response() { String text; - if (treble == minToneLevel) { + if (trebles[0] == minToneLevel) { text = " TREBLE MIN "; - } else if (treble == maxToneLevel) { + } else if (trebles[0] == maxToneLevel) { text = " TREBLE MAX "; - } else if (treble == 0) { + } else if (trebles[0] == 0) { text = " TREBLE 0 "; - } else if (treble > 0) { - text = String.format(" TREBLE +%02d ", treble); + } else if (trebles[0] > 0) { + text = String.format(" TREBLE +%02d ", trebles[0]); } else { - text = String.format(" TREBLE -%02d ", -treble); + text = String.format(" TREBLE -%02d ", -trebles[0]); } return text; } private String buildTrebleLine1RightResponse() { String text; - if (treble == minToneLevel) { + if (trebles[0] == minToneLevel) { text = "HF MIN"; - } else if (treble == maxToneLevel) { + } else if (trebles[0] == maxToneLevel) { text = "HF MAX"; - } else if (treble == 0) { + } else if (trebles[0] == 0) { text = "HF 0"; - } else if (treble > 0) { - text = String.format("HF + %02d", treble); + } else if (trebles[0] > 0) { + text = String.format("HF + %02d", trebles[0]); } else { - text = String.format("HF - %02d", -treble); + text = String.format("HF - %02d", -trebles[0]); } return text; } diff --git a/bundles/org.openhab.binding.rotel/src/main/java/org/openhab/binding/rotel/internal/communication/RotelSource.java b/bundles/org.openhab.binding.rotel/src/main/java/org/openhab/binding/rotel/internal/communication/RotelSource.java index fb5a82906..f6f42867f 100644 --- a/bundles/org.openhab.binding.rotel/src/main/java/org/openhab/binding/rotel/internal/communication/RotelSource.java +++ b/bundles/org.openhab.binding.rotel/src/main/java/org/openhab/binding/rotel/internal/communication/RotelSource.java @@ -301,14 +301,23 @@ public enum RotelSource { CAT20_BLUETOOTH(20, "BLUETOOTH", "Bluetooth", RotelCommand.SOURCE_BLUETOOTH), CAT20_XLR1(20, "XLR1", "XLR 1", RotelCommand.SOURCE_XLR1), CAT20_XLR2(20, "XLR2", "XLR 2", RotelCommand.SOURCE_XLR1), - CAT20_PCUSB(20, "PCUSB", "PC USB", RotelCommand.SOURCE_PCUSB); + CAT20_PCUSB(20, "PCUSB", "PC USB", RotelCommand.SOURCE_PCUSB), + + CAT21_INPUTA(21, "INPUTA", "Input A", RotelCommand.SOURCE_INPUT_A, null, RotelCommand.ZONE1_SOURCE_INPUT_A, + RotelCommand.ZONE2_SOURCE_INPUT_A, RotelCommand.ZONE3_SOURCE_INPUT_A, RotelCommand.ZONE4_SOURCE_INPUT_A), + CAT21_INPUTB(21, "INPUTB", "Input B", RotelCommand.SOURCE_INPUT_B, null, RotelCommand.ZONE1_SOURCE_INPUT_B, + RotelCommand.ZONE2_SOURCE_INPUT_B, RotelCommand.ZONE3_SOURCE_INPUT_B, RotelCommand.ZONE4_SOURCE_INPUT_B), + CAT21_INPUTC(21, "INPUTC", "Input C", RotelCommand.SOURCE_INPUT_C, null, RotelCommand.ZONE1_SOURCE_INPUT_C, + RotelCommand.ZONE2_SOURCE_INPUT_C, RotelCommand.ZONE3_SOURCE_INPUT_C, RotelCommand.ZONE4_SOURCE_INPUT_C), + CAT21_INPUTD(21, "INPUTD", "Input D", RotelCommand.SOURCE_INPUT_D, null, RotelCommand.ZONE1_SOURCE_INPUT_D, + RotelCommand.ZONE2_SOURCE_INPUT_D, RotelCommand.ZONE3_SOURCE_INPUT_D, RotelCommand.ZONE4_SOURCE_INPUT_D); private int category; private String name; private String label; private @Nullable RotelCommand command; private @Nullable RotelCommand recordCommand; - private @Nullable RotelCommand mainZoneCommand; + private @Nullable RotelCommand zone1Command; private @Nullable RotelCommand zone2Command; private @Nullable RotelCommand zone3Command; private @Nullable RotelCommand zone4Command; @@ -333,13 +342,13 @@ public enum RotelSource { * @param label the label of the source * @param command the command to select the source * @param recordCommand the command to select the source as source to be recorded - * @param mainZoneCommand the command to select the source in the main zone + * @param zone1Command the command to select the source in the zone 1 or main zone * @param zone2Command the command to select the source in the zone 2 * @param zone3Command the command to select the source in the zone 3 * @param zone4Command the command to select the source in the zone 4 */ private RotelSource(int category, String name, String label, @Nullable RotelCommand command, - @Nullable RotelCommand recordCommand, @Nullable RotelCommand mainZoneCommand, + @Nullable RotelCommand recordCommand, @Nullable RotelCommand zone1Command, @Nullable RotelCommand zone2Command, @Nullable RotelCommand zone3Command, @Nullable RotelCommand zone4Command) { this.category = category; @@ -347,7 +356,7 @@ public enum RotelSource { this.label = label; this.command = command; this.recordCommand = recordCommand; - this.mainZoneCommand = mainZoneCommand; + this.zone1Command = zone1Command; this.zone2Command = zone2Command; this.zone3Command = zone3Command; this.zone4Command = zone4Command; @@ -399,47 +408,33 @@ public enum RotelSource { } /** - * Get the command to select the source in the main zone + * Get the command to select the source in a zone + * + * @param numZone the zone number, 1 for main zone or zone 1, 2 for zone 2, 3 for zone 3, 4 for zone 4 * * @return the command */ - public @Nullable RotelCommand getMainZoneCommand() { - return mainZoneCommand; - } - - /** - * Get the command to select the source in the zone 2 - * - * @return the command - */ - public @Nullable RotelCommand getZone2Command() { - return zone2Command; - } - - /** - * Get the command to select the source in the zone 3 - * - * @return the command - */ - public @Nullable RotelCommand getZone3Command() { - return zone3Command; - } - - /** - * Get the command to select the source in the zone 4 - * - * @return the command - */ - public @Nullable RotelCommand getZone4Command() { - return zone4Command; + public @Nullable RotelCommand getZoneCommand(int numZone) { + switch (numZone) { + case 1: + return zone1Command; + case 2: + return zone2Command; + case 3: + return zone3Command; + case 4: + return zone4Command; + default: + throw new IllegalArgumentException("numZone must be a value between 1 and 4"); + } } /** * Get the list of {@link RotelSource} available for a particular category of models * * @param category a category of models - * @param type a source type (0 for global source, 1 for main zone, 2 for zone 2, 3 for zone 3, 4 for zone 4 and 5 - * for record source) + * @param type a source type (0 for global source, 1 for main zone or zone 1, 2 for zone 2, 3 for zone 3, 4 for zone + * 4 and 5 for record source) * * @return the list of {@link RotelSource} available in a zone for a provided category of models */ @@ -447,9 +442,8 @@ public enum RotelSource { List sources = new ArrayList<>(); for (RotelSource value : RotelSource.values()) { if (value.getCategory() == category && ((type == 0 && value.getCommand() != null) - || (type == 1 && value.getMainZoneCommand() != null) - || (type == 2 && value.getZone2Command() != null) || (type == 3 && value.getZone3Command() != null) - || (type == 4 && value.getZone4Command() != null) + || (type == 1 && value.getZoneCommand(1) != null) || (type == 2 && value.getZoneCommand(2) != null) + || (type == 3 && value.getZoneCommand(3) != null) || (type == 4 && value.getZoneCommand(4) != null) || (type == 5 && value.getRecordCommand() != null))) { sources.add(value); } @@ -481,8 +475,8 @@ public enum RotelSource { * * @param category a category of models * @param command the command used to identify the source - * @param type a source type (0 for global source, 1 for main zone, 2 for zone 2, 3 for zone 3, 4 for zone 4 and 5 - * for record source) + * @param type a source type (0 for global source, 1 for main zone or zone 1, 2 for zone 2, 3 for zone 3, + * 4 for zone 4 and 5 for record source) * * @return the source associated to the searched command for the provided category of models * @@ -491,10 +485,10 @@ public enum RotelSource { public static RotelSource getFromCommand(int category, RotelCommand command, int type) throws RotelException { for (RotelSource value : RotelSource.values()) { if (value.getCategory() == category && ((type == 0 && value.getCommand() == command) - || (type == 1 && value.getMainZoneCommand() == command) - || (type == 2 && value.getZone2Command() == command) - || (type == 3 && value.getZone3Command() == command) - || (type == 4 && value.getZone4Command() == command) + || (type == 1 && value.getZoneCommand(1) == command) + || (type == 2 && value.getZoneCommand(2) == command) + || (type == 3 && value.getZoneCommand(3) == command) + || (type == 4 && value.getZoneCommand(4) == command) || (type == 5 && value.getRecordCommand() == command))) { return value; } 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 39876a3d3..6a2d2392b 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 @@ -82,11 +82,8 @@ public class RotelHandler extends BaseThingHandler implements RotelMessageEventL private static final int SLEEP_INTV = 30; private @Nullable ScheduledFuture reconnectJob; - private @Nullable ScheduledFuture powerOnJob; private @Nullable ScheduledFuture powerOffJob; - private @Nullable ScheduledFuture powerOnZone2Job; - private @Nullable ScheduledFuture powerOnZone3Job; - private @Nullable ScheduledFuture powerOnZone4Job; + private @Nullable ScheduledFuture[] powerOnZoneJobs = { null, null, null, null, null }; private RotelStateDescriptionOptionProvider stateDescriptionProvider; private SerialPortManager serialPortManager; @@ -103,37 +100,24 @@ public class RotelHandler extends BaseThingHandler implements RotelMessageEventL private int currentZone = 1; private boolean selectingRecord; - private @Nullable Boolean power; - private boolean powerZone2; - private boolean powerZone3; - private boolean powerZone4; - private RotelSource source = RotelSource.CAT0_CD; + private @Nullable Boolean[] powers = { null, false, false, false, false }; + private boolean powerControlPerZone; private @Nullable RotelSource recordSource; - private @Nullable RotelSource sourceZone2; - private @Nullable RotelSource sourceZone3; - private @Nullable RotelSource sourceZone4; + private @Nullable RotelSource[] sources = { RotelSource.CAT0_CD, null, null, null, null }; private RotelDsp dsp = RotelDsp.CAT1_NONE; - private int volume; - private boolean mute; - private boolean fixedVolumeZone2; - private int volumeZone2; - private boolean muteZone2; - private boolean fixedVolumeZone3; - private int volumeZone3; - private boolean muteZone3; - private boolean fixedVolumeZone4; - private int volumeZone4; - private boolean muteZone4; - private int bass; - private int treble; + private boolean[] fixedVolumeZones = { false, false, false, false, false }; + private int[] volumes = { 0, 0, 0, 0, 0 }; + private boolean[] mutes = { false, false, false, false, false }; + private int[] basses = { 0, 0, 0, 0, 0 }; + private int[] trebles = { 0, 0, 0, 0, 0 }; private RotelPlayStatus playStatus = RotelPlayStatus.STOPPED; private int track; - private double frequency; + private double[] frequencies = { 0.0, 0.0, 0.0, 0.0, 0.0 }; private String frontPanelLine1 = ""; private String frontPanelLine2 = ""; private int brightness; private boolean tcbypass; - private int balance; + private int[] balances = { 0, 0, 0, 0, 0 }; private int minBalanceLevel; private int maxBalanceLevel; private boolean speakera; @@ -281,6 +265,9 @@ public class RotelHandler extends BaseThingHandler implements RotelMessageEventL case THING_TYPE_ID_T14: model = RotelModel.T14; break; + case THING_TYPE_ID_C8: + model = RotelModel.C8; + break; case THING_TYPE_ID_M8: model = RotelModel.M8; break; @@ -350,6 +337,8 @@ public class RotelHandler extends BaseThingHandler implements RotelMessageEventL minBalanceLevel, maxBalanceLevel); } + powerControlPerZone = model.hasPowerControlPerZone(); + // Check configuration settings String configError = null; if ((config.serialPort == null || config.serialPort.isEmpty()) @@ -445,17 +434,21 @@ public class RotelHandler extends BaseThingHandler implements RotelMessageEventL new ChannelUID(getThing().getUID(), CHANNEL_MAIN_RECORD_SOURCE), getStateOptions(model.getRecordSources(), sourcesCustomLabels)); } - if (model.hasZone2SourceControl()) { + if (model.hasZoneSourceControl(1)) { + stateDescriptionProvider.setStateOptions(new ChannelUID(getThing().getUID(), CHANNEL_ZONE1_SOURCE), + getStateOptions(model.getZoneSources(1), sourcesCustomLabels)); + } + if (model.hasZoneSourceControl(2)) { stateDescriptionProvider.setStateOptions(new ChannelUID(getThing().getUID(), CHANNEL_ZONE2_SOURCE), - getStateOptions(model.getZone2Sources(), sourcesCustomLabels)); + getStateOptions(model.getZoneSources(2), sourcesCustomLabels)); } - if (model.hasZone3SourceControl()) { + if (model.hasZoneSourceControl(3)) { stateDescriptionProvider.setStateOptions(new ChannelUID(getThing().getUID(), CHANNEL_ZONE3_SOURCE), - getStateOptions(model.getZone3Sources(), sourcesCustomLabels)); + getStateOptions(model.getZoneSources(3), sourcesCustomLabels)); } - if (model.hasZone4SourceControl()) { + if (model.hasZoneSourceControl(4)) { stateDescriptionProvider.setStateOptions(new ChannelUID(getThing().getUID(), CHANNEL_ZONE4_SOURCE), - getStateOptions(model.getZone4Sources(), sourcesCustomLabels)); + getStateOptions(model.getZoneSources(4), sourcesCustomLabels)); } if (model.hasDspControl()) { stateDescriptionProvider.setStateOptions(new ChannelUID(getThing().getUID(), CHANNEL_DSP), @@ -476,10 +469,9 @@ public class RotelHandler extends BaseThingHandler implements RotelMessageEventL public void dispose() { logger.debug("Disposing handler for thing {}", getThing().getUID()); cancelPowerOffJob(); - cancelPowerOnJob(); - cancelPowerOnZone2Job(); - cancelPowerOnZone3Job(); - cancelPowerOnZone4Job(); + for (int zone = 0; zone <= model.getNumberOfZones(); zone++) { + cancelPowerOnZoneJob(zone); + } cancelReconnectJob(); closeConnection(); super.dispose(); @@ -513,6 +505,48 @@ public class RotelHandler extends BaseThingHandler implements RotelMessageEventL return; } + int numZone = 0; + switch (channel) { + case CHANNEL_ZONE1_SOURCE: + case CHANNEL_ZONE1_VOLUME: + case CHANNEL_ZONE1_MUTE: + case CHANNEL_ZONE1_BASS: + case CHANNEL_ZONE1_TREBLE: + case CHANNEL_ZONE1_BALANCE: + numZone = 1; + break; + case CHANNEL_ZONE2_POWER: + case CHANNEL_ZONE2_SOURCE: + case CHANNEL_ZONE2_VOLUME: + case CHANNEL_ZONE2_VOLUME_UP_DOWN: + case CHANNEL_ZONE2_MUTE: + case CHANNEL_ZONE2_BASS: + case CHANNEL_ZONE2_TREBLE: + case CHANNEL_ZONE2_BALANCE: + numZone = 2; + break; + case CHANNEL_ZONE3_POWER: + case CHANNEL_ZONE3_SOURCE: + case CHANNEL_ZONE3_VOLUME: + case CHANNEL_ZONE3_MUTE: + case CHANNEL_ZONE3_BASS: + case CHANNEL_ZONE3_TREBLE: + case CHANNEL_ZONE3_BALANCE: + numZone = 3; + break; + case CHANNEL_ZONE4_POWER: + case CHANNEL_ZONE4_SOURCE: + case CHANNEL_ZONE4_VOLUME: + case CHANNEL_ZONE4_MUTE: + case CHANNEL_ZONE4_BASS: + case CHANNEL_ZONE4_TREBLE: + case CHANNEL_ZONE4_BALANCE: + numZone = 4; + break; + default: + break; + } + RotelSource src; RotelCommand cmd; boolean success = true; @@ -521,13 +555,13 @@ public class RotelHandler extends BaseThingHandler implements RotelMessageEventL switch (channel) { case CHANNEL_POWER: case CHANNEL_MAIN_POWER: - handlePowerCmd(channel, command, getPowerOnCommand(), getPowerOffCommand()); - break; case CHANNEL_ZONE2_POWER: - if (model.hasZone2Commands()) { - handlePowerCmd(channel, command, RotelCommand.ZONE2_POWER_ON, RotelCommand.ZONE2_POWER_OFF); - } else if (model.getNbAdditionalZones() == 1) { - if (isPowerOn() || powerZone2) { + case CHANNEL_ZONE3_POWER: + case CHANNEL_ZONE4_POWER: + if (numZone == 0 || model.hasZoneCommands(numZone)) { + handlePowerCmd(channel, command, getPowerOnCommand(numZone), getPowerOffCommand(numZone)); + } else if (numZone == 2 && model.getNumberOfZones() == 2) { + if (isPowerOn() || isPowerOn(numZone)) { selectZone(2, model.getZoneSelectCmd()); } sendCommand(RotelCommand.ZONE_SELECT); @@ -536,30 +570,26 @@ public class RotelHandler extends BaseThingHandler implements RotelMessageEventL logger.debug("Command {} from channel {} failed: unavailable feature", command, channel); } break; - case CHANNEL_ZONE3_POWER: - if (model.hasZone3Commands()) { - handlePowerCmd(channel, command, RotelCommand.ZONE3_POWER_ON, RotelCommand.ZONE3_POWER_OFF); - } else { - success = false; - logger.debug("Command {} from channel {} failed: unavailable feature", command, channel); - } - break; - case CHANNEL_ZONE4_POWER: - if (model.hasZone4Commands()) { - handlePowerCmd(channel, command, RotelCommand.ZONE4_POWER_ON, RotelCommand.ZONE4_POWER_OFF); - } else { - success = false; - logger.debug("Command {} from channel {} failed: unavailable feature", command, channel); - } + case CHANNEL_ALL_POWER: + handlePowerCmd(channel, command, RotelCommand.POWER_ON, RotelCommand.POWER_OFF); break; case CHANNEL_SOURCE: case CHANNEL_MAIN_SOURCE: - if (!isPowerOn()) { + case CHANNEL_ZONE1_SOURCE: + case CHANNEL_ZONE2_SOURCE: + case CHANNEL_ZONE3_SOURCE: + case CHANNEL_ZONE4_SOURCE: + if (!isPowerOn(numZone)) { success = false; - logger.debug("Command {} from channel {} ignored: device in standby", command, channel); - } else { + logger.debug("Command {} from channel {} ignored: {} in standby", command, channel, + numZone == 0 ? "device" : "zone " + numZone); + } else if (numZone == 0 || model.hasZoneCommands(numZone)) { src = model.getSourceFromName(command.toString()); - cmd = model.hasOtherThanPrimaryCommands() ? src.getMainZoneCommand() : src.getCommand(); + if (numZone == 0) { + cmd = model.hasOtherThanPrimaryCommands() ? src.getZoneCommand(1) : src.getCommand(); + } else { + cmd = src.getZoneCommand(numZone); + } if (cmd != null) { sendCommand(cmd); if (model.canGetFrequency()) { @@ -579,6 +609,32 @@ public class RotelHandler extends BaseThingHandler implements RotelMessageEventL logger.debug("Command {} from channel {} failed: undefined source command", command, channel); } + } else if (numZone == 2 && model.getNumberOfZones() > 1) { + src = model.getSourceFromName(command.toString()); + cmd = src.getCommand(); + if (cmd != null) { + selectZone(2, model.getZoneSelectCmd()); + sendCommand(cmd); + if (model.canGetFrequency()) { + // send returns + // 1.) the selected + // 2.) the used frequency + // BUT: + // at response-time the frequency has the value of + // so we must wait a short moment to get the frequency of + Thread.sleep(1000); + sendCommand(RotelCommand.FREQUENCY); + Thread.sleep(100); + updateChannelState(CHANNEL_FREQUENCY); + } + } else { + success = false; + logger.debug("Command {} from channel {} failed: undefined source command", command, + channel); + } + } else { + success = false; + logger.debug("Command {} from channel {} failed: unavailable feature", command, channel); } break; case CHANNEL_MAIN_RECORD_SOURCE: @@ -609,74 +665,6 @@ public class RotelHandler extends BaseThingHandler implements RotelMessageEventL } } break; - case CHANNEL_ZONE2_SOURCE: - if (!powerZone2) { - success = false; - logger.debug("Command {} from channel {} ignored: zone 2 in standby", command, channel); - } else if (model.hasZone2Commands()) { - src = model.getSourceFromName(command.toString()); - cmd = src.getZone2Command(); - if (cmd != null) { - sendCommand(cmd); - } else { - success = false; - logger.debug("Command {} from channel {} failed: undefined zone 2 source command", - command, channel); - } - } else if (model.getNbAdditionalZones() >= 1) { - src = model.getSourceFromName(command.toString()); - cmd = src.getCommand(); - if (cmd != null) { - selectZone(2, model.getZoneSelectCmd()); - sendCommand(cmd); - } else { - success = false; - logger.debug("Command {} from channel {} failed: undefined source command", command, - channel); - } - } else { - success = false; - logger.debug("Command {} from channel {} failed: unavailable feature", command, channel); - } - break; - case CHANNEL_ZONE3_SOURCE: - if (!powerZone3) { - success = false; - logger.debug("Command {} from channel {} ignored: zone 3 in standby", command, channel); - } else if (model.hasZone3Commands()) { - src = model.getSourceFromName(command.toString()); - cmd = src.getZone3Command(); - if (cmd != null) { - sendCommand(cmd); - } else { - success = false; - logger.debug("Command {} from channel {} failed: undefined zone 3 source command", - command, channel); - } - } else { - success = false; - logger.debug("Command {} from channel {} failed: unavailable feature", command, channel); - } - break; - case CHANNEL_ZONE4_SOURCE: - if (!powerZone4) { - success = false; - logger.debug("Command {} from channel {} ignored: zone 4 in standby", command, channel); - } else if (model.hasZone4Commands()) { - src = model.getSourceFromName(command.toString()); - cmd = src.getZone4Command(); - if (cmd != null) { - sendCommand(cmd); - } else { - success = false; - logger.debug("Command {} from channel {} failed: undefined zone 4 source command", - command, channel); - } - } else { - success = false; - logger.debug("Command {} from channel {} failed: unavailable feature", command, channel); - } - break; case CHANNEL_DSP: case CHANNEL_MAIN_DSP: if (!isPowerOn()) { @@ -688,100 +676,30 @@ public class RotelHandler extends BaseThingHandler implements RotelMessageEventL break; case CHANNEL_VOLUME: case CHANNEL_MAIN_VOLUME: - if (!isPowerOn()) { - success = false; - logger.debug("Command {} from channel {} ignored: device in standby", command, channel); - } else if (model.hasVolumeControl()) { - handleVolumeCmd(volume, channel, command, getVolumeUpCommand(), getVolumeDownCommand(), - RotelCommand.VOLUME_SET); - } else { - success = false; - logger.debug("Command {} from channel {} failed: unavailable feature", command, channel); - } - break; case CHANNEL_MAIN_VOLUME_UP_DOWN: - if (!isPowerOn()) { - success = false; - logger.debug("Command {} from channel {} ignored: device in standby", command, channel); - } else if (model.hasVolumeControl()) { - handleVolumeCmd(volume, channel, command, getVolumeUpCommand(), getVolumeDownCommand(), - null); - } else { - success = false; - logger.debug("Command {} from channel {} failed: unavailable feature", command, channel); - } - break; + case CHANNEL_ZONE1_VOLUME: case CHANNEL_ZONE2_VOLUME: - if (!powerZone2) { - success = false; - logger.debug("Command {} from channel {} ignored: zone 2 in standby", command, channel); - } else if (fixedVolumeZone2) { - success = false; - logger.debug("Command {} from channel {} ignored: fixed volume in zone 2", command, - channel); - } 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, model.getZoneSelectCmd()); - handleVolumeCmd(volumeZone2, channel, command, RotelCommand.VOLUME_UP, - RotelCommand.VOLUME_DOWN, RotelCommand.VOLUME_SET); - } - } else { - success = false; - logger.debug("Command {} from channel {} failed: unavailable feature", command, channel); - } - break; case CHANNEL_ZONE2_VOLUME_UP_DOWN: - if (!powerZone2) { - success = false; - logger.debug("Command {} from channel {} ignored: zone 2 in standby", command, channel); - } else if (fixedVolumeZone2) { - success = false; - logger.debug("Command {} from channel {} ignored: fixed volume in zone 2", command, - channel); - } 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, model.getZoneSelectCmd()); - handleVolumeCmd(volumeZone2, channel, command, RotelCommand.VOLUME_UP, - RotelCommand.VOLUME_DOWN, null); - } - } else { - success = false; - logger.debug("Command {} from channel {} failed: unavailable feature", command, channel); - } - break; case CHANNEL_ZONE3_VOLUME: - if (!powerZone3) { - success = false; - logger.debug("Command {} from channel {} ignored: zone 3 in standby", command, channel); - } else if (fixedVolumeZone3) { - success = false; - logger.debug("Command {} from channel {} ignored: fixed volume in zone 3", command, - channel); - } else if (model.hasVolumeControl() && model.hasZone3Commands()) { - handleVolumeCmd(volumeZone3, channel, command, RotelCommand.ZONE3_VOLUME_UP, - RotelCommand.ZONE3_VOLUME_DOWN, RotelCommand.ZONE3_VOLUME_SET); - } else { - success = false; - logger.debug("Command {} from channel {} failed: unavailable feature", command, channel); - } - break; case CHANNEL_ZONE4_VOLUME: - if (!powerZone4) { + if (!isPowerOn(numZone)) { success = false; - logger.debug("Command {} from channel {} ignored: zone 4 in standby", command, channel); - } else if (fixedVolumeZone4) { + logger.debug("Command {} from channel {} ignored: zone {} in standby", command, channel, + numZone == 0 ? "device" : "zone " + numZone); + } else if (fixedVolumeZones[numZone]) { success = false; - logger.debug("Command {} from channel {} ignored: fixed volume in zone 4", command, - channel); - } else if (model.hasVolumeControl() && model.hasZone4Commands()) { - handleVolumeCmd(volumeZone4, channel, command, RotelCommand.ZONE4_VOLUME_UP, - RotelCommand.ZONE4_VOLUME_DOWN, RotelCommand.ZONE4_VOLUME_SET); + logger.debug("Command {} from channel {} ignored: fixed volume", command, channel); + } else if (model.hasVolumeControl() && (numZone == 0 || model.hasZoneCommands(numZone))) { + handleVolumeCmd(volumes[numZone], channel, command, getVolumeUpCommand(numZone), + getVolumeDownCommand(numZone), + CHANNEL_MAIN_VOLUME_UP_DOWN.equals(channel) + || CHANNEL_ZONE2_VOLUME_UP_DOWN.equals(channel) ? null + : getVolumeSetCommand(numZone)); + } else if (numZone == 2 && model.hasVolumeControl() && model.getNumberOfZones() > 1) { + selectZone(2, model.getZoneSelectCmd()); + handleVolumeCmd(volumes[numZone], channel, command, RotelCommand.VOLUME_UP, + RotelCommand.VOLUME_DOWN, + CHANNEL_ZONE2_VOLUME_UP_DOWN.equals(channel) ? null : RotelCommand.VOLUME_SET); } else { success = false; logger.debug("Command {} from channel {} failed: unavailable feature", command, channel); @@ -789,48 +707,18 @@ public class RotelHandler extends BaseThingHandler implements RotelMessageEventL break; case CHANNEL_MUTE: case CHANNEL_MAIN_MUTE: - if (!isPowerOn()) { - success = false; - logger.debug("Command {} from channel {} ignored: device in standby", command, channel); - } 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); - } - break; + case CHANNEL_ZONE1_MUTE: case CHANNEL_ZONE2_MUTE: - if (!powerZone2) { - success = false; - logger.debug("Command {} from channel {} ignored: zone 2 in standby", command, channel); - } else if (model.hasVolumeControl() && model.hasZone2Commands()) { - handleMuteCmd(false, channel, command, RotelCommand.ZONE2_MUTE_ON, - RotelCommand.ZONE2_MUTE_OFF, RotelCommand.ZONE2_MUTE_TOGGLE); - } else { - success = false; - logger.debug("Command {} from channel {} failed: unavailable feature", command, channel); - } - break; case CHANNEL_ZONE3_MUTE: - if (!powerZone3) { - success = false; - logger.debug("Command {} from channel {} ignored: zone 3 in standby", command, channel); - } else if (model.hasVolumeControl() && model.hasZone3Commands()) { - handleMuteCmd(false, channel, command, RotelCommand.ZONE3_MUTE_ON, - RotelCommand.ZONE3_MUTE_OFF, RotelCommand.ZONE3_MUTE_TOGGLE); - } else { - success = false; - logger.debug("Command {} from channel {} failed: unavailable feature", command, channel); - } - break; case CHANNEL_ZONE4_MUTE: - if (!powerZone4) { + if (!isPowerOn(numZone)) { success = false; - logger.debug("Command {} from channel {} ignored: zone 4 in standby", command, channel); - } else if (model.hasVolumeControl() && model.hasZone4Commands()) { - handleMuteCmd(false, channel, command, RotelCommand.ZONE4_MUTE_ON, - RotelCommand.ZONE4_MUTE_OFF, RotelCommand.ZONE4_MUTE_TOGGLE); + logger.debug("Command {} from channel {} ignored: zone {} in standby", command, channel, + numZone == 0 ? "device" : "zone " + numZone); + } else if (model.hasVolumeControl() && (numZone == 0 || model.hasZoneCommands(numZone))) { + handleMuteCmd(numZone == 0 && protocol == RotelProtocol.HEX, channel, command, + getMuteOnCommand(numZone), getMuteOffCommand(numZone), + getMuteToggleCommand(numZone)); } else { success = false; logger.debug("Command {} from channel {} failed: unavailable feature", command, channel); @@ -838,30 +726,46 @@ public class RotelHandler extends BaseThingHandler implements RotelMessageEventL break; case CHANNEL_BASS: case CHANNEL_MAIN_BASS: - if (!isPowerOn()) { + case CHANNEL_ZONE1_BASS: + case CHANNEL_ZONE2_BASS: + case CHANNEL_ZONE3_BASS: + case CHANNEL_ZONE4_BASS: + if (!isPowerOn(numZone)) { success = false; - logger.debug("Command {} from channel {} ignored: device in standby", command, channel); + logger.debug("Command {} from channel {} ignored: zone {} in standby", command, channel, + numZone == 0 ? "device" : "zone " + numZone); } else if (tcbypass) { + success = false; logger.debug("Command {} from channel {} ignored: tone control bypass is ON", command, channel); - updateChannelState(CHANNEL_BASS); + } else if (model.hasToneControl() && (numZone == 0 || model.hasZoneCommands(numZone))) { + handleToneCmd(basses[numZone], channel, command, 2, getBassUpCommand(numZone), + getBassDownCommand(numZone), getBassSetCommand(numZone)); } else { - handleToneCmd(bass, channel, command, 2, RotelCommand.BASS_UP, RotelCommand.BASS_DOWN, - RotelCommand.BASS_SET); + success = false; + logger.debug("Command {} from channel {} failed: unavailable feature", command, channel); } break; case CHANNEL_TREBLE: case CHANNEL_MAIN_TREBLE: - if (!isPowerOn()) { + case CHANNEL_ZONE1_TREBLE: + case CHANNEL_ZONE2_TREBLE: + case CHANNEL_ZONE3_TREBLE: + case CHANNEL_ZONE4_TREBLE: + if (!isPowerOn(numZone)) { success = false; - logger.debug("Command {} from channel {} ignored: device in standby", command, channel); + logger.debug("Command {} from channel {} ignored: zone {} in standby", command, channel, + numZone == 0 ? "device" : "zone " + numZone); } else if (tcbypass) { + success = false; logger.debug("Command {} from channel {} ignored: tone control bypass is ON", command, channel); - updateChannelState(CHANNEL_TREBLE); + } else if (model.hasToneControl() && (numZone == 0 || model.hasZoneCommands(numZone))) { + handleToneCmd(trebles[numZone], channel, command, 1, getTrebleUpCommand(numZone), + getTrebleDownCommand(numZone), getTrebleSetCommand(numZone)); } else { - handleToneCmd(treble, channel, command, 1, RotelCommand.TREBLE_UP, RotelCommand.TREBLE_DOWN, - RotelCommand.TREBLE_SET); + success = false; + logger.debug("Command {} from channel {} failed: unavailable feature", command, channel); } break; case CHANNEL_PLAY_CONTROL: @@ -887,6 +791,7 @@ public class RotelHandler extends BaseThingHandler implements RotelMessageEventL } break; case CHANNEL_BRIGHTNESS: + case CHANNEL_ALL_BRIGHTNESS: if (!isPowerOn()) { success = false; logger.debug("Command {} from channel {} ignored: device in standby", command, channel); @@ -919,15 +824,20 @@ public class RotelHandler extends BaseThingHandler implements RotelMessageEventL } break; case CHANNEL_BALANCE: - if (!isPowerOn()) { + case CHANNEL_ZONE1_BALANCE: + case CHANNEL_ZONE2_BALANCE: + case CHANNEL_ZONE3_BALANCE: + case CHANNEL_ZONE4_BALANCE: + if (!isPowerOn(numZone)) { success = false; - logger.debug("Command {} from channel {} ignored: device in standby", command, channel); + logger.debug("Command {} from channel {} ignored: zone {} in standby", command, channel, + numZone == 0 ? "device" : "zone " + numZone); } else if (!model.hasBalanceControl() || protocol == RotelProtocol.HEX) { success = false; logger.debug("Command {} from channel {} failed: unavailable feature", command, channel); } else { - handleBalanceCmd(channel, command, RotelCommand.BALANCE_LEFT, RotelCommand.BALANCE_RIGHT, - RotelCommand.BALANCE_SET); + handleBalanceCmd(channel, command, getBalanceLeftCommand(numZone), + getBalanceRightCommand(numZone), getBalanceSetCommand(numZone)); } break; case CHANNEL_SPEAKER_A: @@ -1110,8 +1020,8 @@ public class RotelHandler extends BaseThingHandler implements RotelMessageEventL if (command instanceof OnOffType) { if (command == OnOffType.ON) { sendCommand(onCmd); - bass = 0; - treble = 0; + basses[0] = 0; + trebles[0] = 0; updateChannelState(CHANNEL_BASS); updateChannelState(CHANNEL_TREBLE); } else if (command == OnOffType.OFF) { @@ -1207,11 +1117,11 @@ public class RotelHandler extends BaseThingHandler implements RotelMessageEventL */ private void selectZone(int zone, @Nullable RotelCommand selectCommand) throws RotelException, InterruptedException { - if (protocol == RotelProtocol.HEX && model.getNbAdditionalZones() >= 1 && zone >= 1 && zone != currentZone + if (protocol == RotelProtocol.HEX && model.getNumberOfZones() > 1 && zone >= 1 && zone != currentZone && selectCommand != null) { int nbSelect; if (zone < currentZone) { - nbSelect = zone + model.getNbAdditionalZones() - currentZone; + nbSelect = zone + model.getNumberOfZones() - 1 - currentZone; if (isPowerOn() && selectCommand == RotelCommand.RECORD_FONCTION_SELECT) { nbSelect++; } @@ -1287,6 +1197,53 @@ public class RotelHandler extends BaseThingHandler implements RotelMessageEventL if (!KEY_ERROR.equals(key)) { updateStatus(ThingStatus.ONLINE); } + int numZone = 0; + switch (key) { + case KEY_INPUT_ZONE1: + case KEY_VOLUME_ZONE1: + case KEY_MUTE_ZONE1: + case KEY_BASS_ZONE1: + case KEY_TREBLE_ZONE1: + case KEY_BALANCE_ZONE1: + case KEY_FREQ_ZONE1: + numZone = 1; + break; + case KEY_POWER_ZONE2: + case KEY_SOURCE_ZONE2: + case KEY_INPUT_ZONE2: + case KEY_VOLUME_ZONE2: + case KEY_MUTE_ZONE2: + case KEY_BASS_ZONE2: + case KEY_TREBLE_ZONE2: + case KEY_BALANCE_ZONE2: + case KEY_FREQ_ZONE2: + numZone = 2; + break; + case KEY_POWER_ZONE3: + case KEY_SOURCE_ZONE3: + case KEY_INPUT_ZONE3: + case KEY_VOLUME_ZONE3: + case KEY_MUTE_ZONE3: + case KEY_BASS_ZONE3: + case KEY_TREBLE_ZONE3: + case KEY_BALANCE_ZONE3: + case KEY_FREQ_ZONE3: + numZone = 3; + break; + case KEY_POWER_ZONE4: + case KEY_SOURCE_ZONE4: + case KEY_INPUT_ZONE4: + case KEY_VOLUME_ZONE4: + case KEY_MUTE_ZONE4: + case KEY_BASS_ZONE4: + case KEY_TREBLE_ZONE4: + case KEY_BALANCE_ZONE4: + case KEY_FREQ_ZONE4: + numZone = 4; + break; + default: + break; + } try { switch (key) { case KEY_ERROR: @@ -1314,6 +1271,11 @@ public class RotelHandler extends BaseThingHandler implements RotelMessageEventL handlePowerOn(); } else if (STANDBY.equalsIgnoreCase(value)) { handlePowerOff(); + if (model.getNumberOfZones() > 1 && !powerControlPerZone) { + for (int zone = 1; zone <= model.getNumberOfZones(); zone++) { + handlePowerOffZone(zone); + } + } } else if (POWER_OFF_DELAYED.equalsIgnoreCase(value)) { schedulePowerOffJob(false); } else { @@ -1321,28 +1283,12 @@ public class RotelHandler extends BaseThingHandler implements RotelMessageEventL } break; case KEY_POWER_ZONE2: - if (POWER_ON.equalsIgnoreCase(value)) { - handlePowerOnZone2(); - } else if (STANDBY.equalsIgnoreCase(value)) { - handlePowerOffZone2(); - } else { - throw new RotelException("Invalid value"); - } - break; case KEY_POWER_ZONE3: - if (POWER_ON.equalsIgnoreCase(value)) { - handlePowerOnZone3(); - } else if (STANDBY.equalsIgnoreCase(value)) { - handlePowerOffZone3(); - } else { - throw new RotelException("Invalid value"); - } - break; case KEY_POWER_ZONE4: if (POWER_ON.equalsIgnoreCase(value)) { - handlePowerOnZone4(); + handlePowerOnZone(numZone); } else if (STANDBY.equalsIgnoreCase(value)) { - handlePowerOffZone4(); + handlePowerOffZone(numZone); } else { throw new RotelException("Invalid value"); } @@ -1362,99 +1308,50 @@ public class RotelHandler extends BaseThingHandler implements RotelMessageEventL } break; case KEY_VOLUME: - if (MSG_VALUE_MIN.equalsIgnoreCase(value)) { - volume = minVolume; + case KEY_VOLUME_ZONE1: + case KEY_VOLUME_ZONE2: + case KEY_VOLUME_ZONE3: + case KEY_VOLUME_ZONE4: + fixedVolumeZones[numZone] = false; + if (MSG_VALUE_FIX.equalsIgnoreCase(value)) { + fixedVolumeZones[numZone] = true; + } else if (MSG_VALUE_MIN.equalsIgnoreCase(value)) { + volumes[numZone] = minVolume; } else if (MSG_VALUE_MAX.equalsIgnoreCase(value)) { - volume = maxVolume; + volumes[numZone] = maxVolume; } else { - volume = Integer.parseInt(value); + volumes[numZone] = Integer.parseInt(value); + } + if (numZone == 0) { + updateChannelState(CHANNEL_VOLUME); + updateChannelState(CHANNEL_MAIN_VOLUME); + updateChannelState(CHANNEL_MAIN_VOLUME_UP_DOWN); + } else { + updateGroupChannelState(numZone, CHANNEL_VOLUME); + updateGroupChannelState(numZone, CHANNEL_VOLUME_UP_DOWN); } - updateChannelState(CHANNEL_VOLUME); - updateChannelState(CHANNEL_MAIN_VOLUME); - updateChannelState(CHANNEL_MAIN_VOLUME_UP_DOWN); break; case KEY_MUTE: - if (MSG_VALUE_ON.equalsIgnoreCase(value)) { - mute = true; - updateChannelState(CHANNEL_MUTE); - updateChannelState(CHANNEL_MAIN_MUTE); - } else if (MSG_VALUE_OFF.equalsIgnoreCase(value)) { - mute = false; - updateChannelState(CHANNEL_MUTE); - updateChannelState(CHANNEL_MAIN_MUTE); - } else { - throw new RotelException("Invalid value"); - } - break; - case KEY_VOLUME_ZONE2: - fixedVolumeZone2 = false; - if (MSG_VALUE_FIX.equalsIgnoreCase(value)) { - fixedVolumeZone2 = true; - } else if (MSG_VALUE_MIN.equalsIgnoreCase(value)) { - volumeZone2 = minVolume; - } else if (MSG_VALUE_MAX.equalsIgnoreCase(value)) { - volumeZone2 = maxVolume; - } else { - volumeZone2 = Integer.parseInt(value); - } - updateChannelState(CHANNEL_ZONE2_VOLUME); - updateChannelState(CHANNEL_ZONE2_VOLUME_UP_DOWN); - break; - case KEY_VOLUME_ZONE3: - fixedVolumeZone3 = false; - if (MSG_VALUE_FIX.equalsIgnoreCase(value)) { - fixedVolumeZone3 = true; - } else if (MSG_VALUE_MIN.equalsIgnoreCase(value)) { - volumeZone3 = minVolume; - } else if (MSG_VALUE_MAX.equalsIgnoreCase(value)) { - volumeZone3 = maxVolume; - } else { - volumeZone3 = Integer.parseInt(value); - } - updateChannelState(CHANNEL_ZONE3_VOLUME); - break; - case KEY_VOLUME_ZONE4: - fixedVolumeZone4 = false; - if (MSG_VALUE_FIX.equalsIgnoreCase(value)) { - fixedVolumeZone4 = true; - } else if (MSG_VALUE_MIN.equalsIgnoreCase(value)) { - volumeZone4 = minVolume; - } else if (MSG_VALUE_MAX.equalsIgnoreCase(value)) { - volumeZone4 = maxVolume; - } else { - volumeZone4 = Integer.parseInt(value); - } - updateChannelState(CHANNEL_ZONE4_VOLUME); - break; + case KEY_MUTE_ZONE1: case KEY_MUTE_ZONE2: - if (MSG_VALUE_ON.equalsIgnoreCase(value)) { - muteZone2 = true; - updateChannelState(CHANNEL_ZONE2_MUTE); - } else if (MSG_VALUE_OFF.equalsIgnoreCase(value)) { - muteZone2 = false; - updateChannelState(CHANNEL_ZONE2_MUTE); - } else { - throw new RotelException("Invalid value"); - } - break; case KEY_MUTE_ZONE3: - if (MSG_VALUE_ON.equalsIgnoreCase(value)) { - muteZone3 = true; - updateChannelState(CHANNEL_ZONE3_MUTE); - } else if (MSG_VALUE_OFF.equalsIgnoreCase(value)) { - muteZone3 = false; - updateChannelState(CHANNEL_ZONE3_MUTE); - } else { - throw new RotelException("Invalid value"); - } - break; case KEY_MUTE_ZONE4: if (MSG_VALUE_ON.equalsIgnoreCase(value)) { - muteZone4 = true; - updateChannelState(CHANNEL_ZONE4_MUTE); + mutes[numZone] = true; + if (numZone == 0) { + updateChannelState(CHANNEL_MUTE); + updateChannelState(CHANNEL_MAIN_MUTE); + } else { + updateGroupChannelState(numZone, CHANNEL_MUTE); + } } else if (MSG_VALUE_OFF.equalsIgnoreCase(value)) { - muteZone4 = false; - updateChannelState(CHANNEL_ZONE4_MUTE); + mutes[numZone] = false; + if (numZone == 0) { + updateChannelState(CHANNEL_MUTE); + updateChannelState(CHANNEL_MAIN_MUTE); + } else { + updateGroupChannelState(numZone, CHANNEL_MUTE); + } } else { throw new RotelException("Invalid value"); } @@ -1467,29 +1364,45 @@ public class RotelHandler extends BaseThingHandler implements RotelMessageEventL minToneLevel, maxToneLevel); break; case KEY_BASS: + case KEY_BASS_ZONE1: + case KEY_BASS_ZONE2: + case KEY_BASS_ZONE3: + case KEY_BASS_ZONE4: if (MSG_VALUE_MIN.equalsIgnoreCase(value)) { - bass = minToneLevel; + basses[numZone] = minToneLevel; } else if (MSG_VALUE_MAX.equalsIgnoreCase(value)) { - bass = maxToneLevel; + basses[numZone] = maxToneLevel; } else { - bass = Integer.parseInt(value); + basses[numZone] = Integer.parseInt(value); + } + if (numZone == 0) { + updateChannelState(CHANNEL_BASS); + updateChannelState(CHANNEL_MAIN_BASS); + } else { + updateGroupChannelState(numZone, CHANNEL_BASS); } - updateChannelState(CHANNEL_BASS); - updateChannelState(CHANNEL_MAIN_BASS); break; case KEY_TREBLE: + case KEY_TREBLE_ZONE1: + case KEY_TREBLE_ZONE2: + case KEY_TREBLE_ZONE3: + case KEY_TREBLE_ZONE4: if (MSG_VALUE_MIN.equalsIgnoreCase(value)) { - treble = minToneLevel; + trebles[numZone] = minToneLevel; } else if (MSG_VALUE_MAX.equalsIgnoreCase(value)) { - treble = maxToneLevel; + trebles[numZone] = maxToneLevel; } else { - treble = Integer.parseInt(value); + trebles[numZone] = Integer.parseInt(value); + } + if (numZone == 0) { + updateChannelState(CHANNEL_TREBLE); + updateChannelState(CHANNEL_MAIN_TREBLE); + } else { + updateGroupChannelState(numZone, CHANNEL_TREBLE); } - updateChannelState(CHANNEL_TREBLE); - updateChannelState(CHANNEL_MAIN_TREBLE); break; case KEY_SOURCE: - source = model.getSourceFromCommand(RotelCommand.getFromAsciiCommand(value)); + sources[0] = model.getSourceFromCommand(RotelCommand.getFromAsciiCommand(value)); updateChannelState(CHANNEL_SOURCE); updateChannelState(CHANNEL_MAIN_SOURCE); break; @@ -1498,16 +1411,14 @@ public class RotelHandler extends BaseThingHandler implements RotelMessageEventL updateChannelState(CHANNEL_MAIN_RECORD_SOURCE); break; case KEY_SOURCE_ZONE2: - sourceZone2 = model.getZone2SourceFromCommand(RotelCommand.getFromAsciiCommand(value)); - updateChannelState(CHANNEL_ZONE2_SOURCE); - break; case KEY_SOURCE_ZONE3: - sourceZone3 = model.getZone3SourceFromCommand(RotelCommand.getFromAsciiCommand(value)); - updateChannelState(CHANNEL_ZONE3_SOURCE); - break; case KEY_SOURCE_ZONE4: - sourceZone4 = model.getZone4SourceFromCommand(RotelCommand.getFromAsciiCommand(value)); - updateChannelState(CHANNEL_ZONE4_SOURCE); + case KEY_INPUT_ZONE1: + case KEY_INPUT_ZONE2: + case KEY_INPUT_ZONE3: + case KEY_INPUT_ZONE4: + sources[numZone] = model.getZoneSourceFromCommand(RotelCommand.getFromAsciiCommand(value), numZone); + updateGroupChannelState(numZone, CHANNEL_SOURCE); break; case KEY_DSP_MODE: if ("dolby_pliix_movie".equals(value)) { @@ -1538,26 +1449,36 @@ public class RotelHandler extends BaseThingHandler implements RotelMessageEventL } break; case KEY_TRACK: - if (source.getName().equals("CD") && !model.hasSourceControl()) { + RotelSource source = sources[0]; + if (source != null && source.getName().equals("CD") && !model.hasSourceControl()) { track = Integer.parseInt(value); updateChannelState(CHANNEL_TRACK); } break; case KEY_FREQ: - if (MSG_VALUE_OFF.equalsIgnoreCase(value)) { - frequency = 0.0; + case KEY_FREQ_ZONE1: + case KEY_FREQ_ZONE2: + case KEY_FREQ_ZONE3: + case KEY_FREQ_ZONE4: + if (MSG_VALUE_OFF.equalsIgnoreCase(value) || MSG_VALUE_NONE.equalsIgnoreCase(value)) { + frequencies[numZone] = 0.0; } else { // Suppress a potential ending "k" or "K" if (value.toUpperCase().endsWith("K")) { value = value.substring(0, value.length() - 1); } - frequency = Double.parseDouble(value); + frequencies[numZone] = Double.parseDouble(value); + } + if (numZone == 0) { + updateChannelState(CHANNEL_FREQUENCY); + } else { + updateGroupChannelState(numZone, CHANNEL_FREQUENCY); } - updateChannelState(CHANNEL_FREQUENCY); break; case KEY_DIMMER: brightness = Integer.parseInt(value); updateChannelState(CHANNEL_BRIGHTNESS); + updateChannelState(CHANNEL_ALL_BRIGHTNESS); break; case KEY_UPDATE_MODE: case KEY_DISPLAY_UPDATE: @@ -1585,18 +1506,26 @@ public class RotelHandler extends BaseThingHandler implements RotelMessageEventL } break; case KEY_BALANCE: + case KEY_BALANCE_ZONE1: + case KEY_BALANCE_ZONE2: + case KEY_BALANCE_ZONE3: + case KEY_BALANCE_ZONE4: if (MSG_VALUE_MIN.equalsIgnoreCase(value)) { - balance = minBalanceLevel; + balances[numZone] = minBalanceLevel; } else if (MSG_VALUE_MAX.equalsIgnoreCase(value)) { - balance = maxBalanceLevel; + balances[numZone] = maxBalanceLevel; } else if (value.toUpperCase().startsWith("L")) { - balance = -Integer.parseInt(value.substring(1)); + balances[numZone] = -Integer.parseInt(value.substring(1)); } else if (value.toUpperCase().startsWith("R")) { - balance = Integer.parseInt(value.substring(1)); + balances[numZone] = Integer.parseInt(value.substring(1)); } else { - balance = Integer.parseInt(value); + balances[numZone] = Integer.parseInt(value); + } + if (numZone == 0) { + updateChannelState(CHANNEL_BALANCE); + } else { + updateGroupChannelState(numZone, CHANNEL_BALANCE); } - updateChannelState(CHANNEL_BALANCE); break; case KEY_SPEAKER: if (MSG_VALUE_SPEAKER_A.equalsIgnoreCase(value)) { @@ -1623,6 +1552,12 @@ public class RotelHandler extends BaseThingHandler implements RotelMessageEventL throw new RotelException("Invalid value"); } break; + case KEY_MODEL: + getThing().setProperty(Thing.PROPERTY_MODEL_ID, value); + break; + case KEY_VERSION: + getThing().setProperty(Thing.PROPERTY_FIRMWARE_VERSION, value); + break; default: logger.debug("onNewMessageEvent: unhandled key {}", key); break; @@ -1636,10 +1571,11 @@ public class RotelHandler extends BaseThingHandler implements RotelMessageEventL * Handle the received information that device power (main zone) is ON */ private void handlePowerOn() { - Boolean prev = power; - power = true; + Boolean prev = powers[0]; + powers[0] = true; updateChannelState(CHANNEL_POWER); updateChannelState(CHANNEL_MAIN_POWER); + updateChannelState(CHANNEL_ALL_POWER); if ((prev == null) || !prev) { schedulePowerOnJob(); } @@ -1649,24 +1585,15 @@ public class RotelHandler extends BaseThingHandler implements RotelMessageEventL * Handle the received information that device power (main zone) is OFF */ private void handlePowerOff() { - cancelPowerOnJob(); - power = false; + cancelPowerOnZoneJob(0); + powers[0] = false; updateChannelState(CHANNEL_POWER); - updateChannelState(CHANNEL_MAIN_POWER); updateChannelState(CHANNEL_SOURCE); - updateChannelState(CHANNEL_MAIN_SOURCE); - updateChannelState(CHANNEL_MAIN_RECORD_SOURCE); updateChannelState(CHANNEL_DSP); - updateChannelState(CHANNEL_MAIN_DSP); updateChannelState(CHANNEL_VOLUME); - updateChannelState(CHANNEL_MAIN_VOLUME); - updateChannelState(CHANNEL_MAIN_VOLUME_UP_DOWN); updateChannelState(CHANNEL_MUTE); - updateChannelState(CHANNEL_MAIN_MUTE); updateChannelState(CHANNEL_BASS); - updateChannelState(CHANNEL_MAIN_BASS); updateChannelState(CHANNEL_TREBLE); - updateChannelState(CHANNEL_MAIN_TREBLE); updateChannelState(CHANNEL_PLAY_CONTROL); updateChannelState(CHANNEL_TRACK); updateChannelState(CHANNEL_FREQUENCY); @@ -1675,79 +1602,48 @@ public class RotelHandler extends BaseThingHandler implements RotelMessageEventL updateChannelState(CHANNEL_BALANCE); updateChannelState(CHANNEL_SPEAKER_A); updateChannelState(CHANNEL_SPEAKER_B); + + updateChannelState(CHANNEL_MAIN_POWER); + updateChannelState(CHANNEL_MAIN_SOURCE); + updateChannelState(CHANNEL_MAIN_RECORD_SOURCE); + updateChannelState(CHANNEL_MAIN_DSP); + updateChannelState(CHANNEL_MAIN_VOLUME); + updateChannelState(CHANNEL_MAIN_VOLUME_UP_DOWN); + updateChannelState(CHANNEL_MAIN_MUTE); + updateChannelState(CHANNEL_MAIN_BASS); + updateChannelState(CHANNEL_MAIN_TREBLE); + + updateChannelState(CHANNEL_ALL_POWER); + updateChannelState(CHANNEL_ALL_BRIGHTNESS); } /** - * Handle the received information that zone 2 power is ON + * Handle the received information that a zone power is ON */ - private void handlePowerOnZone2() { - boolean prev = powerZone2; - powerZone2 = true; - updateChannelState(CHANNEL_ZONE2_POWER); - if (!prev) { - schedulePowerOnZone2Job(); + private void handlePowerOnZone(int numZone) { + Boolean prev = powers[numZone]; + powers[numZone] = true; + updateGroupChannelState(numZone, CHANNEL_POWER); + if ((prev == null) || !prev) { + schedulePowerOnZoneJob(numZone, getVolumeDownCommand(numZone), getVolumeUpCommand(numZone)); } } /** - * Handle the received information that zone 2 power is OFF + * Handle the received information that a zone power is OFF */ - private void handlePowerOffZone2() { - cancelPowerOnZone2Job(); - powerZone2 = false; - updateChannelState(CHANNEL_ZONE2_POWER); - updateChannelState(CHANNEL_ZONE2_SOURCE); - updateChannelState(CHANNEL_ZONE2_VOLUME); - updateChannelState(CHANNEL_ZONE2_VOLUME_UP_DOWN); - updateChannelState(CHANNEL_ZONE2_MUTE); - } - - /** - * Handle the received information that zone 3 power is ON - */ - private void handlePowerOnZone3() { - boolean prev = powerZone3; - powerZone3 = true; - updateChannelState(CHANNEL_ZONE3_POWER); - if (!prev) { - schedulePowerOnZone3Job(); - } - } - - /** - * Handle the received information that zone 3 power is OFF - */ - private void handlePowerOffZone3() { - cancelPowerOnZone3Job(); - powerZone3 = false; - updateChannelState(CHANNEL_ZONE3_POWER); - updateChannelState(CHANNEL_ZONE3_SOURCE); - updateChannelState(CHANNEL_ZONE3_VOLUME); - updateChannelState(CHANNEL_ZONE3_MUTE); - } - - /** - * Handle the received information that zone 4 power is ON - */ - private void handlePowerOnZone4() { - boolean prev = powerZone4; - powerZone4 = true; - updateChannelState(CHANNEL_ZONE4_POWER); - if (!prev) { - schedulePowerOnZone4Job(); - } - } - - /** - * Handle the received information that zone 4 power is OFF - */ - private void handlePowerOffZone4() { - cancelPowerOnZone4Job(); - powerZone4 = false; - updateChannelState(CHANNEL_ZONE4_POWER); - updateChannelState(CHANNEL_ZONE4_SOURCE); - updateChannelState(CHANNEL_ZONE4_VOLUME); - updateChannelState(CHANNEL_ZONE4_MUTE); + private void handlePowerOffZone(int numZone) { + cancelPowerOnZoneJob(numZone); + powers[numZone] = false; + updateGroupChannelState(numZone, CHANNEL_POWER); + updateGroupChannelState(numZone, CHANNEL_SOURCE); + updateGroupChannelState(numZone, CHANNEL_VOLUME); + updateGroupChannelState(numZone, CHANNEL_MUTE); + updateGroupChannelState(numZone, CHANNEL_BASS); + updateGroupChannelState(numZone, CHANNEL_TREBLE); + updateGroupChannelState(numZone, CHANNEL_BALANCE); + updateGroupChannelState(numZone, CHANNEL_FREQUENCY); + updateGroupChannelState(numZone, CHANNEL_VOLUME_UP_DOWN); } /** @@ -1762,9 +1658,9 @@ public class RotelHandler extends BaseThingHandler implements RotelMessageEventL logger.debug("Power OFF job"); handlePowerOff(); if (switchOffAllZones) { - handlePowerOffZone2(); - handlePowerOffZone3(); - handlePowerOffZone4(); + for (int zone = 1; zone <= model.getNumberOfZones(); zone++) { + handlePowerOffZone(zone); + } } }, 2000, TimeUnit.MILLISECONDS); } @@ -1785,20 +1681,20 @@ public class RotelHandler extends BaseThingHandler implements RotelMessageEventL */ private void schedulePowerOnJob() { logger.debug("Schedule power ON job"); - cancelPowerOnJob(); - powerOnJob = scheduler.schedule(() -> { + cancelPowerOnZoneJob(0); + powerOnZoneJobs[0] = scheduler.schedule(() -> { synchronized (sequenceLock) { logger.debug("Power ON job"); try { switch (protocol) { case HEX: if (model.getRespNbChars() <= 13 && model.hasVolumeControl()) { - sendCommand(getVolumeDownCommand()); + sendCommand(getVolumeDownCommand(0)); Thread.sleep(100); - sendCommand(getVolumeUpCommand()); + sendCommand(getVolumeUpCommand(0)); Thread.sleep(100); } - if (model.getNbAdditionalZones() >= 1) { + if (model.getNumberOfZones() > 1) { if (currentZone != 1 && model.getZoneSelectCmd() == RotelCommand.RECORD_FONCTION_SELECT) { selectZone(1, model.getZoneSelectCmd()); @@ -1864,8 +1760,10 @@ public class RotelHandler extends BaseThingHandler implements RotelMessageEventL Thread.sleep(SLEEP_INTV); sendCommand(RotelCommand.TREBLE); Thread.sleep(SLEEP_INTV); - sendCommand(RotelCommand.TONE_CONTROLS); - Thread.sleep(SLEEP_INTV); + if (model.canGetBypassStatus()) { + sendCommand(RotelCommand.TONE_CONTROLS); + Thread.sleep(SLEEP_INTV); + } } } if (model.hasBalanceControl()) { @@ -1873,8 +1771,10 @@ public class RotelHandler extends BaseThingHandler implements RotelMessageEventL Thread.sleep(SLEEP_INTV); } if (model.hasPlayControl()) { + RotelSource source = sources[0]; if (model != RotelModel.RCD1570 && model != RotelModel.RCD1572 - && (model != RotelModel.RCX1500 || !source.getName().equals("CD"))) { + && (model != RotelModel.RCX1500 || source == null + || !source.getName().equals("CD"))) { sendCommand(RotelCommand.PLAY_STATUS); Thread.sleep(SLEEP_INTV); } else { @@ -1903,7 +1803,11 @@ public class RotelHandler extends BaseThingHandler implements RotelMessageEventL sendCommand(RotelCommand.UPDATE_AUTO); Thread.sleep(SLEEP_INTV); if (model.hasSourceControl()) { - sendCommand(RotelCommand.SOURCE); + if (model.getNumberOfZones() > 1) { + sendCommand(RotelCommand.INPUT); + } else { + sendCommand(RotelCommand.SOURCE); + } Thread.sleep(SLEEP_INTV); } if (model.hasVolumeControl()) { @@ -1917,8 +1821,10 @@ public class RotelHandler extends BaseThingHandler implements RotelMessageEventL Thread.sleep(SLEEP_INTV); sendCommand(RotelCommand.TREBLE); Thread.sleep(SLEEP_INTV); - sendCommand(RotelCommand.TCBYPASS); - Thread.sleep(SLEEP_INTV); + if (model.canGetBypassStatus()) { + sendCommand(RotelCommand.TCBYPASS); + Thread.sleep(SLEEP_INTV); + } } if (model.hasBalanceControl()) { sendCommand(RotelCommand.BALANCE); @@ -1927,7 +1833,8 @@ public class RotelHandler extends BaseThingHandler implements RotelMessageEventL if (model.hasPlayControl()) { sendCommand(RotelCommand.PLAY_STATUS); Thread.sleep(SLEEP_INTV); - if (source.getName().equals("CD") && !model.hasSourceControl()) { + RotelSource source = sources[0]; + if (source != null && source.getName().equals("CD") && !model.hasSourceControl()) { sendCommand(RotelCommand.TRACK); Thread.sleep(SLEEP_INTV); } @@ -1948,6 +1855,10 @@ public class RotelHandler extends BaseThingHandler implements RotelMessageEventL sendCommand(RotelCommand.SPEAKER); Thread.sleep(SLEEP_INTV); } + sendCommand(RotelCommand.MODEL); + Thread.sleep(SLEEP_INTV); + sendCommand(RotelCommand.VERSION); + Thread.sleep(SLEEP_INTV); break; } } catch (RotelException e) { @@ -1964,41 +1875,29 @@ public class RotelHandler extends BaseThingHandler implements RotelMessageEventL } /** - * Cancel the job scheduled when the device power (main zone) switched ON + * Schedule the job to run with a few seconds delay when the zone power switched ON */ - private void cancelPowerOnJob() { - ScheduledFuture powerOnJob = this.powerOnJob; - if (powerOnJob != null && !powerOnJob.isCancelled()) { - powerOnJob.cancel(true); - this.powerOnJob = null; - } - } - - /** - * Schedule the job to run with a few seconds delay when the zone 2 power switched ON - */ - private void schedulePowerOnZone2Job() { - logger.debug("Schedule power ON zone 2 job"); - cancelPowerOnZone2Job(); - powerOnZone2Job = scheduler.schedule(() -> { + private void schedulePowerOnZoneJob(int numZone, RotelCommand volumeDown, RotelCommand volumeUp) { + logger.debug("Schedule power ON zone {} job", numZone); + cancelPowerOnZoneJob(numZone); + powerOnZoneJobs[numZone] = scheduler.schedule(() -> { synchronized (sequenceLock) { - logger.debug("Power ON zone 2 job"); + logger.debug("Power ON zone {} job", numZone); try { - if (protocol == RotelProtocol.HEX && model.getNbAdditionalZones() >= 1) { - selectZone(2, model.getZoneSelectCmd()); - sendCommand( - model.hasZone2Commands() ? RotelCommand.ZONE2_VOLUME_DOWN : RotelCommand.VOLUME_DOWN); + if (protocol == RotelProtocol.HEX && model.getNumberOfZones() >= numZone) { + selectZone(numZone, model.getZoneSelectCmd()); + sendCommand(model.hasZoneCommands(numZone) ? volumeDown : RotelCommand.VOLUME_DOWN); Thread.sleep(100); - sendCommand(model.hasZone2Commands() ? RotelCommand.ZONE2_VOLUME_UP : RotelCommand.VOLUME_UP); + sendCommand(model.hasZoneCommands(numZone) ? volumeUp : RotelCommand.VOLUME_UP); Thread.sleep(100); } } catch (RotelException e) { - logger.debug("Init sequence zone 2 failed: {}", e.getMessage()); + logger.debug("Init sequence zone {} failed: {}", numZone, e.getMessage()); updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, - "@text/offline.comm-error-init-sequence-zone [\"2\"]"); + String.format("@text/offline.comm-error-init-sequence-zone [\"%d\"]", numZone)); closeConnection(); } catch (InterruptedException e) { - logger.debug("Init sequence zone 2 interrupted: {}", e.getMessage()); + logger.debug("Init sequence zone {} interrupted: {}", numZone, e.getMessage()); Thread.currentThread().interrupt(); } } @@ -2006,97 +1905,13 @@ public class RotelHandler extends BaseThingHandler implements RotelMessageEventL } /** - * Cancel the job scheduled when the zone 2 power switched ON + * Cancel the job scheduled when the device power (main zone) or a zone power switched ON */ - private void cancelPowerOnZone2Job() { - ScheduledFuture powerOnZone2Job = this.powerOnZone2Job; - if (powerOnZone2Job != null && !powerOnZone2Job.isCancelled()) { - powerOnZone2Job.cancel(true); - this.powerOnZone2Job = null; - } - } - - /** - * Schedule the job to run with a few seconds delay when the zone 3 power switched ON - */ - private void schedulePowerOnZone3Job() { - logger.debug("Schedule power ON zone 3 job"); - cancelPowerOnZone3Job(); - powerOnZone3Job = scheduler.schedule(() -> { - synchronized (sequenceLock) { - logger.debug("Power ON zone 3 job"); - try { - if (protocol == RotelProtocol.HEX && model.getNbAdditionalZones() >= 2) { - selectZone(3, model.getZoneSelectCmd()); - sendCommand( - model.hasZone3Commands() ? RotelCommand.ZONE3_VOLUME_DOWN : RotelCommand.VOLUME_DOWN); - Thread.sleep(100); - sendCommand(model.hasZone3Commands() ? RotelCommand.ZONE3_VOLUME_UP : RotelCommand.VOLUME_UP); - Thread.sleep(100); - } - } catch (RotelException e) { - logger.debug("Init sequence zone 3 failed: {}", e.getMessage()); - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, - "@text/offline.comm-error-init-sequence-zone [\"3\"]"); - closeConnection(); - } catch (InterruptedException e) { - logger.debug("Init sequence zone 3 interrupted: {}", e.getMessage()); - Thread.currentThread().interrupt(); - } - } - }, 2500, TimeUnit.MILLISECONDS); - } - - /** - * Cancel the job scheduled when the zone 3 power switched ON - */ - private void cancelPowerOnZone3Job() { - ScheduledFuture powerOnZone3Job = this.powerOnZone3Job; - if (powerOnZone3Job != null && !powerOnZone3Job.isCancelled()) { - powerOnZone3Job.cancel(true); - this.powerOnZone3Job = null; - } - } - - /** - * Schedule the job to run with a few seconds delay when the zone 4 power switched ON - */ - private void schedulePowerOnZone4Job() { - logger.debug("Schedule power ON zone 4 job"); - cancelPowerOnZone4Job(); - powerOnZone4Job = scheduler.schedule(() -> { - synchronized (sequenceLock) { - logger.debug("Power ON zone 4 job"); - try { - if (protocol == RotelProtocol.HEX && model.getNbAdditionalZones() >= 3) { - selectZone(4, model.getZoneSelectCmd()); - sendCommand( - model.hasZone4Commands() ? RotelCommand.ZONE4_VOLUME_DOWN : RotelCommand.VOLUME_DOWN); - Thread.sleep(100); - sendCommand(model.hasZone4Commands() ? RotelCommand.ZONE4_VOLUME_UP : RotelCommand.VOLUME_UP); - Thread.sleep(100); - } - } catch (RotelException e) { - logger.debug("Init sequence zone 4 failed: {}", e.getMessage()); - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, - "@text/offline.comm-error-init-sequence-zone [\"4\"]"); - closeConnection(); - } catch (InterruptedException e) { - logger.debug("Init sequence zone 4 interrupted: {}", e.getMessage()); - Thread.currentThread().interrupt(); - } - } - }, 2500, TimeUnit.MILLISECONDS); - } - - /** - * Cancel the job scheduled when the zone 4 power switched ON - */ - private void cancelPowerOnZone4Job() { - ScheduledFuture powerOnZone4Job = this.powerOnZone4Job; - if (powerOnZone4Job != null && !powerOnZone4Job.isCancelled()) { - powerOnZone4Job.cancel(true); - this.powerOnZone4Job = null; + private void cancelPowerOnZoneJob(int numZone) { + ScheduledFuture powerOnZoneJob = powerOnZoneJobs[numZone]; + if (powerOnZoneJob != null && !powerOnZoneJob.isCancelled()) { + powerOnZoneJob.cancel(true); + powerOnZoneJobs[numZone] = null; } } @@ -2110,7 +1925,7 @@ public class RotelHandler extends BaseThingHandler implements RotelMessageEventL if (!connector.isConnected()) { logger.debug("Trying to reconnect..."); closeConnection(); - power = null; + powers[0] = null; String error = null; if (openConnection()) { synchronized (sequenceLock) { @@ -2129,9 +1944,9 @@ public class RotelHandler extends BaseThingHandler implements RotelMessageEventL } if (error != null) { handlePowerOff(); - handlePowerOffZone2(); - handlePowerOffZone3(); - handlePowerOffZone4(); + for (int zone = 1; zone <= model.getNumberOfZones(); zone++) { + handlePowerOffZone(zone); + } updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, error); } else { updateStatus(ThingStatus.ONLINE); @@ -2151,6 +1966,10 @@ public class RotelHandler extends BaseThingHandler implements RotelMessageEventL } } + private void updateGroupChannelState(int numZone, String channel) { + updateChannelState(String.format("zone%d#%s", numZone, channel)); + } + /** * Update the state of a channel * @@ -2161,51 +1980,79 @@ public class RotelHandler extends BaseThingHandler implements RotelMessageEventL return; } State state = UnDefType.UNDEF; + RotelSource localSource; + int numZone = 0; + switch (channel) { + case CHANNEL_ZONE1_SOURCE: + case CHANNEL_ZONE1_VOLUME: + case CHANNEL_ZONE1_MUTE: + case CHANNEL_ZONE1_BASS: + case CHANNEL_ZONE1_TREBLE: + case CHANNEL_ZONE1_BALANCE: + case CHANNEL_ZONE1_FREQUENCY: + numZone = 1; + break; + case CHANNEL_ZONE2_POWER: + case CHANNEL_ZONE2_SOURCE: + case CHANNEL_ZONE2_VOLUME: + case CHANNEL_ZONE2_VOLUME_UP_DOWN: + case CHANNEL_ZONE2_MUTE: + case CHANNEL_ZONE2_BASS: + case CHANNEL_ZONE2_TREBLE: + case CHANNEL_ZONE2_BALANCE: + case CHANNEL_ZONE2_FREQUENCY: + numZone = 2; + break; + case CHANNEL_ZONE3_POWER: + case CHANNEL_ZONE3_SOURCE: + case CHANNEL_ZONE3_VOLUME: + case CHANNEL_ZONE3_MUTE: + case CHANNEL_ZONE3_BASS: + case CHANNEL_ZONE3_TREBLE: + case CHANNEL_ZONE3_BALANCE: + case CHANNEL_ZONE3_FREQUENCY: + numZone = 3; + break; + case CHANNEL_ZONE4_POWER: + case CHANNEL_ZONE4_SOURCE: + case CHANNEL_ZONE4_VOLUME: + case CHANNEL_ZONE4_MUTE: + case CHANNEL_ZONE4_BASS: + case CHANNEL_ZONE4_TREBLE: + case CHANNEL_ZONE4_BALANCE: + case CHANNEL_ZONE4_FREQUENCY: + numZone = 4; + break; + default: + break; + } switch (channel) { case CHANNEL_POWER: case CHANNEL_MAIN_POWER: - Boolean po = power; - if (po != null) { - state = OnOffType.from(po.booleanValue()); - } - break; + case CHANNEL_ALL_POWER: case CHANNEL_ZONE2_POWER: - state = OnOffType.from(powerZone2); - break; case CHANNEL_ZONE3_POWER: - state = OnOffType.from(powerZone3); - break; case CHANNEL_ZONE4_POWER: - state = OnOffType.from(powerZone4); + Boolean powerZone = powers[numZone]; + if (powerZone != null) { + state = OnOffType.from(powerZone.booleanValue()); + } break; case CHANNEL_SOURCE: case CHANNEL_MAIN_SOURCE: - if (isPowerOn()) { - state = new StringType(source.getName()); + case CHANNEL_ZONE1_SOURCE: + case CHANNEL_ZONE2_SOURCE: + case CHANNEL_ZONE3_SOURCE: + case CHANNEL_ZONE4_SOURCE: + localSource = sources[numZone]; + if (isPowerOn(numZone) && localSource != null) { + state = new StringType(localSource.getName()); } break; case CHANNEL_MAIN_RECORD_SOURCE: - RotelSource recordSource = this.recordSource; - if (isPowerOn() && recordSource != null) { - state = new StringType(recordSource.getName()); - } - break; - case CHANNEL_ZONE2_SOURCE: - RotelSource sourceZone2 = this.sourceZone2; - if (powerZone2 && sourceZone2 != null) { - state = new StringType(sourceZone2.getName()); - } - break; - case CHANNEL_ZONE3_SOURCE: - RotelSource sourceZone3 = this.sourceZone3; - if (powerZone3 && sourceZone3 != null) { - state = new StringType(sourceZone3.getName()); - } - break; - case CHANNEL_ZONE4_SOURCE: - RotelSource sourceZone4 = this.sourceZone4; - if (powerZone4 && sourceZone4 != null) { - state = new StringType(sourceZone4.getName()); + localSource = recordSource; + if (isPowerOn() && localSource != null) { + state = new StringType(localSource.getName()); } break; case CHANNEL_DSP: @@ -2216,78 +2063,54 @@ public class RotelHandler extends BaseThingHandler implements RotelMessageEventL break; case CHANNEL_VOLUME: case CHANNEL_MAIN_VOLUME: - if (isPowerOn()) { + case CHANNEL_ZONE1_VOLUME: + case CHANNEL_ZONE2_VOLUME: + case CHANNEL_ZONE3_VOLUME: + case CHANNEL_ZONE4_VOLUME: + if (isPowerOn(numZone) && !fixedVolumeZones[numZone]) { long volumePct = Math - .round((double) (volume - minVolume) / (double) (maxVolume - minVolume) * 100.0); + .round((double) (volumes[numZone] - minVolume) / (double) (maxVolume - minVolume) * 100.0); state = new PercentType(BigDecimal.valueOf(volumePct)); } break; case CHANNEL_MAIN_VOLUME_UP_DOWN: - if (isPowerOn()) { - state = new DecimalType(volume); - } - break; - case CHANNEL_ZONE2_VOLUME: - if (powerZone2 && !fixedVolumeZone2) { - long volumePct = Math - .round((double) (volumeZone2 - minVolume) / (double) (maxVolume - minVolume) * 100.0); - state = new PercentType(BigDecimal.valueOf(volumePct)); - } - break; case CHANNEL_ZONE2_VOLUME_UP_DOWN: - if (powerZone2 && !fixedVolumeZone2) { - state = new DecimalType(volumeZone2); - } - break; - case CHANNEL_ZONE3_VOLUME: - if (powerZone3 && !fixedVolumeZone3) { - long volumePct = Math - .round((double) (volumeZone3 - minVolume) / (double) (maxVolume - minVolume) * 100.0); - state = new PercentType(BigDecimal.valueOf(volumePct)); - } - break; - case CHANNEL_ZONE4_VOLUME: - if (powerZone4 && !fixedVolumeZone4) { - long volumePct = Math - .round((double) (volumeZone4 - minVolume) / (double) (maxVolume - minVolume) * 100.0); - state = new PercentType(BigDecimal.valueOf(volumePct)); + if (isPowerOn(numZone) && !fixedVolumeZones[numZone]) { + state = new DecimalType(volumes[numZone]); } break; case CHANNEL_MUTE: case CHANNEL_MAIN_MUTE: - if (isPowerOn()) { - state = OnOffType.from(mute); - } - break; + case CHANNEL_ZONE1_MUTE: case CHANNEL_ZONE2_MUTE: - if (powerZone2) { - state = OnOffType.from(muteZone2); - } - break; case CHANNEL_ZONE3_MUTE: - if (powerZone3) { - state = OnOffType.from(muteZone3); - } - break; case CHANNEL_ZONE4_MUTE: - if (powerZone4) { - state = OnOffType.from(muteZone4); + if (isPowerOn(numZone)) { + state = OnOffType.from(mutes[numZone]); } break; case CHANNEL_BASS: case CHANNEL_MAIN_BASS: - if (isPowerOn()) { - state = new DecimalType(bass); + case CHANNEL_ZONE1_BASS: + case CHANNEL_ZONE2_BASS: + case CHANNEL_ZONE3_BASS: + case CHANNEL_ZONE4_BASS: + if (isPowerOn(numZone)) { + state = new DecimalType(basses[numZone]); } break; case CHANNEL_TREBLE: case CHANNEL_MAIN_TREBLE: - if (isPowerOn()) { - state = new DecimalType(treble); + case CHANNEL_ZONE1_TREBLE: + case CHANNEL_ZONE2_TREBLE: + case CHANNEL_ZONE3_TREBLE: + case CHANNEL_ZONE4_TREBLE: + if (isPowerOn(numZone)) { + state = new DecimalType(trebles[numZone]); } break; case CHANNEL_TRACK: - if (track > 0 && isPowerOn()) { + if (isPowerOn() && track > 0) { state = new DecimalType(track); } break; @@ -2305,8 +2128,12 @@ public class RotelHandler extends BaseThingHandler implements RotelMessageEventL } break; case CHANNEL_FREQUENCY: - if (frequency > 0.0 && isPowerOn()) { - state = new DecimalType(frequency); + case CHANNEL_ZONE1_FREQUENCY: + case CHANNEL_ZONE2_FREQUENCY: + case CHANNEL_ZONE3_FREQUENCY: + case CHANNEL_ZONE4_FREQUENCY: + if (isPowerOn(numZone) && frequencies[numZone] > 0.0) { + state = new DecimalType(frequencies[numZone]); } break; case CHANNEL_LINE1: @@ -2316,6 +2143,7 @@ public class RotelHandler extends BaseThingHandler implements RotelMessageEventL state = new StringType(frontPanelLine2); break; case CHANNEL_BRIGHTNESS: + case CHANNEL_ALL_BRIGHTNESS: if (isPowerOn() && model.hasDimmerControl()) { long dimmerPct = Math.round((double) (brightness - model.getDimmerLevelMin()) / (double) (model.getDimmerLevelMax() - model.getDimmerLevelMin()) * 100.0); @@ -2328,8 +2156,12 @@ public class RotelHandler extends BaseThingHandler implements RotelMessageEventL } break; case CHANNEL_BALANCE: - if (isPowerOn()) { - state = new DecimalType(balance); + case CHANNEL_ZONE1_BALANCE: + case CHANNEL_ZONE2_BALANCE: + case CHANNEL_ZONE3_BALANCE: + case CHANNEL_ZONE4_BALANCE: + if (isPowerOn(numZone)) { + state = new DecimalType(balances[numZone]); } break; case CHANNEL_SPEAKER_A: @@ -2349,76 +2181,433 @@ public class RotelHandler extends BaseThingHandler implements RotelMessageEventL } /** - * Inform about the main zone power state + * Inform about the device / main zone power state * - * @return true if main zone power state is known and known as ON + * @return true if device / main zone power state is known and known as ON */ private boolean isPowerOn() { - Boolean power = this.power; - return power != null && power.booleanValue(); + return isPowerOn(0); } /** - * Get the command to be used for main zone POWER ON + * Inform about the power state * - * @return the command + * @param numZone the zone number (1-4) or 0 for the device or main zone + * + * @return true if power state is known and known as ON */ - private RotelCommand getPowerOnCommand() { - return model.hasOtherThanPrimaryCommands() ? RotelCommand.MAIN_ZONE_POWER_ON : RotelCommand.POWER_ON; + private boolean isPowerOn(int numZone) { + if (numZone < 0 || numZone > MAX_NUMBER_OF_ZONES) { + throw new IllegalArgumentException("numZone must be in range 0-" + MAX_NUMBER_OF_ZONES); + } + Boolean power = powers[numZone]; + return (numZone > 0 && !powerControlPerZone) ? isPowerOn(0) : power != null && power.booleanValue(); } /** - * Get the command to be used for main zone POWER OFF + * Get the command to be used for POWER ON + * + * @param numZone the zone number (2-4) or 0 for the device or main zone * * @return the command */ - private RotelCommand getPowerOffCommand() { - return model.hasOtherThanPrimaryCommands() ? RotelCommand.MAIN_ZONE_POWER_OFF : RotelCommand.POWER_OFF; + private RotelCommand getPowerOnCommand(int numZone) { + switch (numZone) { + case 0: + return model.hasOtherThanPrimaryCommands() ? RotelCommand.MAIN_ZONE_POWER_ON : RotelCommand.POWER_ON; + case 2: + return RotelCommand.ZONE2_POWER_ON; + case 3: + return RotelCommand.ZONE3_POWER_ON; + case 4: + return RotelCommand.ZONE4_POWER_ON; + default: + throw new IllegalArgumentException("No power ON command defined for zone " + numZone); + } } /** - * Get the command to be used for main zone VOLUME UP + * Get the command to be used for POWER OFF + * + * @param numZone the zone number (2-4) or 0 for the device or main zone * * @return the command */ - private RotelCommand getVolumeUpCommand() { - return model.hasOtherThanPrimaryCommands() ? RotelCommand.MAIN_ZONE_VOLUME_UP : RotelCommand.VOLUME_UP; + private RotelCommand getPowerOffCommand(int numZone) { + switch (numZone) { + case 0: + return model.hasOtherThanPrimaryCommands() ? RotelCommand.MAIN_ZONE_POWER_OFF : RotelCommand.POWER_OFF; + case 2: + return RotelCommand.ZONE2_POWER_OFF; + case 3: + return RotelCommand.ZONE3_POWER_OFF; + case 4: + return RotelCommand.ZONE4_POWER_OFF; + default: + throw new IllegalArgumentException("No power OFF command defined for zone " + numZone); + } } /** - * Get the command to be used for main zone VOLUME DOWN + * Get the command to be used for VOLUME UP + * + * @param numZone the zone number (1-4) or 0 for the device or main zone * * @return the command */ - private RotelCommand getVolumeDownCommand() { - return model.hasOtherThanPrimaryCommands() ? RotelCommand.MAIN_ZONE_VOLUME_DOWN : RotelCommand.VOLUME_DOWN; + private RotelCommand getVolumeUpCommand(int numZone) { + switch (numZone) { + case 0: + return model.hasOtherThanPrimaryCommands() ? RotelCommand.MAIN_ZONE_VOLUME_UP : RotelCommand.VOLUME_UP; + case 1: + return RotelCommand.ZONE1_VOLUME_UP; + case 2: + return RotelCommand.ZONE2_VOLUME_UP; + case 3: + return RotelCommand.ZONE3_VOLUME_UP; + case 4: + return RotelCommand.ZONE4_VOLUME_UP; + default: + throw new IllegalArgumentException("No VOLUME UP command defined for zone " + numZone); + } } /** - * Get the command to be used for main zone MUTE ON + * Get the command to be used for VOLUME DOWN + * + * @param numZone the zone number (1-4) or 0 for the device or main zone * * @return the command */ - private RotelCommand getMuteOnCommand() { - return model.hasOtherThanPrimaryCommands() ? RotelCommand.MAIN_ZONE_MUTE_ON : RotelCommand.MUTE_ON; + private RotelCommand getVolumeDownCommand(int numZone) { + switch (numZone) { + case 0: + return model.hasOtherThanPrimaryCommands() ? RotelCommand.MAIN_ZONE_VOLUME_DOWN + : RotelCommand.VOLUME_DOWN; + case 1: + return RotelCommand.ZONE1_VOLUME_DOWN; + case 2: + return RotelCommand.ZONE2_VOLUME_DOWN; + case 3: + return RotelCommand.ZONE3_VOLUME_DOWN; + case 4: + return RotelCommand.ZONE4_VOLUME_DOWN; + default: + throw new IllegalArgumentException("No VOLUME DOWN command defined for zone " + numZone); + } } /** - * Get the command to be used for main zone MUTE OFF + * Get the command to be used for VOLUME SET + * + * @param numZone the zone number (1-4) or 0 for the device * * @return the command */ - private RotelCommand getMuteOffCommand() { - return model.hasOtherThanPrimaryCommands() ? RotelCommand.MAIN_ZONE_MUTE_OFF : RotelCommand.MUTE_OFF; + private RotelCommand getVolumeSetCommand(int numZone) { + switch (numZone) { + case 0: + return RotelCommand.VOLUME_SET; + case 1: + return RotelCommand.ZONE1_VOLUME_SET; + case 2: + return RotelCommand.ZONE2_VOLUME_SET; + case 3: + return RotelCommand.ZONE3_VOLUME_SET; + case 4: + return RotelCommand.ZONE4_VOLUME_SET; + default: + throw new IllegalArgumentException("No VOLUME SET command defined for zone " + numZone); + } } /** - * Get the command to be used for main zone MUTE TOGGLE + * Get the command to be used for MUTE ON + * + * @param numZone the zone number (1-4) or 0 for the device or main zone * * @return the command */ - private RotelCommand getMuteToggleCommand() { - return model.hasOtherThanPrimaryCommands() ? RotelCommand.MAIN_ZONE_MUTE_TOGGLE : RotelCommand.MUTE_TOGGLE; + private RotelCommand getMuteOnCommand(int numZone) { + switch (numZone) { + case 0: + return model.hasOtherThanPrimaryCommands() ? RotelCommand.MAIN_ZONE_MUTE_ON : RotelCommand.MUTE_ON; + case 1: + return RotelCommand.ZONE1_MUTE_ON; + case 2: + return RotelCommand.ZONE2_MUTE_ON; + case 3: + return RotelCommand.ZONE3_MUTE_ON; + case 4: + return RotelCommand.ZONE4_MUTE_ON; + default: + throw new IllegalArgumentException("No MUTE ON command defined for zone " + numZone); + } + } + + /** + * Get the command to be used for MUTE OFF + * + * @param numZone the zone number (1-4) or 0 for the device or main zone + * + * @return the command + */ + private RotelCommand getMuteOffCommand(int numZone) { + switch (numZone) { + case 0: + return model.hasOtherThanPrimaryCommands() ? RotelCommand.MAIN_ZONE_MUTE_OFF : RotelCommand.MUTE_OFF; + case 1: + return RotelCommand.ZONE1_MUTE_OFF; + case 2: + return RotelCommand.ZONE2_MUTE_OFF; + case 3: + return RotelCommand.ZONE3_MUTE_OFF; + case 4: + return RotelCommand.ZONE4_MUTE_OFF; + default: + throw new IllegalArgumentException("No MUTE OFF command defined for zone " + numZone); + } + } + + /** + * Get the command to be used for MUTE TOGGLE + * + * @param numZone the zone number (1-4) or 0 for the device or main zone + * + * @return the command + */ + private RotelCommand getMuteToggleCommand(int numZone) { + switch (numZone) { + case 0: + return model.hasOtherThanPrimaryCommands() ? RotelCommand.MAIN_ZONE_MUTE_TOGGLE + : RotelCommand.MUTE_TOGGLE; + case 1: + return RotelCommand.ZONE1_MUTE_TOGGLE; + case 2: + return RotelCommand.ZONE2_MUTE_TOGGLE; + case 3: + return RotelCommand.ZONE3_MUTE_TOGGLE; + case 4: + return RotelCommand.ZONE4_MUTE_TOGGLE; + default: + throw new IllegalArgumentException("No MUTE TOGGLE command defined for zone " + numZone); + } + } + + /** + * Get the command to be used for BASS UP + * + * @param numZone the zone number (1-4) or 0 for the device + * + * @return the command + */ + private RotelCommand getBassUpCommand(int numZone) { + switch (numZone) { + case 0: + return RotelCommand.BASS_UP; + case 1: + return RotelCommand.ZONE1_BASS_UP; + case 2: + return RotelCommand.ZONE2_BASS_UP; + case 3: + return RotelCommand.ZONE3_BASS_UP; + case 4: + return RotelCommand.ZONE4_BASS_UP; + default: + throw new IllegalArgumentException("No BASS UP command defined for zone " + numZone); + } + } + + /** + * Get the command to be used for BASS DOWN + * + * @param numZone the zone number (1-4) or 0 for the device + * + * @return the command + */ + private RotelCommand getBassDownCommand(int numZone) { + switch (numZone) { + case 0: + return RotelCommand.BASS_DOWN; + case 1: + return RotelCommand.ZONE1_BASS_DOWN; + case 2: + return RotelCommand.ZONE2_BASS_DOWN; + case 3: + return RotelCommand.ZONE3_BASS_DOWN; + case 4: + return RotelCommand.ZONE4_BASS_DOWN; + default: + throw new IllegalArgumentException("No BASS DOWN command defined for zone " + numZone); + } + } + + /** + * Get the command to be used for BASS SET + * + * @param numZone the zone number (1-4) or 0 for the device + * + * @return the command + */ + private RotelCommand getBassSetCommand(int numZone) { + switch (numZone) { + case 0: + return RotelCommand.BASS_SET; + case 1: + return RotelCommand.ZONE1_BASS_SET; + case 2: + return RotelCommand.ZONE2_BASS_SET; + case 3: + return RotelCommand.ZONE3_BASS_SET; + case 4: + return RotelCommand.ZONE4_BASS_SET; + default: + throw new IllegalArgumentException("No BASS SET command defined for zone " + numZone); + } + } + + /** + * Get the command to be used for TREBLE UP + * + * @param numZone the zone number (1-4) or 0 for the device + * + * @return the command + */ + private RotelCommand getTrebleUpCommand(int numZone) { + switch (numZone) { + case 0: + return RotelCommand.TREBLE_UP; + case 1: + return RotelCommand.ZONE1_TREBLE_UP; + case 2: + return RotelCommand.ZONE2_TREBLE_UP; + case 3: + return RotelCommand.ZONE3_TREBLE_UP; + case 4: + return RotelCommand.ZONE4_TREBLE_UP; + default: + throw new IllegalArgumentException("No TREBLE UP command defined for zone " + numZone); + } + } + + /** + * Get the command to be used for TREBLE DOWN + * + * @param numZone the zone number (1-4) or 0 for the device + * + * @return the command + */ + private RotelCommand getTrebleDownCommand(int numZone) { + switch (numZone) { + case 0: + return RotelCommand.TREBLE_DOWN; + case 1: + return RotelCommand.ZONE1_TREBLE_DOWN; + case 2: + return RotelCommand.ZONE2_TREBLE_DOWN; + case 3: + return RotelCommand.ZONE3_TREBLE_DOWN; + case 4: + return RotelCommand.ZONE4_TREBLE_DOWN; + default: + throw new IllegalArgumentException("No TREBLE DOWN command defined for zone " + numZone); + } + } + + /** + * Get the command to be used for TREBLE SET + * + * @param numZone the zone number (1-4) or 0 for the device + * + * @return the command + */ + private RotelCommand getTrebleSetCommand(int numZone) { + switch (numZone) { + case 0: + return RotelCommand.TREBLE_SET; + case 1: + return RotelCommand.ZONE1_TREBLE_SET; + case 2: + return RotelCommand.ZONE2_TREBLE_SET; + case 3: + return RotelCommand.ZONE3_TREBLE_SET; + case 4: + return RotelCommand.ZONE4_TREBLE_SET; + default: + throw new IllegalArgumentException("No TREBLE SET command defined for zone " + numZone); + } + } + + /** + * Get the command to be used for BALANCE LEFT + * + * @param numZone the zone number (1-4) or 0 for the device + * + * @return the command + */ + private RotelCommand getBalanceLeftCommand(int numZone) { + switch (numZone) { + case 0: + return RotelCommand.BALANCE_LEFT; + case 1: + return RotelCommand.ZONE1_BALANCE_LEFT; + case 2: + return RotelCommand.ZONE2_BALANCE_LEFT; + case 3: + return RotelCommand.ZONE3_BALANCE_LEFT; + case 4: + return RotelCommand.ZONE4_BALANCE_LEFT; + default: + throw new IllegalArgumentException("No BALANCE LEFT command defined for zone " + numZone); + } + } + + /** + * Get the command to be used for BALANCE RIGHT + * + * @param numZone the zone number (1-4) or 0 for the device + * + * @return the command + */ + private RotelCommand getBalanceRightCommand(int numZone) { + switch (numZone) { + case 0: + return RotelCommand.BALANCE_RIGHT; + case 1: + return RotelCommand.ZONE1_BALANCE_RIGHT; + case 2: + return RotelCommand.ZONE2_BALANCE_RIGHT; + case 3: + return RotelCommand.ZONE3_BALANCE_RIGHT; + case 4: + return RotelCommand.ZONE4_BALANCE_RIGHT; + default: + throw new IllegalArgumentException("No BALANCE RIGHT command defined for zone " + numZone); + } + } + + /** + * Get the command to be used for BALANCE SET + * + * @param numZone the zone number (1-4) or 0 for the device + * + * @return the command + */ + private RotelCommand getBalanceSetCommand(int numZone) { + switch (numZone) { + case 0: + return RotelCommand.BALANCE_SET; + case 1: + return RotelCommand.ZONE1_BALANCE_SET; + case 2: + return RotelCommand.ZONE2_BALANCE_SET; + case 3: + return RotelCommand.ZONE3_BALANCE_SET; + case 4: + return RotelCommand.ZONE4_BALANCE_SET; + default: + throw new IllegalArgumentException("No BALANCE SET command defined for zone " + numZone); + } } private void sendCommand(RotelCommand cmd) throws RotelException { 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 index c90f5a8cd..ab406c8cd 100644 --- 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 @@ -12,6 +12,8 @@ */ package org.openhab.binding.rotel.internal.protocol.ascii; +import static org.openhab.binding.rotel.internal.RotelBindingConstants.*; + import java.nio.charset.StandardCharsets; import org.eclipse.jdt.annotation.NonNullByDefault; @@ -58,10 +60,22 @@ public class RotelAsciiV2ProtocolHandler extends RotelAbstractAsciiProtocolHandl if (value != null) { switch (cmd) { case VOLUME_SET: + case ZONE1_VOLUME_SET: + case ZONE2_VOLUME_SET: + case ZONE3_VOLUME_SET: + case ZONE4_VOLUME_SET: messageStr += String.format("%02d", value); break; case BASS_SET: + case ZONE1_BASS_SET: + case ZONE2_BASS_SET: + case ZONE3_BASS_SET: + case ZONE4_BASS_SET: case TREBLE_SET: + case ZONE1_TREBLE_SET: + case ZONE2_TREBLE_SET: + case ZONE3_TREBLE_SET: + case ZONE4_TREBLE_SET: if (value == 0) { messageStr += "000"; } else if (value > 0) { @@ -71,6 +85,10 @@ public class RotelAsciiV2ProtocolHandler extends RotelAbstractAsciiProtocolHandl } break; case BALANCE_SET: + case ZONE1_BALANCE_SET: + case ZONE2_BALANCE_SET: + case ZONE3_BALANCE_SET: + case ZONE4_BALANCE_SET: if (value == 0) { messageStr += "000"; } else if (value > 0) { @@ -97,4 +115,36 @@ public class RotelAsciiV2ProtocolHandler extends RotelAbstractAsciiProtocolHandl logger.debug("Command \"{}\" => {}", cmd.getName(), messageStr); return message; } + + @Override + protected void dispatchKeyValue(String key, String value) { + // For distribution amplifiers, we need to split certain values to get the value for each zone + if (model == RotelModel.C8 && value.contains(",")) { + switch (key) { + case KEY_INPUT: + case KEY_VOLUME: + case KEY_MUTE: + case KEY_BASS: + case KEY_TREBLE: + case KEY_BALANCE: + case KEY_FREQ: + String[] splitValues = value.split(","); + int nb = splitValues.length; + if (nb > MAX_NUMBER_OF_ZONES) { + nb = MAX_NUMBER_OF_ZONES; + } + for (int i = 1; i <= nb; i++) { + String val = KEY_INPUT.equals(key) ? String.format("z%d:input_%s", i, splitValues[i - 1]) + : splitValues[i - 1]; + dispatchKeyValue(String.format("%s_zone%d", key, i), val); + } + break; + default: + super.dispatchKeyValue(key, value); + break; + } + } else { + super.dispatchKeyValue(key, value); + } + } } 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 index d37203347..0214b96e6 100644 --- 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 @@ -675,7 +675,7 @@ public class RotelHexProtocolHandler extends RotelAbstractProtocolHandler { } else if (!MSG_VALUE_OFF.equalsIgnoreCase(value)) { RotelSource source = parseSource(value, true); if (source != null) { - RotelCommand cmd = source.getZone2Command(); + RotelCommand cmd = source.getZoneCommand(2); if (cmd != null) { value = cmd.getAsciiCommandV2(); if (value != null) { @@ -709,7 +709,7 @@ public class RotelHexProtocolHandler extends RotelAbstractProtocolHandler { } else if (!MSG_VALUE_OFF.equalsIgnoreCase(value)) { RotelSource source = parseSource(value, true); if (source != null) { - RotelCommand cmd = source.getZone3Command(); + RotelCommand cmd = source.getZoneCommand(3); if (cmd != null) { value = cmd.getAsciiCommandV2(); if (value != null) { @@ -743,7 +743,7 @@ public class RotelHexProtocolHandler extends RotelAbstractProtocolHandler { } else if (!MSG_VALUE_OFF.equalsIgnoreCase(value)) { RotelSource source = parseSource(value, true); if (source != null) { - RotelCommand cmd = source.getZone4Command(); + RotelCommand cmd = source.getZoneCommand(4); if (cmd != null) { value = cmd.getAsciiCommandV2(); if (value != null) { diff --git a/bundles/org.openhab.binding.rotel/src/main/resources/OH-INF/i18n/rotel.properties b/bundles/org.openhab.binding.rotel/src/main/resources/OH-INF/i18n/rotel.properties index 6308b2ce9..800555516 100644 --- a/bundles/org.openhab.binding.rotel/src/main/resources/OH-INF/i18n/rotel.properties +++ b/bundles/org.openhab.binding.rotel/src/main/resources/OH-INF/i18n/rotel.properties @@ -170,8 +170,14 @@ config.serialPort.description = Serial port to use for connecting to the Rotel d # channel group types +channel-group.allZones.label = All Zones +channel-group.allZones.description = The controls applied to all zones channel-group.mainZone.label = Main Zone channel-group.mainZone.description = The controls of the main zone +channel-group.zone.label = Zone +channel-group.zone.description = The controls of the zone +channel-group.zone1.label = Zone 1 +channel-group.zone1.description = The controls of the zone 1 channel-group.zone2.label = Zone 2 channel-group.zone2.description = The controls of the zone 2 channel-group.zone3.label = Zone 3 @@ -261,3 +267,7 @@ source.DAB = DAB source.PLAYFI = PlayFi source.IRADIO = iRadio source.NETWORK = Network +source.INPUTA = Input A +source.INPUTB = Input B +source.INPUTC = Input C +source.INPUTD = Input D diff --git a/bundles/org.openhab.binding.rotel/src/main/resources/OH-INF/thing/c8.xml b/bundles/org.openhab.binding.rotel/src/main/resources/OH-INF/thing/c8.xml new file mode 100644 index 000000000..dc2770043 --- /dev/null +++ b/bundles/org.openhab.binding.rotel/src/main/resources/OH-INF/thing/c8.xml @@ -0,0 +1,39 @@ + + + + + + + Connection to the Rotel C8 or C8+ distribution amplifier + + + + + + @text/channel-group.zone1.description + + + + @text/channel-group.zone2.description + + + + @text/channel-group.zone3.description + + + + @text/channel-group.zone4.description + + + + + ASCII_V2 + + + + + + diff --git a/bundles/org.openhab.binding.rotel/src/main/resources/OH-INF/thing/channels.xml b/bundles/org.openhab.binding.rotel/src/main/resources/OH-INF/thing/channels.xml index 261e3d9f6..8cdfe8a9e 100644 --- a/bundles/org.openhab.binding.rotel/src/main/resources/OH-INF/thing/channels.xml +++ b/bundles/org.openhab.binding.rotel/src/main/resources/OH-INF/thing/channels.xml @@ -128,6 +128,29 @@ + + + @text/channel-group.allZones.description + + + + + + + + + @text/channel-group.zone.description + + + + + + + + + + + String