diff --git a/bundles/org.openhab.binding.rotel/README.md b/bundles/org.openhab.binding.rotel/README.md index 4fa8e652932..169b0a99fd3 100644 --- a/bundles/org.openhab.binding.rotel/README.md +++ b/bundles/org.openhab.binding.rotel/README.md @@ -22,7 +22,7 @@ You can connect it for example to a Raspberry Pi and use [ser2net Linux tool](ht Recent devices provide a network interface (RJ45 connector). So you can use the device IP to connect to the device, keeping 9590 as default port. -The binding has been tested with a RSP-1066 and a RSP-1570. +The binding has been tested with an RSP-1066, an RSP-1570 and an RX-1052. ## Supported Things @@ -72,6 +72,7 @@ This binding supports the following thing types: | rt09 | Connection to the Rotel RT-09 tuner | | rt11 | Connection to the Rotel RT-11 tuner | | rt1570 | Connection to the Rotel RT-1570 tuner | +| rx1052 | Connection to the Rotel RX-1052 stereo receiver | | s5 | Connection to the Rotel Michi S5 stereo amplifier | | t11 | Connection to the Rotel T11 tuner | | t14 | Connection to the Rotel T14 tuner | @@ -95,6 +96,7 @@ The thing requires the following configuration parameters: | Parameter Label | Parameter ID | Description | Accepted values | |-------------------------|------------------|-------------------------------------------------------|-----------------| | Serial Port | serialPort | Serial port to use for connecting to the Rotel device | | +| Baud Rate | baudRate | Baud rate to use for connecting to the Rotel device | 19200, 38400 | | Address | host | Host name or IP address of the Rotel device (IP connection) or the machine connected to the Rotel device (serial over IP) | | | Port | port | Communication port (IP or serial over IP). For IP connection to the Rotel device, keep the default port (9590) | | | Protocol Version | Protocol | Choose one of the two protocol versions (depends on your device firmware). Default is ASCII_V2 | ASCII_V1 or ASCII_V2 | @@ -135,7 +137,7 @@ Some have additional parameters listed in the next table: | rsx1550 | inputLabelCd, inputLabelTuner, inputLabelTape, inputLabelVideo1, inputLabelVideo2, inputLabelVideo3, inputLabelVideo4, inputLabelVideo5, inputLabelMulti | | rsx1560 | inputLabelCd, inputLabelTuner, inputLabelTape, inputLabelVideo1, inputLabelVideo2, inputLabelVideo3, inputLabelVideo4, inputLabelVideo5, inputLabelMulti | | rsx1562 | inputLabelCd, inputLabelTuner, inputLabelUsb, inputLabelVideo1, inputLabelVideo2, inputLabelVideo3, inputLabelVideo4, inputLabelVideo5, inputLabelVideo6, inputLabelMulti | - +| rx1052 | baudRate, inputLabelVideo1, inputLabelVideo2, inputLabelVideo3, inputLabelVideo4 | Some notes: - On Linux, you may get an error stating the serial port cannot be opened when the Rotel binding tries to load. You can get around this by adding the `openhab` user to the `dialout` group like this: `usermod -a -G dialout openhab`. @@ -154,7 +156,7 @@ The following channels are available: |--------------|---------------------|-----------|---------------------------------------|------------------------------------| | power, mainZone#power, allZones#power, zone2#power, zone3#power, zone4#power | Power | Switch | Power ON/OFF the equipment or the zone | ON, OFF | | source, mainZone#source, zone1#source, zone2#source, zone3#source, zone4#source | Source Input | String | Select the source input | CD, TUNER, TAPE, VIDEO1, VIDEO2, VIDEO3, VIDEO4, VIDEO5, VIDEO6, VIDEO7, VIDEO8, USB, PCUSB, MULTI, PHONO, BLUETOOTH, AUX, AUX1, AUX2, AUX1_COAX, AUX1_OPTICAL, COAX1, COAX2, COAX3, OPTICAL1, OPTICAL2, OPTICAL3, XLR, RCD, FM, DAB, PLAYFI, IRADIO, NETWORK, INPUTA, INPUTB, INPUTC, INPUTD | -| mainZone#recordSource | Record Source | String | Select the source to be recorded | CD, TUNER, TAPE, VIDEO1, VIDEO2, VIDEO3, VIDEO4, VIDEO5, VIDEO6, USB, MAIN | +| mainZone#recordSource | Record Source | String | Select the source to be recorded | CD, TUNER, TAPE, VIDEO1, VIDEO2, VIDEO3, VIDEO4, VIDEO5, VIDEO6, USB, PHONO, MAIN | | dsp, mainZone#dsp | DSP Mode | String | Select the DSP mode | NONE, STEREO3, STEREO5, STEREO7, STEREO9, STEREO11, MUSIC1, MUSIC2, MUSIC3, MUSIC4, PROLOGIC, PLIICINEMA, PLIIMUSIC, PLIIGAME, PLIIXCINEMA, PLIIXMUSIC, PLIIXGAME, PLIIZ, NEO6MUSIC, NEO6CINEMA, ATMOS, NEURALX, BYPASS | | mainZone#volumeUpDown, zone2#volumeUpDown | Volume | Number | Increase or decrease the volume | INCREASE, DECREASE, value | | volume, mainZone#volume, zone1#volume, zone2#volume, zone3#volume, zone4#volume | Volume | Dimmer | Adjust the volume | value between 0 and 100 | @@ -221,6 +223,7 @@ Here are the list of channels available for each thing type: | rt09 | power, source, playControl, brightness | | rt11 | power, source, radioPreset, brightness | | rt1570 | power, source, radioPreset, brightness | +| rx1052 | mainZone#power, mainZone#source, mainZone#recordSource, mainZone#volume, mainZone#mute, mainZone#bass, mainZone#treble, mainZone#speakera, mainZone#speakerb, mainZone#line1, mainZone#otherCommand, zone2#power, zone2#source, zone2#volume, zone2#mute, zone3#power, zone3#source, zone3#volume, zone3#mute, zone4#power, zone4#source, zone4#volume, zone4#mute | | s5 | power, brightness | | t11 | power, source, radioPreset, brightness | | t14 | power, source, radioPreset, brightness | @@ -270,6 +273,7 @@ Here are the available commands for the otherCommand channel depending on the th | rsx1550 | DSP_TOGGLE, PROLOGIC_TOGGLE, DOLBY_TOGGLE, PLII_PANORAMA_TOGGLE, PLII_DIMENSION_UP, PLII_DIMENSION_DOWN, PLII_CENTER_WIDTH_UP, PLII_CENTER_WIDTH_DOWN, DDEX_TOGGLE, NEO6_TOGGLE, NEXT_MODE, TUNE_UP, TUNE_DOWN, PRESET_UP, PRESET_DOWN, FREQUENCY_UP, FREQUENCY_DOWN, MEMORY, BAND_TOGGLE, AM, FM, TUNE_PRESET_TOGGLE, TUNING_MODE_SELECT, PRESET_MODE_SELECT, FREQUENCY_DIRECT, PRESET_SCAN, TUNER_DISPLAY, RDS_PTY, RDS_TP, RDS_TA, FM_MONO_TOGGLE, ZONE2_TUNE_UP, ZONE2_TUNE_DOWN, ZONE2_PRESET_UP, ZONE2_PRESET_DOWN, ZONE2_FREQUENCY_UP, ZONE2_FREQUENCY_DOWN, ZONE2_BAND_TOGGLE, ZONE2_AM, ZONE2_FM, ZONE2_TUNE_PRESET_TOGGLE, ZONE2_TUNING_MODE_SELECT, ZONE2_PRESET_MODE_SELECT, ZONE2_PRESET_SCAN, ZONE2_FM_MONO_TOGGLE, ZONE3_TUNE_UP, ZONE3_TUNE_DOWN, ZONE3_PRESET_UP, ZONE3_PRESET_DOWN, ZONE3_FREQUENCY_UP, ZONE3_FREQUENCY_DOWN, ZONE3_BAND_TOGGLE, ZONE3_AM, ZONE3_FM, ZONE3_TUNE_PRESET_TOGGLE, ZONE3_TUNING_MODE_SELECT, ZONE3_PRESET_MODE_SELECT, ZONE3_PRESET_SCAN, ZONE3_FM_MONO_TOGGLE, ZONE4_TUNE_UP, ZONE4_TUNE_DOWN, ZONE4_PRESET_UP, ZONE4_PRESET_DOWN, ZONE4_FREQUENCY_UP, ZONE4_FREQUENCY_DOWN, ZONE4_BAND_TOGGLE, ZONE4_AM, ZONE4_FM, ZONE4_TUNE_PRESET_TOGGLE, ZONE4_TUNING_MODE_SELECT, ZONE4_PRESET_MODE_SELECT, ZONE4_PRESET_SCAN, ZONE4_FM_MONO_TOGGLE, KEY1, KEY2, KEY3, KEY4, KEY5, KEY6, KEY7, KEY8, KEY9, KEY0, ZONE2_KEY1, ZONE2_KEY2, ZONE2_KEY3, ZONE2_KEY4, ZONE2_KEY5, ZONE2_KEY6, ZONE2_KEY7, ZONE2_KEY8, ZONE2_KEY9, ZONE2_KEY0, ZONE3_KEY1, ZONE3_KEY2, ZONE3_KEY3, ZONE3_KEY4, ZONE3_KEY5, ZONE3_KEY6, ZONE3_KEY7, ZONE3_KEY8, ZONE3_KEY9, ZONE3_KEY0, ZONE4_KEY1, ZONE4_KEY2, ZONE4_KEY3, ZONE4_KEY4, ZONE4_KEY5, ZONE4_KEY6, ZONE4_KEY7, ZONE4_KEY8, ZONE4_KEY9, ZONE4_KEY0, MENU, UP, DOWN, LEFT, RIGHT, ENTER, RECORD_FONCTION_SELECT, TONE_CONTROL_SELECT, DYNAMIC_RANGE, DIGITAL_INPUT_SELECT, ZONE_TOGGLE, CENTER_TRIM, SUB_TRIM, SURROUND_TRIM, CINEMA_EQ_TOGGLE, POWER_OFF_ALL_ZONES, PARTY_MODE_TOGGLE, ZONE2_PARTY_MODE_TOGGLE, ZONE3_PARTY_MODE_TOGGLE, ZONE4_PARTY_MODE_TOGGLE, OUTPUT_RESOLUTION, HDMI_AMP_MODE, HDMI_TV_MODE, RESET_FACTORY | | rsx1560 | DSP_TOGGLE, PROLOGIC_TOGGLE, DOLBY_TOGGLE, PLII_PANORAMA_TOGGLE, PLII_DIMENSION_UP, PLII_DIMENSION_DOWN, PLII_CENTER_WIDTH_UP, PLII_CENTER_WIDTH_DOWN, DDEX_TOGGLE, NEO6_TOGGLE, NEXT_MODE, TUNE_UP, TUNE_DOWN, PRESET_UP, PRESET_DOWN, FREQUENCY_UP, FREQUENCY_DOWN, MEMORY, BAND_TOGGLE, AM, FM, TUNE_PRESET_TOGGLE, TUNING_MODE_SELECT, PRESET_MODE_SELECT, FREQUENCY_DIRECT, PRESET_SCAN, TUNER_DISPLAY, RDS_PTY, RDS_TP, RDS_TA, FM_MONO_TOGGLE, ZONE2_TUNE_UP, ZONE2_TUNE_DOWN, ZONE2_PRESET_UP, ZONE2_PRESET_DOWN, ZONE2_FREQUENCY_UP, ZONE2_FREQUENCY_DOWN, ZONE2_BAND_TOGGLE, ZONE2_AM, ZONE2_FM, ZONE2_TUNE_PRESET_TOGGLE, ZONE2_TUNING_MODE_SELECT, ZONE2_PRESET_MODE_SELECT, ZONE2_PRESET_SCAN, ZONE2_FM_MONO_TOGGLE, ZONE3_TUNE_UP, ZONE3_TUNE_DOWN, ZONE3_PRESET_UP, ZONE3_PRESET_DOWN, ZONE3_FREQUENCY_UP, ZONE3_FREQUENCY_DOWN, ZONE3_BAND_TOGGLE, ZONE3_AM, ZONE3_FM, ZONE3_TUNE_PRESET_TOGGLE, ZONE3_TUNING_MODE_SELECT, ZONE3_PRESET_MODE_SELECT, ZONE3_PRESET_SCAN, ZONE3_FM_MONO_TOGGLE, ZONE4_TUNE_UP, ZONE4_TUNE_DOWN, ZONE4_PRESET_UP, ZONE4_PRESET_DOWN, ZONE4_FREQUENCY_UP, ZONE4_FREQUENCY_DOWN, ZONE4_BAND_TOGGLE, ZONE4_AM, ZONE4_FM, ZONE4_TUNE_PRESET_TOGGLE, ZONE4_TUNING_MODE_SELECT, ZONE4_PRESET_MODE_SELECT, ZONE4_PRESET_SCAN, ZONE4_FM_MONO_TOGGLE, KEY1, KEY2, KEY3, KEY4, KEY5, KEY6, KEY7, KEY8, KEY9, KEY0, ZONE2_KEY1, ZONE2_KEY2, ZONE2_KEY3, ZONE2_KEY4, ZONE2_KEY5, ZONE2_KEY6, ZONE2_KEY7, ZONE2_KEY8, ZONE2_KEY9, ZONE2_KEY0, ZONE3_KEY1, ZONE3_KEY2, ZONE3_KEY3, ZONE3_KEY4, ZONE3_KEY5, ZONE3_KEY6, ZONE3_KEY7, ZONE3_KEY8, ZONE3_KEY9, ZONE3_KEY0, ZONE4_KEY1, ZONE4_KEY2, ZONE4_KEY3, ZONE4_KEY4, ZONE4_KEY5, ZONE4_KEY6, ZONE4_KEY7, ZONE4_KEY8, ZONE4_KEY9, ZONE4_KEY0, MENU, UP, DOWN, LEFT, RIGHT, ENTER, RECORD_FONCTION_SELECT, TONE_CONTROL_SELECT, DYNAMIC_RANGE, DIGITAL_INPUT_SELECT, ZONE_TOGGLE, CENTER_TRIM, SUB_TRIM, SURROUND_TRIM, CINEMA_EQ_TOGGLE, POWER_OFF_ALL_ZONES, PARTY_MODE_TOGGLE, ZONE2_PARTY_MODE_TOGGLE, ZONE3_PARTY_MODE_TOGGLE, ZONE4_PARTY_MODE_TOGGLE, OUTPUT_RESOLUTION, HDMI_AMP_MODE, HDMI_TV_MODE, RESET_FACTORY | | rsx1562 | STEREO_BYPASS_TOGGLE, DSP_TOGGLE, PROLOGIC_TOGGLE, DOLBY_TOGGLE, PLII_PANORAMA_TOGGLE, PLII_DIMENSION_UP, PLII_DIMENSION_DOWN, PLII_CENTER_WIDTH_UP, PLII_CENTER_WIDTH_DOWN, DDEX_TOGGLE, NEO6_TOGGLE, NEXT_MODE, TUNE_UP, TUNE_DOWN, PRESET_UP, PRESET_DOWN, FREQUENCY_UP, FREQUENCY_DOWN, MEMORY, BAND_TOGGLE, AM, FM, TUNE_PRESET_TOGGLE, TUNING_MODE_SELECT, PRESET_MODE_SELECT, FREQUENCY_DIRECT, PRESET_SCAN, TUNER_DISPLAY, RDS_PTY, RDS_TP, RDS_TA, FM_MONO_TOGGLE, ZONE2_TUNE_UP, ZONE2_TUNE_DOWN, ZONE2_PRESET_UP, ZONE2_PRESET_DOWN, ZONE2_FREQUENCY_UP, ZONE2_FREQUENCY_DOWN, ZONE2_BAND_TOGGLE, ZONE2_AM, ZONE2_FM, ZONE2_TUNE_PRESET_TOGGLE, ZONE2_TUNING_MODE_SELECT, ZONE2_PRESET_MODE_SELECT, ZONE2_PRESET_SCAN, ZONE2_FM_MONO_TOGGLE, ZONE3_TUNE_UP, ZONE3_TUNE_DOWN, ZONE3_PRESET_UP, ZONE3_PRESET_DOWN, ZONE3_FREQUENCY_UP, ZONE3_FREQUENCY_DOWN, ZONE3_BAND_TOGGLE, ZONE3_AM, ZONE3_FM, ZONE3_TUNE_PRESET_TOGGLE, ZONE3_TUNING_MODE_SELECT, ZONE3_PRESET_MODE_SELECT, ZONE3_PRESET_SCAN, ZONE3_FM_MONO_TOGGLE, ZONE4_TUNE_UP, ZONE4_TUNE_DOWN, ZONE4_PRESET_UP, ZONE4_PRESET_DOWN, ZONE4_FREQUENCY_UP, ZONE4_FREQUENCY_DOWN, ZONE4_BAND_TOGGLE, ZONE4_AM, ZONE4_FM, ZONE4_TUNE_PRESET_TOGGLE, ZONE4_TUNING_MODE_SELECT, ZONE4_PRESET_MODE_SELECT, ZONE4_PRESET_SCAN, ZONE4_FM_MONO_TOGGLE, KEY1, KEY2, KEY3, KEY4, KEY5, KEY6, KEY7, KEY8, KEY9, KEY0, ZONE2_KEY1, ZONE2_KEY2, ZONE2_KEY3, ZONE2_KEY4, ZONE2_KEY5, ZONE2_KEY6, ZONE2_KEY7, ZONE2_KEY8, ZONE2_KEY9, ZONE2_KEY0, ZONE3_KEY1, ZONE3_KEY2, ZONE3_KEY3, ZONE3_KEY4, ZONE3_KEY5, ZONE3_KEY6, ZONE3_KEY7, ZONE3_KEY8, ZONE3_KEY9, ZONE3_KEY0, ZONE4_KEY1, ZONE4_KEY2, ZONE4_KEY3, ZONE4_KEY4, ZONE4_KEY5, ZONE4_KEY6, ZONE4_KEY7, ZONE4_KEY8, ZONE4_KEY9, ZONE4_KEY0, MENU, EXIT, UP_PRESSED, UP_RELEASED, DOWN_PRESSED, DOWN_RELEASED, LEFT_PRESSED, LEFT_RELEASED, RIGHT_PRESSED, RIGHT_RELEASED, ENTER, RECORD_FONCTION_SELECT, TONE_CONTROL_SELECT, DYNAMIC_RANGE, DIGITAL_INPUT_SELECT, ZONE_TOGGLE, CENTER_TRIM, SUB_TRIM, SURROUND_TRIM, CINEMA_EQ_TOGGLE, POWER_OFF_ALL_ZONES, PARTY_MODE_TOGGLE, ZONE2_PARTY_MODE_TOGGLE, ZONE3_PARTY_MODE_TOGGLE, ZONE4_PARTY_MODE_TOGGLE, OUTPUT_RESOLUTION, HDMI_AMP_MODE, HDMI_TV_MODE, ROOM_EQ_TOGGLE, SPEAKER_SETTING_TOGGLE, RESET_FACTORY | +| rx1052 | TUNE_UP, TUNE_DOWN, PRESET_UP, PRESET_DOWN, FREQUENCY_UP, FREQUENCY_DOWN, MEMORY, BAND_TOGGLE, AM, FM, TUNE_PRESET_TOGGLE, TUNING_MODE_SELECT, PRESET_MODE_SELECT, FREQUENCY_DIRECT, PRESET_SCAN, FM_MONO_TOGGLE, ZONE2_TUNE_UP, ZONE2_TUNE_DOWN, ZONE2_PRESET_UP, ZONE2_PRESET_DOWN, ZONE2_FREQUENCY_UP, ZONE2_FREQUENCY_DOWN, ZONE2_BAND_TOGGLE, ZONE2_AM, ZONE2_FM, ZONE2_TUNE_PRESET_TOGGLE, ZONE2_TUNING_MODE_SELECT, ZONE2_PRESET_MODE_SELECT, ZONE2_PRESET_SCAN, ZONE2_FM_MONO_TOGGLE, ZONE3_TUNE_UP, ZONE3_TUNE_DOWN, ZONE3_PRESET_UP, ZONE3_PRESET_DOWN, ZONE3_FREQUENCY_UP, ZONE3_FREQUENCY_DOWN, ZONE3_BAND_TOGGLE, ZONE3_AM, ZONE3_FM, ZONE3_TUNE_PRESET_TOGGLE, ZONE3_TUNING_MODE_SELECT, ZONE3_PRESET_MODE_SELECT, ZONE3_PRESET_SCAN, ZONE3_FM_MONO_TOGGLE, ZONE4_TUNE_UP, ZONE4_TUNE_DOWN, ZONE4_PRESET_UP, ZONE4_PRESET_DOWN, ZONE4_FREQUENCY_UP, ZONE4_FREQUENCY_DOWN, ZONE4_BAND_TOGGLE, ZONE4_AM, ZONE4_FM, ZONE4_TUNE_PRESET_TOGGLE, ZONE4_TUNING_MODE_SELECT, ZONE4_PRESET_MODE_SELECT, ZONE4_PRESET_SCAN, ZONE4_FM_MONO_TOGGLE, KEY1, KEY2, KEY3, KEY4, KEY5, KEY6, KEY7, KEY8, KEY9, KEY0, ZONE2_KEY1, ZONE2_KEY2, ZONE2_KEY3, ZONE2_KEY4, ZONE2_KEY5, ZONE2_KEY6, ZONE2_KEY7, ZONE2_KEY8, ZONE2_KEY9, ZONE2_KEY0, ZONE3_KEY1, ZONE3_KEY2, ZONE3_KEY3, ZONE3_KEY4, ZONE3_KEY5, ZONE3_KEY6, ZONE3_KEY7, ZONE3_KEY8, ZONE3_KEY9, ZONE3_KEY0, ZONE4_KEY1, ZONE4_KEY2, ZONE4_KEY3, ZONE4_KEY4, ZONE4_KEY5, ZONE4_KEY6, ZONE4_KEY7, ZONE4_KEY8, ZONE4_KEY9, ZONE4_KEY0, POWER_OFF_ALL_ZONES, PARTY_MODE_TOGGLE, ZONE2_PARTY_MODE_TOGGLE, ZONE3_PARTY_MODE_TOGGLE, ZONE4_PARTY_MODE_TOGGLE, RECORD_FONCTION_SELECT, TONE_CONTROL_SELECT, ZONE_TOGGLE | | x3 | PLAY, PAUSE, STOP, TRACK_FWD, TRACK_BACK | | x5 | PLAY, PAUSE, STOP, TRACK_FWD, TRACK_BACK | 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 c19bda03dd6..2c5391a1ab6 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 @@ -42,6 +42,7 @@ public class RotelBindingConstants { public static final String THING_TYPE_ID_RSX1550 = "rsx1550"; public static final String THING_TYPE_ID_RSX1560 = "rsx1560"; public static final String THING_TYPE_ID_RSX1562 = "rsx1562"; + public static final String THING_TYPE_ID_RX1052 = "rx1052"; public static final String THING_TYPE_ID_A11 = "a11"; public static final String THING_TYPE_ID_A12 = "a12"; public static final String THING_TYPE_ID_A14 = "a14"; @@ -91,6 +92,7 @@ public class RotelBindingConstants { public static final ThingTypeUID THING_TYPE_RSX1550 = new ThingTypeUID(BINDING_ID, THING_TYPE_ID_RSX1550); public static final ThingTypeUID THING_TYPE_RSX1560 = new ThingTypeUID(BINDING_ID, THING_TYPE_ID_RSX1560); public static final ThingTypeUID THING_TYPE_RSX1562 = new ThingTypeUID(BINDING_ID, THING_TYPE_ID_RSX1562); + public static final ThingTypeUID THING_TYPE_RX1052 = new ThingTypeUID(BINDING_ID, THING_TYPE_ID_RX1052); public static final ThingTypeUID THING_TYPE_A11 = new ThingTypeUID(BINDING_ID, THING_TYPE_ID_A11); public static final ThingTypeUID THING_TYPE_A12 = new ThingTypeUID(BINDING_ID, THING_TYPE_ID_A12); public static final ThingTypeUID THING_TYPE_A14 = new ThingTypeUID(BINDING_ID, THING_TYPE_ID_A14); @@ -163,6 +165,8 @@ public class RotelBindingConstants { 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_MAIN_SPEAKER_A = CHANNEL_GROUP_MAIN_ZONE + "#" + CHANNEL_SPEAKER_A; + public static final String CHANNEL_MAIN_SPEAKER_B = CHANNEL_GROUP_MAIN_ZONE + "#" + CHANNEL_SPEAKER_B; public static final String CHANNEL_MAIN_OTHER_COMMAND = CHANNEL_GROUP_MAIN_ZONE + "#" + CHANNEL_OTHER_COMMAND; public static final String CHANNEL_GROUP_ZONE1 = "zone1"; @@ -332,6 +336,7 @@ public class RotelBindingConstants { public static final String KEY_POWER_ZONE2 = "power_zone2"; public static final String KEY_POWER_ZONE3 = "power_zone3"; public static final String KEY_POWER_ZONE4 = "power_zone4"; + public static final String KEY_POWER_ZONES = "power_zones"; 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"; 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 ead0f5d5cd6..a70a64824be 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 @@ -14,10 +14,7 @@ package org.openhab.binding.rotel.internal; import static org.openhab.binding.rotel.internal.RotelBindingConstants.*; -import java.util.Collections; import java.util.Set; -import java.util.stream.Collectors; -import java.util.stream.Stream; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; @@ -42,17 +39,16 @@ import org.osgi.service.component.annotations.Reference; @Component(configurationPid = "binding.rotel", service = ThingHandlerFactory.class) public class RotelHandlerFactory extends BaseThingHandlerFactory { - private static final Set SUPPORTED_THING_TYPES_UIDS = Collections.unmodifiableSet(Stream - .of(THING_TYPE_RSP1066, THING_TYPE_RSP1068, THING_TYPE_RSP1069, THING_TYPE_RSP1098, THING_TYPE_RSP1570, - THING_TYPE_RSP1572, THING_TYPE_RSX1055, THING_TYPE_RSX1056, THING_TYPE_RSX1057, THING_TYPE_RSX1058, - THING_TYPE_RSX1065, THING_TYPE_RSX1067, THING_TYPE_RSX1550, THING_TYPE_RSX1560, THING_TYPE_RSX1562, - THING_TYPE_A11, THING_TYPE_A12, THING_TYPE_A14, THING_TYPE_CD11, THING_TYPE_CD14, THING_TYPE_RA11, - 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_C8, - THING_TYPE_M8, THING_TYPE_P5, THING_TYPE_S5, THING_TYPE_X3, THING_TYPE_X5) - .collect(Collectors.toSet())); + private static final Set SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_RSP1066, THING_TYPE_RSP1068, + THING_TYPE_RSP1069, THING_TYPE_RSP1098, THING_TYPE_RSP1570, THING_TYPE_RSP1572, THING_TYPE_RSX1055, + THING_TYPE_RSX1056, THING_TYPE_RSX1057, THING_TYPE_RSX1058, THING_TYPE_RSX1065, THING_TYPE_RSX1067, + THING_TYPE_RSX1550, THING_TYPE_RSX1560, THING_TYPE_RSX1562, THING_TYPE_RX1052, THING_TYPE_A11, + THING_TYPE_A12, THING_TYPE_A14, THING_TYPE_CD11, THING_TYPE_CD14, THING_TYPE_RA11, 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_C8, THING_TYPE_M8, + THING_TYPE_P5, THING_TYPE_S5, THING_TYPE_X3, THING_TYPE_X5); private final SerialPortManager serialPortManager; private final RotelStateDescriptionOptionProvider stateDescriptionProvider; 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 1cae784ec06..3ed104e5bf6 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 @@ -23,6 +23,8 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.rotel.internal.communication.RotelCommand; import org.openhab.binding.rotel.internal.communication.RotelDsp; +import org.openhab.binding.rotel.internal.communication.RotelFlagInfo; +import org.openhab.binding.rotel.internal.communication.RotelFlagInfoType; import org.openhab.binding.rotel.internal.communication.RotelFlagsMapping; import org.openhab.binding.rotel.internal.communication.RotelSource; import org.openhab.binding.rotel.internal.protocol.RotelProtocol; @@ -44,19 +46,19 @@ public enum RotelModel { concatenate(DSP_CMDS_SET1, MENU2_CTRL_CMDS, OTHER_CMDS_SET1), (byte) 0xA1, 42, 5, true, RotelFlagsMapping.MAPPING2), RSP1069("RSP-1069", 38400, 1, 3, true, 96, true, 6, false, RECORD_FONCTION_SELECT, 2, - concatenate(DSP_CMDS_SET1, MENU2_CTRL_CMDS, OTHER_CMDS_SET1, OTHER_CMDS_SET2), (byte) 0xA2, 42, 5, true, - RotelFlagsMapping.MAPPING5), + concatenate(DSP_CMDS_SET1, MENU2_CTRL_CMDS, OTHER_CMDS_SET1, OTHER_CMDS_SET2, OTHER_CMDS_SET4), (byte) 0xA2, + 42, 5, true, RotelFlagsMapping.MAPPING3), RSP1098("RSP-1098", 19200, 1, 1, true, 96, true, 6, false, ZONE_SELECT, 2, concatenate(DSP_CMDS_SET1, MENU2_CTRL_CMDS, OTHER_CMDS_SET1, List.of(REMOTE_VOLUME_UP, REMOTE_VOLUME_DOWN)), (byte) 0xA0, 13, 8, true, RotelFlagsMapping.MAPPING1), RSP1570("RSP-1570", 115200, 1, 3, true, 96, true, 6, false, RECORD_FONCTION_SELECT, 3, concatenate(DSP_CMDS_SET2, DSP_CMDS_SET1, MENU2_CTRL_CMDS, OTHER_CMDS_SET1, OTHER_CMDS_SET2, - List.of(RESET_FACTORY)), - (byte) 0xA3, 42, 5, true, RotelFlagsMapping.MAPPING5), + OTHER_CMDS_SET4, List.of(RESET_FACTORY)), + (byte) 0xA3, 42, 5, true, RotelFlagsMapping.MAPPING3), RSP1572("RSP-1572", 115200, 2, 3, true, 96, true, null, false, RECORD_FONCTION_SELECT, 4, concatenate(DSP_CMDS_SET2, DSP_CMDS_SET1, NUMERIC_KEY_CMDS, MENU3_CTRL_CMDS, OTHER_CMDS_SET1, - OTHER_CMDS_SET4), - (byte) 0xA5, 42, 5, true, RotelFlagsMapping.MAPPING5), + OTHER_CMDS_SET2, OTHER_CMDS_SET4, OTHER_CMDS_SET8), + (byte) 0xA5, 42, 5, true, RotelFlagsMapping.MAPPING3), RSX1055("RSX-1055", 19200, 3, 1, false, 90, false, 12, false, ZONE_SELECT, 1, concatenate(DSP_CMDS_SET1, TUNER_CMDS_SET1, NUMERIC_KEY_CMDS, MENU2_CTRL_CMDS, OTHER_CMDS_SET1), (byte) 0xC3, 13, 8, true, RotelFlagsMapping.MAPPING1), @@ -65,13 +67,20 @@ public enum RotelModel { MENU2_CTRL_CMDS, OTHER_CMDS_SET1), (byte) 0xC5, 13, 8, true, RotelFlagsMapping.MAPPING1), RSX1057("RSX-1057", 19200, 1, 1, true, 96, true, 6, false, RECORD_FONCTION_SELECT, 2, - concatenate(DSP_CMDS_SET1, TUNER_CMDS_SET2, ZONE2_TUNER_CMDS_SET2, NUMERIC_KEY_CMDS, ZONE2_NUMERIC_KEY_CMDS, - MENU2_CTRL_CMDS, OTHER_CMDS_SET1), + concatenate(DSP_CMDS_SET1, TUNER_CMDS_SET2, TUNER_CMDS_SET3, ZONE2_TUNER_CMDS_SET2, NUMERIC_KEY_CMDS, + ZONE2_NUMERIC_KEY_CMDS, MENU2_CTRL_CMDS, OTHER_CMDS_SET1), (byte) 0xC7, 13, 8, true, RotelFlagsMapping.MAPPING1), RSX1058("RSX-1058", 38400, 1, 3, true, 96, true, 6, false, RECORD_FONCTION_SELECT, 2, - concatenate(DSP_CMDS_SET1, TUNER_CMDS_SET2, ZONE234_TUNER_CMDS_SET1, NUMERIC_KEY_CMDS, - ZONE234_NUMERIC_KEY_CMDS, MENU2_CTRL_CMDS, OTHER_CMDS_SET1, OTHER_CMDS_SET2), - (byte) 0xC8, 13, 8, true, RotelFlagsMapping.MAPPING4), + concatenate(DSP_CMDS_SET1, TUNER_CMDS_SET2, TUNER_CMDS_SET3, ZONE234_TUNER_CMDS_SET1, NUMERIC_KEY_CMDS, + ZONE234_NUMERIC_KEY_CMDS, MENU2_CTRL_CMDS, OTHER_CMDS_SET1, OTHER_CMDS_SET2, OTHER_CMDS_SET4), + (byte) 0xC8, 13, 8, true, new RotelFlagsMapping(List.of(// + new RotelFlagInfo(RotelFlagInfoType.MULTI_INPUT, 3, 1), // + new RotelFlagInfo(RotelFlagInfoType.ZONE2, 1, 7), // + new RotelFlagInfo(RotelFlagInfoType.ZONE3, 2, 7), // + new RotelFlagInfo(RotelFlagInfoType.ZONE4, 6, 2), // + new RotelFlagInfo(RotelFlagInfoType.CENTER, 8, 6), // + new RotelFlagInfo(RotelFlagInfoType.SURROUND_LEFT, 8, 4), // + new RotelFlagInfo(RotelFlagInfoType.SURROUND_RIGHT, 8, 3)))), RSX1065("RSX-1065", 19200, 3, 1, false, 96, false, 12, false, ZONE_SELECT, 1, concatenate(DSP_CMDS_SET1, TUNER_CMDS_SET1, NUMERIC_KEY_CMDS, MENU2_CTRL_CMDS, OTHER_CMDS_SET3), (byte) 0xC1, 42, 5, true, RotelFlagsMapping.MAPPING2), @@ -80,19 +89,34 @@ public enum RotelModel { MENU2_CTRL_CMDS, OTHER_CMDS_SET1), (byte) 0xC4, 42, 5, true, RotelFlagsMapping.MAPPING2), RSX1550("RSX-1550", 115200, 1, 3, true, 96, true, 6, false, RECORD_FONCTION_SELECT, 3, - concatenate(DSP_CMDS_SET1, TUNER_CMDS_SET2, ZONE234_TUNER_CMDS_SET1, NUMERIC_KEY_CMDS, - ZONE234_NUMERIC_KEY_CMDS, MENU2_CTRL_CMDS, OTHER_CMDS_SET1, OTHER_CMDS_SET2, + concatenate(DSP_CMDS_SET1, TUNER_CMDS_SET2, TUNER_CMDS_SET3, ZONE234_TUNER_CMDS_SET1, NUMERIC_KEY_CMDS, + ZONE234_NUMERIC_KEY_CMDS, MENU2_CTRL_CMDS, OTHER_CMDS_SET1, OTHER_CMDS_SET2, OTHER_CMDS_SET4, List.of(RESET_FACTORY)), - (byte) 0xC9, 13, 8, true, RotelFlagsMapping.MAPPING3), + (byte) 0xC9, 13, 8, true, new RotelFlagsMapping(List.of(// + new RotelFlagInfo(RotelFlagInfoType.MULTI_INPUT, 4, 7), // + new RotelFlagInfo(RotelFlagInfoType.ZONE2, 1, 7), // + new RotelFlagInfo(RotelFlagInfoType.ZONE3, 2, 7), // + new RotelFlagInfo(RotelFlagInfoType.ZONE4, 6, 2), // + new RotelFlagInfo(RotelFlagInfoType.CENTER, 5, 6), // + new RotelFlagInfo(RotelFlagInfoType.SURROUND_LEFT, 5, 4), // + new RotelFlagInfo(RotelFlagInfoType.SURROUND_RIGHT, 5, 3)))), RSX1560("RSX-1560", 115200, 1, 3, true, 96, true, 6, false, RECORD_FONCTION_SELECT, 3, - concatenate(DSP_CMDS_SET1, TUNER_CMDS_SET2, ZONE234_TUNER_CMDS_SET1, NUMERIC_KEY_CMDS, - ZONE234_NUMERIC_KEY_CMDS, MENU2_CTRL_CMDS, OTHER_CMDS_SET1, OTHER_CMDS_SET2, + concatenate(DSP_CMDS_SET1, TUNER_CMDS_SET2, TUNER_CMDS_SET3, ZONE234_TUNER_CMDS_SET1, NUMERIC_KEY_CMDS, + ZONE234_NUMERIC_KEY_CMDS, MENU2_CTRL_CMDS, OTHER_CMDS_SET1, OTHER_CMDS_SET2, OTHER_CMDS_SET4, List.of(RESET_FACTORY)), - (byte) 0xCA, 42, 5, true, RotelFlagsMapping.MAPPING5), + (byte) 0xCA, 42, 5, true, RotelFlagsMapping.MAPPING3), RSX1562("RSX-1562", 115200, 2, 3, true, 96, true, null, false, RECORD_FONCTION_SELECT, 4, - concatenate(DSP_CMDS_SET2, DSP_CMDS_SET1, TUNER_CMDS_SET2, ZONE234_TUNER_CMDS_SET1, NUMERIC_KEY_CMDS, - ZONE234_NUMERIC_KEY_CMDS, MENU3_CTRL_CMDS, OTHER_CMDS_SET1, OTHER_CMDS_SET4), - (byte) 0xCC, 42, 5, true, RotelFlagsMapping.MAPPING5), + concatenate(DSP_CMDS_SET2, DSP_CMDS_SET1, TUNER_CMDS_SET2, TUNER_CMDS_SET3, ZONE234_TUNER_CMDS_SET1, + NUMERIC_KEY_CMDS, ZONE234_NUMERIC_KEY_CMDS, MENU3_CTRL_CMDS, OTHER_CMDS_SET1, OTHER_CMDS_SET2, + OTHER_CMDS_SET4, OTHER_CMDS_SET8), + (byte) 0xCC, 42, 5, true, RotelFlagsMapping.MAPPING3), + RX1052("RX-1052", 38400, 22, 3, true, 90, true, 10, false, RECORD_FONCTION_SELECT, -1, + concatenate(TUNER_CMDS_SET2, ZONE234_TUNER_CMDS_SET1, NUMERIC_KEY_CMDS, ZONE234_NUMERIC_KEY_CMDS, + OTHER_CMDS_SET2, OTHER_CMDS_SET9), + (byte) 0x61, 11, 5, false, new RotelFlagsMapping(List.of(// + new RotelFlagInfo(RotelFlagInfoType.ZONE, 1, 1), // + new RotelFlagInfo(RotelFlagInfoType.SPEAKER_A, 1, 3), // + new RotelFlagInfo(RotelFlagInfoType.SPEAKER_B, 1, 2)))), A11("A11", 115200, 4, 96, true, 10, 15, false, -1, false, true, true, 6, 0, SRC_CTRL_CMDS_SET1, NO_SPECIAL_CHARACTERS), A12("A12", 115200, 5, 96, true, 10, 15, false, -1, true, true, true, 6, 0, @@ -154,7 +178,7 @@ public enum RotelModel { T11("T11", 115200, 12, null, false, null, false, -1, false, true, 6, 0, List.of(), NO_SPECIAL_CHARACTERS), T14("T14", 115200, 13, null, false, null, false, -1, false, true, 6, 0, List.of(), NO_SPECIAL_CHARACTERS), C8("C8", 115200, POWER, 21, 3, true, false, 96, true, 10, false, 10, false, null, -1, true, false, true, 4, 0, - List.of(), (byte) 0, 0, 0, false, RotelFlagsMapping.NO_MAPPING, NO_SPECIAL_CHARACTERS), + List.of(), (byte) 0, 0, 0, false, new RotelFlagsMapping(), NO_SPECIAL_CHARACTERS), M8("M8", 115200, 0, null, false, null, false, -1, false, true, 4, 0, List.of(), NO_SPECIAL_CHARACTERS), P5("P5", 115200, 20, 96, true, 10, 10, false, -1, true, false, true, 4, 0, SRC_CTRL_CMDS_SET1, NO_SPECIAL_CHARACTERS), @@ -248,7 +272,7 @@ public enum RotelModel { this(name, baudRate, POWER, sourceCategory, 0, false, false, volumeMax, directVolume, toneLevelMax, toneLevelMax != null, null, playControl, null, dspCategory, getFrequencyAvailable, false, getDimmerLevelAvailable, diummerLevelMin, diummerLevelMax, otherCommands, (byte) 0, 0, 0, false, - RotelFlagsMapping.NO_MAPPING, specialCharacters); + new RotelFlagsMapping(), specialCharacters); } /** @@ -279,7 +303,7 @@ public enum RotelModel { this(name, baudRate, POWER, sourceCategory, 0, false, false, volumeMax, directVolume, toneLevelMax, toneLevelMax != null, balanceLevelMax, playControl, null, dspCategory, getFrequencyAvailable, getSpeakerGroupsAvailable, getDimmerLevelAvailable, diummerLevelMin, diummerLevelMax, otherCommands, - (byte) 0, 0, 0, false, RotelFlagsMapping.NO_MAPPING, specialCharacters); + (byte) 0, 0, 0, false, new RotelFlagsMapping(), specialCharacters); } /** @@ -736,103 +760,41 @@ public enum RotelModel { } /** - * Inform whether the multiple input source is set to ON in the flags + * Inform whether the information is present in flags * - * @param flags the flag from the standard response message + * @param infoType the type of information * - * @return true if the multiple input source is ON - * - * @throws RotelException - If this information is not present in the flags for this model + * @return true if the information is available */ - public boolean isMultiInputOn(byte[] flags) throws RotelException { - return flagsMapping.isMultiInputOn(flags); + public boolean isInfoPresentInFlags(RotelFlagInfoType infoType) { + return flagsMapping.isInfoPresent(infoType); } /** - * Set the multiple input source to ON or OFF in the flags + * Inform whether the information is set to ON in the flags * + * @param infoType the type of information + * @param flags the flag from the standard response message + * + * @return true if the information is ON + * + * @throws RotelException - If this information is not present in the flags for this model + */ + public boolean isInfoOnInFlags(RotelFlagInfoType infoType, byte[] flags) throws RotelException { + return flagsMapping.isInfoOn(infoType, flags); + } + + /** + * Set the information to ON or OFF in the flags + * + * @param infoType the type of information * @param flags the flag from the standard response message * @param on true for ON and false for OFF * - * @throws RotelException - If this information is not present in the flags for this model + * @return true if the information was updated in the flags, false if not */ - public void setMultiInput(byte[] flags, boolean on) throws RotelException { - flagsMapping.setMultiInput(flags, on); - } - - /** - * Inform whether the zone 2 is set to ON in the flags - * - * @param flags the flag from the standard response message - * - * @return true if the zone 2 is ON - * - * @throws RotelException - If this information is not present in the flags for this model - */ - public boolean isZone2On(byte[] flags) throws RotelException { - return flagsMapping.isZone2On(flags); - } - - /** - * Set the zone 2 to ON or OFF in the flags - * - * @param flags the flag from the standard response message - * @param on true for ON and false for OFF - * - * @throws RotelException - If this information is not present in the flags for this model - */ - public void setZone2(byte[] flags, boolean on) throws RotelException { - flagsMapping.setZone2(flags, on); - } - - /** - * Inform whether the zone 3 is set to ON in the flags - * - * @param flags the flag from the standard response message - * - * @return true if the zone 3 is ON - * - * @throws RotelException - If this information is not present in the flags for this model - */ - public boolean isZone3On(byte[] flags) throws RotelException { - return flagsMapping.isZone3On(flags); - } - - /** - * Set the zone 3 to ON or OFF in the flags - * - * @param flags the flag from the standard response message - * @param on true for ON and false for OFF - * - * @throws RotelException - If this information is not present in the flags for this model - */ - public void setZone3(byte[] flags, boolean on) throws RotelException { - flagsMapping.setZone3(flags, on); - } - - /** - * Inform whether the zone 4 is set to ON in the flags - * - * @param flags the flag from the standard response message - * - * @return true if the zone 4 is ON - * - * @throws RotelException - If this information is not present in the flags for this model - */ - public boolean isZone4On(byte[] flags) throws RotelException { - return flagsMapping.isZone4On(flags); - } - - /** - * Set the zone 4 to ON or OFF in the flags - * - * @param flags the flag from the standard response message - * @param on true for ON and false for OFF - * - * @throws RotelException - If this information is not present in the flags for this model - */ - public void setZone4(byte[] flags, boolean on) throws RotelException { - flagsMapping.setZone4(flags, on); + public boolean setInfoInFlags(RotelFlagInfoType infoType, byte[] flags, boolean on) { + return flagsMapping.setInfo(infoType, flags, on); } /** 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 2212ef9e84f..dddac41de24 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 @@ -150,6 +150,7 @@ public enum RotelCommand { "main_zone_video5"), MAIN_ZONE_SOURCE_VIDEO6("Main Zone Source Video 6", MAIN_ZONE_CMD, (byte) 0x94, "main_zone_video6", "main_zone_video6"), + MAIN_ZONE_SOURCE_PHONO("Main Zone Source Phono", MAIN_ZONE_CMD, (byte) 0x35, "main_zone_phono", "main_zone_phono"), 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"), @@ -166,6 +167,7 @@ public enum RotelCommand { RECORD_SOURCE_VIDEO4("Record Source Video 4", RECORD_SRC_CMD, (byte) 0x08, "record_video4", "record_video4"), RECORD_SOURCE_VIDEO5("Record Source Video 5", RECORD_SRC_CMD, (byte) 0x09, "record_video5", "record_video5"), RECORD_SOURCE_VIDEO6("Record Source Video 6", RECORD_SRC_CMD, (byte) 0x94, "record_video6", "record_video6"), + RECORD_SOURCE_PHONO("Record Source Phono", RECORD_SRC_CMD, (byte) 0x35, "record_phono", "record_phono"), RECORD_SOURCE_USB("Record Source Front USB", RECORD_SRC_CMD, (byte) 0x8E, "record_usb", "record_usb"), RECORD_SOURCE_MAIN("Record Follow Main Zone Source", RECORD_SRC_CMD, (byte) 0x6B, "record_follow_main", "record_follow_main"), @@ -178,6 +180,7 @@ public enum RotelCommand { ZONE2_SOURCE_VIDEO4("Zone 2 Source Video 4", ZONE2_CMD, (byte) 0x08, "zone2_video4", "zone2_video4"), ZONE2_SOURCE_VIDEO5("Zone 2 Source Video 5", ZONE2_CMD, (byte) 0x09, "zone2_video5", "zone2_video5"), ZONE2_SOURCE_VIDEO6("Zone 2 Source Video 6", ZONE2_CMD, (byte) 0x94, "zone2_video6", "zone2_video6"), + ZONE2_SOURCE_PHONO("Zone 2 Source Phono", ZONE2_CMD, (byte) 0x35, "zone2_phono", "zone2_phono"), 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"), @@ -194,6 +197,7 @@ public enum RotelCommand { ZONE3_SOURCE_VIDEO4("Zone 3 Source Video 4", ZONE3_CMD, (byte) 0x08, "zone3_video4", "zone3_video4"), ZONE3_SOURCE_VIDEO5("Zone 3 Source Video 5", ZONE3_CMD, (byte) 0x09, "zone3_video5", "zone3_video5"), ZONE3_SOURCE_VIDEO6("Zone 3 Source Video 6", ZONE3_CMD, (byte) 0x94, "zone3_video6", "zone3_video6"), + ZONE3_SOURCE_PHONO("Zone 3 Source Phono", ZONE3_CMD, (byte) 0x35, "zone3_phono", "zone3_phono"), 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"), @@ -210,6 +214,7 @@ public enum RotelCommand { ZONE4_SOURCE_VIDEO4("Zone 4 Source Video 4", ZONE4_CMD, (byte) 0x08, "zone4_video4", "zone4_video4"), ZONE4_SOURCE_VIDEO5("Zone 4 Source Video 5", ZONE4_CMD, (byte) 0x09, "zone4_video5", "zone4_video5"), ZONE4_SOURCE_VIDEO6("Zone 4 Source Video 6", ZONE4_CMD, (byte) 0x94, "zone4_video6", "zone4_video6"), + ZONE4_SOURCE_PHONO("Zone 4 Source Phono", ZONE4_CMD, (byte) 0x35, "zone4_phono", "zone4_phono"), 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"), @@ -520,7 +525,8 @@ public enum RotelCommand { RDS_PTY, RDS_TP, RDS_TA, FM_MONO_TOGGLE); public static final List TUNER_CMDS_SET2 = List.of(TUNE_UP, TUNE_DOWN, PRESET_UP, PRESET_DOWN, FREQUENCY_UP, FREQUENCY_DOWN, MEMORY, BAND_TOGGLE, AM, FM, TUNE_PRESET_TOGGLE, TUNING_MODE_SELECT, - PRESET_MODE_SELECT, FREQUENCY_DIRECT, PRESET_SCAN, TUNER_DISPLAY, RDS_PTY, RDS_TP, RDS_TA, FM_MONO_TOGGLE); + PRESET_MODE_SELECT, FREQUENCY_DIRECT, PRESET_SCAN, FM_MONO_TOGGLE); + public static final List TUNER_CMDS_SET3 = List.of(TUNER_DISPLAY, RDS_PTY, RDS_TP, RDS_TA); public static final List ZONE2_TUNER_CMDS_SET1 = List.of(ZONE2_TUNE_UP, ZONE2_TUNE_DOWN, ZONE2_BAND_TOGGLE, ZONE2_AM, ZONE2_FM, ZONE2_TUNE_PRESET_TOGGLE, ZONE2_TUNING_MODE_SELECT, ZONE2_PRESET_MODE_SELECT, ZONE2_PRESET_SCAN, ZONE2_FM_MONO_TOGGLE); @@ -563,17 +569,18 @@ public enum RotelCommand { public static final List OTHER_CMDS_SET1 = List.of(RECORD_FONCTION_SELECT, TONE_CONTROL_SELECT, DYNAMIC_RANGE, DIGITAL_INPUT_SELECT, ZONE_TOGGLE, CENTER_TRIM, SUB_TRIM, SURROUND_TRIM, CINEMA_EQ_TOGGLE); public static final List OTHER_CMDS_SET2 = List.of(POWER_OFF_ALL_ZONES, PARTY_MODE_TOGGLE, - ZONE2_PARTY_MODE_TOGGLE, ZONE3_PARTY_MODE_TOGGLE, ZONE4_PARTY_MODE_TOGGLE, OUTPUT_RESOLUTION, HDMI_AMP_MODE, - HDMI_TV_MODE); + ZONE2_PARTY_MODE_TOGGLE, ZONE3_PARTY_MODE_TOGGLE, ZONE4_PARTY_MODE_TOGGLE); public static final List OTHER_CMDS_SET3 = List.of(RECORD_FONCTION_SELECT, DYNAMIC_RANGE, DIGITAL_INPUT_SELECT, ZONE_TOGGLE, CENTER_TRIM, SUB_TRIM, SURROUND_TRIM, CINEMA_EQ_TOGGLE); - public static final List OTHER_CMDS_SET4 = List.of(POWER_OFF_ALL_ZONES, PARTY_MODE_TOGGLE, - ZONE2_PARTY_MODE_TOGGLE, ZONE3_PARTY_MODE_TOGGLE, ZONE4_PARTY_MODE_TOGGLE, OUTPUT_RESOLUTION, HDMI_AMP_MODE, - HDMI_TV_MODE, ROOM_EQ_TOGGLE, SPEAKER_SETTING_TOGGLE, RESET_FACTORY); + public static final List OTHER_CMDS_SET4 = List.of(OUTPUT_RESOLUTION, HDMI_AMP_MODE, HDMI_TV_MODE); public static final List OTHER_CMDS_SET5 = List.of(POWER_MODE, POWER_MODE_QUICK, POWER_MODE_NORMAL, RESET_FACTORY); public static final List OTHER_CMDS_SET6 = List.of(POWER_MODE, RESET_FACTORY); public static final List OTHER_CMDS_SET7 = List.of(NEXT_MODE, RESET_FACTORY); + public static final List OTHER_CMDS_SET8 = List.of(ROOM_EQ_TOGGLE, SPEAKER_SETTING_TOGGLE, + RESET_FACTORY); + public static final List OTHER_CMDS_SET9 = List.of(RECORD_FONCTION_SELECT, TONE_CONTROL_SELECT, + ZONE_TOGGLE); public static final byte PRIMARY_COMMAND = (byte) 0x10; @@ -766,4 +773,27 @@ public enum RotelCommand { return Stream.of(list1, list2, list3, list4, list5, list6, list7, list8, list9).flatMap(Collection::stream) .collect(Collectors.toList()); } + + public static List concatenate(List list1, List list2, + List list3, List list4, List list5, List list6, + List list7, List list8, List list9, List list10) { + return Stream.of(list1, list2, list3, list4, list5, list6, list7, list8, list9, list10) + .flatMap(Collection::stream).collect(Collectors.toList()); + } + + public static List concatenate(List list1, List list2, + List list3, List list4, List list5, List list6, + List list7, List list8, List list9, List list10, + List list11) { + return Stream.of(list1, list2, list3, list4, list5, list6, list7, list8, list9, list10, list11) + .flatMap(Collection::stream).collect(Collectors.toList()); + } + + public static List concatenate(List list1, List list2, + List list3, List list4, List list5, List list6, + List list7, List list8, List list9, List list10, + List list11, List list12) { + return Stream.of(list1, list2, list3, list4, list5, list6, list7, list8, list9, list10, list11, list12) + .flatMap(Collection::stream).collect(Collectors.toList()); + } } diff --git a/bundles/org.openhab.binding.rotel/src/main/java/org/openhab/binding/rotel/internal/communication/RotelFlagInfo.java b/bundles/org.openhab.binding.rotel/src/main/java/org/openhab/binding/rotel/internal/communication/RotelFlagInfo.java new file mode 100644 index 00000000000..516fcf6d074 --- /dev/null +++ b/bundles/org.openhab.binding.rotel/src/main/java/org/openhab/binding/rotel/internal/communication/RotelFlagInfo.java @@ -0,0 +1,24 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.rotel.internal.communication; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * Record describing one information in response flags (HEX protocol) + * + * @author Laurent Garnier - Initial contribution + */ +@NonNullByDefault +public record RotelFlagInfo(RotelFlagInfoType infoType, int flagNumber, int bitNumber) { +} diff --git a/bundles/org.openhab.binding.rotel/src/main/java/org/openhab/binding/rotel/internal/communication/RotelFlagInfoType.java b/bundles/org.openhab.binding.rotel/src/main/java/org/openhab/binding/rotel/internal/communication/RotelFlagInfoType.java new file mode 100644 index 00000000000..6fb627c7ff6 --- /dev/null +++ b/bundles/org.openhab.binding.rotel/src/main/java/org/openhab/binding/rotel/internal/communication/RotelFlagInfoType.java @@ -0,0 +1,34 @@ +/** + * Copyright (c) 2010-2024 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.rotel.internal.communication; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * Represents the different types of information that can be included in response flags (HEX protocol) + * + * @author Laurent Garnier - Initial contribution + */ +@NonNullByDefault +public enum RotelFlagInfoType { + MULTI_INPUT, + ZONE2, + ZONE3, + ZONE4, + ZONE, + CENTER, + SURROUND_LEFT, + SURROUND_RIGHT, + SPEAKER_A, + SPEAKER_B +} diff --git a/bundles/org.openhab.binding.rotel/src/main/java/org/openhab/binding/rotel/internal/communication/RotelFlagsMapping.java b/bundles/org.openhab.binding.rotel/src/main/java/org/openhab/binding/rotel/internal/communication/RotelFlagsMapping.java index 46fa30dcfbb..c8e2686bcc1 100644 --- a/bundles/org.openhab.binding.rotel/src/main/java/org/openhab/binding/rotel/internal/communication/RotelFlagsMapping.java +++ b/bundles/org.openhab.binding.rotel/src/main/java/org/openhab/binding/rotel/internal/communication/RotelFlagsMapping.java @@ -12,6 +12,10 @@ */ package org.openhab.binding.rotel.internal.communication; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + import org.eclipse.jdt.annotation.NonNullByDefault; import org.openhab.binding.rotel.internal.RotelException; @@ -23,174 +27,77 @@ import org.openhab.binding.rotel.internal.RotelException; @NonNullByDefault public class RotelFlagsMapping { - public static final RotelFlagsMapping MAPPING1 = new RotelFlagsMapping(3, 1, 5, 0, -1, -1, -1, -1, 8, 6, 8, 4, 8, - 3); - public static final RotelFlagsMapping MAPPING2 = new RotelFlagsMapping(-1, -1, 4, 7, -1, -1, -1, -1, 5, 6, 5, 4, 5, - 3); - public static final RotelFlagsMapping MAPPING3 = new RotelFlagsMapping(4, 7, 1, 7, 2, 7, 6, 2, 5, 6, 5, 4, 5, 3); - public static final RotelFlagsMapping MAPPING4 = new RotelFlagsMapping(3, 1, 1, 7, 2, 7, 6, 2, 8, 6, 8, 4, 8, 3); - public static final RotelFlagsMapping MAPPING5 = new RotelFlagsMapping(-1, -1, 3, 2, 4, 2, 4, 1, 5, 6, 5, 4, 5, 3); - public static final RotelFlagsMapping NO_MAPPING = new RotelFlagsMapping(-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1); + public static final RotelFlagsMapping MAPPING1 = new RotelFlagsMapping( + List.of(new RotelFlagInfo(RotelFlagInfoType.MULTI_INPUT, 3, 1), // + new RotelFlagInfo(RotelFlagInfoType.ZONE2, 5, 0), // + new RotelFlagInfo(RotelFlagInfoType.CENTER, 8, 6), // + new RotelFlagInfo(RotelFlagInfoType.SURROUND_LEFT, 8, 4), // + new RotelFlagInfo(RotelFlagInfoType.SURROUND_RIGHT, 8, 3))); + public static final RotelFlagsMapping MAPPING2 = new RotelFlagsMapping( + List.of(new RotelFlagInfo(RotelFlagInfoType.ZONE2, 4, 7), // + new RotelFlagInfo(RotelFlagInfoType.CENTER, 5, 6), // + new RotelFlagInfo(RotelFlagInfoType.SURROUND_LEFT, 5, 4), // + new RotelFlagInfo(RotelFlagInfoType.SURROUND_RIGHT, 5, 3))); + public static final RotelFlagsMapping MAPPING3 = new RotelFlagsMapping( + List.of(new RotelFlagInfo(RotelFlagInfoType.ZONE2, 3, 2), // + new RotelFlagInfo(RotelFlagInfoType.ZONE3, 4, 2), // + new RotelFlagInfo(RotelFlagInfoType.ZONE4, 4, 1), // + new RotelFlagInfo(RotelFlagInfoType.CENTER, 5, 6), // + new RotelFlagInfo(RotelFlagInfoType.SURROUND_LEFT, 5, 4), // + new RotelFlagInfo(RotelFlagInfoType.SURROUND_RIGHT, 5, 3))); - private int multiInputFlagNumber; - private int multiInputBitNumber; - private int zone2FlagNumber; - private int zone2BitNumber; - private int zone3FlagNumber; - private int zone3BitNumber; - private int zone4FlagNumber; - private int zone4BitNumber; - private int centerFlagNumber; - private int centerBitNumber; - private int surroundLeftFlagNumber; - private int surroundLeftBitNumber; - private int surroundRightFlagNumber; - private int surroundRightBitNumber; + private Map infoMap; - /** - * Constructor - * - * For each flag number, value 1 means the first flag; a negative value is used for an undefined indicator. - * For each bit number, value is from 0 to 7; a negative value is used for an undefined indicator. - * - * @param multiInputFlagNumber the flag number in the standard feedback message in which to find the multi input - * indicator - * @param multiInputBitNumber the bit number in the flag in which to find the multi input indicator - * @param zone2FlagNumber the flag number in the standard feedback message in which to find the zone 2 indicator - * @param zone2BitNumber the bit number in the flag in which to find the zone 2 indicator - * @param zone3FlagNumber the flag number in the standard feedback message in which to find the zone 3 indicator - * @param zone3BitNumber the bit number in the flag in which to find the zone 3 indicator - * @param zone4FlagNumber the flag number in the standard feedback message in which to find the zone 4 indicator - * @param zone4BitNumber the bit number in the flag in which to find the zone 4 indicator - * @param centerFlagNumber the flag number in the standard feedback message in which to find the center channel - * indicator - * @param centerBitNumber the bit number in the flag in which to find the center channel indicator - * @param surroundLeftFlagNumber the flag number in the standard feedback message in which to find the surround left - * channel indicator - * @param surroundLeftBitNumber the bit number in the flag in which to find the surround left channel indicator - * @param surroundRightFlagNumber the flag number in the standard feedback message in which to find the surround - * right channel indicator - * @param surroundRightBitNumber the bit number in the flag in which to find the surround right channel indicator - */ - private RotelFlagsMapping(int multiInputFlagNumber, int multiInputBitNumber, int zone2FlagNumber, - int zone2BitNumber, int zone3FlagNumber, int zone3BitNumber, int zone4FlagNumber, int zone4BitNumber, - int centerFlagNumber, int centerBitNumber, int surroundLeftFlagNumber, int surroundLeftBitNumber, - int surroundRightFlagNumber, int surroundRightBitNumber) { - this.multiInputFlagNumber = multiInputFlagNumber; - this.multiInputBitNumber = multiInputBitNumber; - this.zone2FlagNumber = zone2FlagNumber; - this.zone2BitNumber = zone2BitNumber; - this.zone3FlagNumber = zone3FlagNumber; - this.zone3BitNumber = zone3BitNumber; - this.zone4FlagNumber = zone4FlagNumber; - this.zone4BitNumber = zone4BitNumber; - this.centerFlagNumber = centerFlagNumber; - this.centerBitNumber = centerBitNumber; - this.surroundLeftFlagNumber = surroundLeftFlagNumber; - this.surroundLeftBitNumber = surroundLeftBitNumber; - this.surroundRightFlagNumber = surroundRightFlagNumber; - this.surroundRightBitNumber = surroundRightBitNumber; + public RotelFlagsMapping() { + this.infoMap = new HashMap<>(); + } + + public RotelFlagsMapping(List infos) { + this.infoMap = new HashMap<>(); + for (RotelFlagInfo info : infos) { + this.infoMap.put(info.infoType(), info); + } } /** - * Get the multi input indicator + * Get the availability of the information * - * @param flags the table of flags - * - * @return true if the indicator is ON in the flags or false if OFF - * - * @throws RotelException in case the multi input indicator is undefined + * @return true if the information is available */ - public boolean isMultiInputOn(byte[] flags) throws RotelException { - return RotelFlagsMapping.isBitFlagOn(flags, multiInputFlagNumber, multiInputBitNumber); + public boolean isInfoPresent(RotelFlagInfoType infoType) { + return infoMap.get(infoType) != null; } /** - * Set the multi input indicator + * Get the information * + * @param infoType the type of information * @param flags the table of flags - * @param on true to set the indicator to ON or false to set it to OFF * - * @throws RotelException in case the multi input indicator is undefined + * @return true if the information is ON in the flags or false if OFF + * + * @throws RotelException in case the information is undefined */ - public void setMultiInput(byte[] flags, boolean on) throws RotelException { - RotelFlagsMapping.setBitFlag(flags, multiInputFlagNumber, multiInputBitNumber, on); + public boolean isInfoOn(RotelFlagInfoType infoType, byte[] flags) throws RotelException { + RotelFlagInfo info = infoMap.get(infoType); + if (info == null || info.flagNumber() > flags.length) { + throw new RotelException("Info " + infoType.name() + " not available in flags"); + } + return RotelFlagsMapping.isBitFlagOn(flags, info.flagNumber(), info.bitNumber()); } /** - * Get the zone 2 indicator + * Set the information * + * @param infoType the type of information * @param flags the table of flags + * @param on true to set the information to ON or false to set it to OFF * - * @return true if the indicator is ON in the flags or false if OFF - * - * @throws RotelException in case the zone 2 indicator is undefined + * @return true if the information was updated, false if not */ - public boolean isZone2On(byte[] flags) throws RotelException { - return RotelFlagsMapping.isBitFlagOn(flags, zone2FlagNumber, zone2BitNumber); - } - - /** - * Set the zone 2 indicator - * - * @param flags the table of flags - * @param on true to set the indicator to ON or false to set it to OFF - * - * @throws RotelException in case the zone 2 indicator is undefined - */ - public void setZone2(byte[] flags, boolean on) throws RotelException { - RotelFlagsMapping.setBitFlag(flags, zone2FlagNumber, zone2BitNumber, on); - } - - /** - * Get the zone 3 indicator - * - * @param flags the table of flags - * - * @return true if the indicator is ON in the flags or false if OFF - * - * @throws RotelException in case the zone 3 indicator is undefined - */ - public boolean isZone3On(byte[] flags) throws RotelException { - return RotelFlagsMapping.isBitFlagOn(flags, zone3FlagNumber, zone3BitNumber); - } - - /** - * Set the zone 3 indicator - * - * @param flags the table of flags - * @param on true to set the indicator to ON or false to set it to OFF - * - * @throws RotelException in case the zone 3 indicator is undefined - */ - public void setZone3(byte[] flags, boolean on) throws RotelException { - RotelFlagsMapping.setBitFlag(flags, zone3FlagNumber, zone3BitNumber, on); - } - - /** - * Get the zone 4 indicator - * - * @param flags the table of flags - * - * @return true if the indicator is ON in the flags or false if OFF - * - * @throws RotelException in case the zone 4 indicator is undefined - */ - public boolean isZone4On(byte[] flags) throws RotelException { - return RotelFlagsMapping.isBitFlagOn(flags, zone4FlagNumber, zone4BitNumber); - } - - /** - * Set the zone 4 indicator - * - * @param flags the table of flags - * @param on true to set the indicator to ON or false to set it to OFF - * - * @throws RotelException in case the zone 4 indicator is undefined - */ - public void setZone4(byte[] flags, boolean on) throws RotelException { - RotelFlagsMapping.setBitFlag(flags, zone4FlagNumber, zone4BitNumber, on); + public boolean setInfo(RotelFlagInfoType infoType, byte[] flags, boolean on) { + RotelFlagInfo info = infoMap.get(infoType); + return info == null ? false : RotelFlagsMapping.setBitFlag(flags, info.flagNumber(), info.bitNumber(), on); } /** @@ -203,12 +110,15 @@ public class RotelFlagsMapping { * @throws RotelException in case the center or surround channel indicators are undefined */ public boolean isMoreThan2Channels(byte[] flags) throws RotelException { - return (centerFlagNumber >= 1 && centerFlagNumber <= flags.length - && RotelFlagsMapping.isBitFlagOn(flags, centerFlagNumber, centerBitNumber)) - || (surroundLeftFlagNumber >= 1 && surroundLeftFlagNumber <= flags.length - && RotelFlagsMapping.isBitFlagOn(flags, surroundLeftFlagNumber, surroundLeftBitNumber)) - || (surroundRightFlagNumber >= 1 && surroundRightFlagNumber <= flags.length - && RotelFlagsMapping.isBitFlagOn(flags, surroundRightFlagNumber, surroundRightBitNumber)); + RotelFlagInfo center = infoMap.get(RotelFlagInfoType.CENTER); + RotelFlagInfo surroundLeft = infoMap.get(RotelFlagInfoType.SURROUND_LEFT); + RotelFlagInfo surroundRight = infoMap.get(RotelFlagInfoType.SURROUND_RIGHT); + return (center != null && center.flagNumber() <= flags.length + && RotelFlagsMapping.isBitFlagOn(flags, center.flagNumber(), center.bitNumber())) + || (surroundLeft != null && surroundLeft.flagNumber() <= flags.length + && RotelFlagsMapping.isBitFlagOn(flags, surroundLeft.flagNumber(), surroundLeft.bitNumber())) + || (surroundRight != null && surroundRight.flagNumber() <= flags.length + && RotelFlagsMapping.isBitFlagOn(flags, surroundRight.flagNumber(), surroundRight.bitNumber())); } /** @@ -241,19 +151,17 @@ public class RotelFlagsMapping { * @param bitNumber the bit number in the flag to consider * @param on true to set the bit value to 1 or false to set it to 0 * - * @throws RotelException in case of out of bounds value for the flag number or the bit number + * @return true if the flag was updated, false if not */ - private static void setBitFlag(byte[] flags, int flagNumber, int bitNumber, boolean on) throws RotelException { - if (flagNumber < 1 || flagNumber > flags.length) { - throw new RotelException("Flag number out of bounds"); - } - if (bitNumber < 0 || bitNumber > 7) { - throw new RotelException("Bit number out of bounds"); + private static boolean setBitFlag(byte[] flags, int flagNumber, int bitNumber, boolean on) { + if (flagNumber < 1 || flagNumber > flags.length || bitNumber < 0 || bitNumber > 7) { + return false; } if (on) { flags[flagNumber - 1] |= (1 << bitNumber); } else { flags[flagNumber - 1] &= ~(1 << bitNumber); } + return true; } } 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 a7aa9a079ed..c749e421a6b 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 @@ -312,11 +312,11 @@ public class RotelSimuConnector extends RotelConnector { case RECORD_FONCTION_SELECT: if (model.getNumberOfZones() > 1 && model.getZoneSelectCmd() == cmd) { showZone++; - if (showZone >= model.getNumberOfZones()) { + if (showZone > model.getNumberOfZones()) { showZone = 1; - if (!powers[0]) { - showZone++; - } + } + if (showZone == 1 && !powers[0]) { + showZone++; } } else { showZone = 1; @@ -325,6 +325,9 @@ public class RotelSimuConnector extends RotelConnector { selectingRecord = powers[0]; showTreble = false; textLine2 = buildRecordResponse(); + if (model.getRespNbChars() == 11) { + text = textLine2; + } } else if (showZone >= 2 && showZone <= 4) { selectingRecord = false; text = textLine2 = buildZonePowerResponse(showZone); @@ -1182,8 +1185,8 @@ public class RotelSimuConnector extends RotelConnector { // Check if command is a change of record source try { recordSource = model.getRecordSourceFromCommand(cmd); - text = buildSourceLine1Response(); textLine2 = buildRecordResponse(); + text = model.getRespNbChars() == 11 ? textLine2 : buildSourceLine1Response(); accepted = true; } catch (RotelException e) { } @@ -1217,21 +1220,26 @@ public class RotelSimuConnector extends RotelConnector { if (protocol == RotelProtocol.HEX) { byte[] chars = Arrays.copyOf(text.getBytes(StandardCharsets.US_ASCII), model.getRespNbChars()); byte[] flags = new byte[model.getRespNbFlags()]; - try { - model.setMultiInput(flags, multiinput); - } catch (RotelException e) { + if (model.isInfoPresentInFlags(RotelFlagInfoType.MULTI_INPUT)) { + model.setInfoInFlags(RotelFlagInfoType.MULTI_INPUT, flags, multiinput); } - try { - model.setZone2(flags, powers[2]); - } catch (RotelException e) { + if (model.isInfoPresentInFlags(RotelFlagInfoType.ZONE2)) { + model.setInfoInFlags(RotelFlagInfoType.ZONE2, flags, powers[2]); } - try { - model.setZone3(flags, powers[3]); - } catch (RotelException e) { + if (model.isInfoPresentInFlags(RotelFlagInfoType.ZONE3)) { + model.setInfoInFlags(RotelFlagInfoType.ZONE3, flags, powers[3]); } - try { - model.setZone4(flags, powers[4]); - } catch (RotelException e) { + if (model.isInfoPresentInFlags(RotelFlagInfoType.ZONE4)) { + model.setInfoInFlags(RotelFlagInfoType.ZONE4, flags, powers[4]); + } + if (model.isInfoPresentInFlags(RotelFlagInfoType.ZONE)) { + model.setInfoInFlags(RotelFlagInfoType.ZONE, flags, powers[2] || powers[3] || powers[4]); + } + if (model.isInfoPresentInFlags(RotelFlagInfoType.SPEAKER_A)) { + model.setInfoInFlags(RotelFlagInfoType.SPEAKER_A, flags, speakerA); + } + if (model.isInfoPresentInFlags(RotelFlagInfoType.SPEAKER_B)) { + model.setInfoInFlags(RotelFlagInfoType.SPEAKER_B, flags, speakerB); } int size = 6 + model.getRespNbChars() + model.getRespNbFlags(); byte[] dataBuffer = new byte[size]; @@ -1479,9 +1487,18 @@ public class RotelSimuConnector extends RotelConnector { if (!powers[0]) { text = ""; } else if (mutes[0]) { - text = "MUTE ON"; + text = model.getRespNbChars() == 11 ? " MUTE ON " : "MUTE ON"; } else { - text = getSourceLabel(sources[0], false) + " " + getSourceLabel(recordSource, true); + text = getSourceLabel(sources[0], false); + if (model.getRespNbChars() == 11) { + if ("TUNER".equals(text)) { + text = "104.30M 10 "; + } else { + text += " " + buildVolumeValue(0); + } + } else { + text += " " + getSourceLabel(recordSource, true); + } } return text; } @@ -1507,26 +1524,16 @@ public class RotelSimuConnector extends RotelConnector { } private String buildZonePowerResponse(int numZone) { - String zone; - if (numZone == 2) { - zone = model.getNumberOfZones() > 2 ? "ZONE2" : "ZONE"; - } else { - zone = String.format("ZONE%d", numZone); + String zone = model.getRespNbChars() == 11 ? " Z" : "ZONE"; + if (numZone != 2 || model.getNumberOfZones() > 2) { + zone += String.format("%d", numZone); } String state = powers[numZone] ? getSourceLabel(sources[numZone], true) : "OFF"; return zone + " " + state; } private String buildVolumeLine1Response() { - String text; - if (volumes[0] == minVolume) { - text = " VOLUME MIN "; - } else if (volumes[0] == maxVolume) { - text = " VOLUME MAX "; - } else { - text = String.format(" VOLUME %02d ", volumes[0]); - } - return text; + return String.format(model.getRespNbChars() == 11 ? "VOLUME %s" : " VOLUME %s ", buildVolumeValue(0)); } private String buildVolumeLine1RightResponse() { @@ -1535,32 +1542,36 @@ public class RotelSimuConnector extends RotelConnector { text = ""; } else if (mutes[0]) { text = "MUTE ON"; - } else if (volumes[0] == minVolume) { - text = "VOL MIN"; - } else if (volumes[0] == maxVolume) { - text = "VOL MAX"; } else { - text = String.format("VOL %02d", volumes[0]); + text = String.format("VOL %s", buildVolumeValue(0)); } return text; } private String buildZoneVolumeResponse(int numZone) { - String zone; - if (numZone == 2) { - zone = model.getNumberOfZones() > 2 ? "ZONE2" : "ZONE"; - } else { - zone = String.format("ZONE%d", numZone); + String zone = model.getRespNbChars() == 11 ? " Z" : "ZONE"; + if (numZone != 2 || model.getNumberOfZones() > 2) { + zone += String.format("%d", numZone); } String text; if (mutes[numZone]) { text = zone + " MUTE ON"; - } else if (volumes[numZone] == minVolume) { - text = zone + " VOL MIN"; - } else if (volumes[numZone] == maxVolume) { - text = zone + " VOL MAX"; } else { - text = String.format("%s VOL %02d", zone, volumes[numZone]); + text = String.format("%s VOL %s", zone, buildVolumeValue(numZone)); + } + return text; + } + + private String buildVolumeValue(int numZone) { + String text; + if (volumes[numZone] == minVolume) { + text = "MIN"; + } else if (volumes[numZone] == maxVolume) { + text = "MAX"; + } else if (model.getRespNbChars() == 11) { + text = String.format(volumes[numZone] < 10 ? " %d" : " %d", volumes[numZone]); + } else { + text = String.format(" %02d", volumes[numZone]); } return text; } @@ -1568,15 +1579,15 @@ public class RotelSimuConnector extends RotelConnector { private String buildBassLine1Response() { String text; if (basses[0] == minToneLevel) { - text = " BASS MIN "; + text = model.getRespNbChars() == 11 ? " BASS MIN " : " BASS MIN "; } else if (basses[0] == maxToneLevel) { - text = " BASS MAX "; + text = model.getRespNbChars() == 11 ? " BASS MAX " : " BASS MAX "; } else if (basses[0] == 0) { - text = " BASS 0 "; + text = model.getRespNbChars() == 11 ? " BASS 0 " : " BASS 0 "; } else if (basses[0] > 0) { - text = String.format(" BASS +%02d ", basses[0]); + text = String.format(model.getRespNbChars() == 11 ? " BASS + %d " : " BASS +%02d ", basses[0]); } else { - text = String.format(" BASS -%02d ", -basses[0]); + text = String.format(model.getRespNbChars() == 11 ? " BASS - %d " : " BASS -%02d ", -basses[0]); } return text; } @@ -1600,15 +1611,15 @@ public class RotelSimuConnector extends RotelConnector { private String buildTrebleLine1Response() { String text; if (trebles[0] == minToneLevel) { - text = " TREBLE MIN "; + text = model.getRespNbChars() == 11 ? "TREBLE MIN " : " TREBLE MIN "; } else if (trebles[0] == maxToneLevel) { - text = " TREBLE MAX "; + text = model.getRespNbChars() == 11 ? "TREBLE MAX " : " TREBLE MAX "; } else if (trebles[0] == 0) { - text = " TREBLE 0 "; + text = model.getRespNbChars() == 11 ? "TREBLE 0 " : " TREBLE 0 "; } else if (trebles[0] > 0) { - text = String.format(" TREBLE +%02d ", trebles[0]); + text = String.format(model.getRespNbChars() == 11 ? "TREBLE + %d " : " TREBLE +%02d ", trebles[0]); } else { - text = String.format(" TREBLE -%02d ", -trebles[0]); + text = String.format(model.getRespNbChars() == 11 ? "TREBLE - %d " : " TREBLE -%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 dab2a9fd205..4a54a3ebb7d 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 @@ -310,7 +310,33 @@ public enum RotelSource { 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); + RotelCommand.ZONE2_SOURCE_INPUT_D, RotelCommand.ZONE3_SOURCE_INPUT_D, RotelCommand.ZONE4_SOURCE_INPUT_D), + + CAT22_CD(22, "CD", "CD", RotelCommand.SOURCE_CD, RotelCommand.RECORD_SOURCE_CD, RotelCommand.MAIN_ZONE_SOURCE_CD, + RotelCommand.ZONE2_SOURCE_CD, RotelCommand.ZONE3_SOURCE_CD, RotelCommand.ZONE4_SOURCE_CD), + CAT22_TUNER(22, "TUNER", "TUNER", RotelCommand.SOURCE_TUNER, RotelCommand.RECORD_SOURCE_TUNER, + RotelCommand.MAIN_ZONE_SOURCE_TUNER, RotelCommand.ZONE2_SOURCE_TUNER, RotelCommand.ZONE3_SOURCE_TUNER, + RotelCommand.ZONE4_SOURCE_TUNER), + CAT22_TAPE(22, "TAPE", "TAPE", RotelCommand.SOURCE_TAPE, RotelCommand.RECORD_SOURCE_TAPE, + RotelCommand.MAIN_ZONE_SOURCE_TAPE, RotelCommand.ZONE2_SOURCE_TAPE, RotelCommand.ZONE3_SOURCE_TAPE, + RotelCommand.ZONE4_SOURCE_TAPE), + CAT22_PHONO(22, "PHONO", "PHONO", RotelCommand.SOURCE_PHONO, RotelCommand.RECORD_SOURCE_PHONO, + RotelCommand.MAIN_ZONE_SOURCE_PHONO, RotelCommand.ZONE2_SOURCE_PHONO, RotelCommand.ZONE3_SOURCE_PHONO, + RotelCommand.ZONE4_SOURCE_PHONO), + CAT22_VIDEO1(22, "VIDEO1", "VIDEO 1", RotelCommand.SOURCE_VIDEO1, RotelCommand.RECORD_SOURCE_VIDEO1, + RotelCommand.MAIN_ZONE_SOURCE_VIDEO1, RotelCommand.ZONE2_SOURCE_VIDEO1, RotelCommand.ZONE3_SOURCE_VIDEO1, + RotelCommand.ZONE4_SOURCE_VIDEO1), + CAT22_VIDEO2(22, "VIDEO2", "VIDEO 2", RotelCommand.SOURCE_VIDEO2, RotelCommand.RECORD_SOURCE_VIDEO2, + RotelCommand.MAIN_ZONE_SOURCE_VIDEO2, RotelCommand.ZONE2_SOURCE_VIDEO2, RotelCommand.ZONE3_SOURCE_VIDEO2, + RotelCommand.ZONE4_SOURCE_VIDEO2), + CAT22_VIDEO3(22, "VIDEO3", "VIDEO 3", RotelCommand.SOURCE_VIDEO3, RotelCommand.RECORD_SOURCE_VIDEO3, + RotelCommand.MAIN_ZONE_SOURCE_VIDEO3, RotelCommand.ZONE2_SOURCE_VIDEO3, RotelCommand.ZONE3_SOURCE_VIDEO3, + RotelCommand.ZONE4_SOURCE_VIDEO3), + CAT22_VIDEO4(22, "VIDEO4", "VIDEO 4", RotelCommand.SOURCE_VIDEO4, RotelCommand.RECORD_SOURCE_VIDEO4, + RotelCommand.MAIN_ZONE_SOURCE_VIDEO4, RotelCommand.ZONE2_SOURCE_VIDEO4, RotelCommand.ZONE3_SOURCE_VIDEO4, + RotelCommand.ZONE4_SOURCE_VIDEO4), + CAT22_FOLLOW_MAIN(22, "MAIN", "Follow Main Zone Source", null, RotelCommand.RECORD_SOURCE_MAIN, null, + RotelCommand.ZONE2_SOURCE_MAIN, RotelCommand.ZONE3_SOURCE_MAIN, RotelCommand.ZONE4_SOURCE_MAIN); private int category; private String name; diff --git a/bundles/org.openhab.binding.rotel/src/main/java/org/openhab/binding/rotel/internal/configuration/RotelThingConfiguration.java b/bundles/org.openhab.binding.rotel/src/main/java/org/openhab/binding/rotel/internal/configuration/RotelThingConfiguration.java index a50ed0cfe58..a9542f5467e 100644 --- a/bundles/org.openhab.binding.rotel/src/main/java/org/openhab/binding/rotel/internal/configuration/RotelThingConfiguration.java +++ b/bundles/org.openhab.binding.rotel/src/main/java/org/openhab/binding/rotel/internal/configuration/RotelThingConfiguration.java @@ -13,6 +13,7 @@ package org.openhab.binding.rotel.internal.configuration; import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; /** * The {@link RotelThingConfiguration} class contains fields mapping thing configuration parameters. @@ -23,6 +24,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault; public class RotelThingConfiguration { public @NonNullByDefault({}) String serialPort; + public @Nullable Integer baudRate; public @NonNullByDefault({}) String host; public @NonNullByDefault({}) Integer port; public @NonNullByDefault({}) String inputLabelCd; 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 a980d64d8c2..3285a76d13c 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 @@ -90,6 +90,7 @@ public class RotelHandler extends BaseThingHandler implements RotelMessageEventL private @Nullable ScheduledFuture reconnectJob; private @Nullable ScheduledFuture powerOffJob; + private @Nullable ScheduledFuture powerZonesJob; private @Nullable ScheduledFuture[] powerOnZoneJobs = { null, null, null, null, null }; private RotelModel model; @@ -104,6 +105,7 @@ public class RotelHandler extends BaseThingHandler implements RotelMessageEventL private int currentZone = 1; private boolean selectingRecord; + private @Nullable Boolean powerZones; private @Nullable Boolean[] powers = { null, false, false, false, false }; private boolean powerControlPerZone; private @Nullable RotelSource recordSource; @@ -219,6 +221,9 @@ public class RotelHandler extends BaseThingHandler implements RotelMessageEventL case THING_TYPE_ID_RSX1562: model = RotelModel.RSX1562; break; + case THING_TYPE_ID_RX1052: + model = RotelModel.RX1052; + break; case THING_TYPE_ID_A11: model = RotelModel.A11; break; @@ -435,8 +440,9 @@ public class RotelHandler extends BaseThingHandler implements RotelMessageEventL if (USE_SIMULATED_DEVICE) { connector = new RotelSimuConnector(model, protocolHandler, sourcesLabels, readerThreadName); } else if (config.serialPort != null) { - connector = new RotelSerialConnector(serialPortManager, config.serialPort, model.getBaudRate(), - protocolHandler, readerThreadName); + connector = new RotelSerialConnector(serialPortManager, config.serialPort, + config.baudRate != null ? config.baudRate : model.getBaudRate(), protocolHandler, + readerThreadName); } else { connector = new RotelIpConnector(config.host, config.port, protocolHandler, readerThreadName); } @@ -493,6 +499,7 @@ public class RotelHandler extends BaseThingHandler implements RotelMessageEventL public void dispose() { logger.debug("Disposing handler for thing {}", getThing().getUID()); cancelPowerOffJob(); + cancelCheckPowerZonesJob(); for (int zone = 0; zone <= model.getNumberOfZones(); zone++) { cancelPowerOnZoneJob(zone); } @@ -946,6 +953,7 @@ public class RotelHandler extends BaseThingHandler implements RotelMessageEventL } break; case CHANNEL_SPEAKER_A: + case CHANNEL_MAIN_SPEAKER_A: if (!isPowerOn()) { success = false; logger.debug("Command {} from channel {} ignored: device in standby", command, channel); @@ -955,6 +963,7 @@ public class RotelHandler extends BaseThingHandler implements RotelMessageEventL } break; case CHANNEL_SPEAKER_B: + case CHANNEL_MAIN_SPEAKER_B: if (!isPowerOn()) { success = false; logger.debug("Command {} from channel {} ignored: device in standby", command, channel); @@ -1431,6 +1440,13 @@ public class RotelHandler extends BaseThingHandler implements RotelMessageEventL throw new RotelException("Invalid value"); } break; + case KEY_POWER_ZONES: + if (POWER_ON.equalsIgnoreCase(value) || STANDBY.equalsIgnoreCase(value)) { + handlePowerZones(POWER_ON.equalsIgnoreCase(value)); + } else { + throw new RotelException("Invalid value"); + } + break; case KEY_POWER_ZONE2: case KEY_POWER_ZONE3: case KEY_POWER_ZONE4: @@ -1747,21 +1763,29 @@ public class RotelHandler extends BaseThingHandler implements RotelMessageEventL speakerb = false; updateChannelState(CHANNEL_SPEAKER_A); updateChannelState(CHANNEL_SPEAKER_B); + updateChannelState(CHANNEL_MAIN_SPEAKER_A); + updateChannelState(CHANNEL_MAIN_SPEAKER_B); } else if (MSG_VALUE_SPEAKER_B.equalsIgnoreCase(value)) { speakera = false; speakerb = true; updateChannelState(CHANNEL_SPEAKER_A); updateChannelState(CHANNEL_SPEAKER_B); + updateChannelState(CHANNEL_MAIN_SPEAKER_A); + updateChannelState(CHANNEL_MAIN_SPEAKER_B); } else if (MSG_VALUE_SPEAKER_AB.equalsIgnoreCase(value)) { speakera = true; speakerb = true; updateChannelState(CHANNEL_SPEAKER_A); updateChannelState(CHANNEL_SPEAKER_B); + updateChannelState(CHANNEL_MAIN_SPEAKER_A); + updateChannelState(CHANNEL_MAIN_SPEAKER_B); } else if (MSG_VALUE_OFF.equalsIgnoreCase(value)) { speakera = false; speakerb = false; updateChannelState(CHANNEL_SPEAKER_A); updateChannelState(CHANNEL_SPEAKER_B); + updateChannelState(CHANNEL_MAIN_SPEAKER_A); + updateChannelState(CHANNEL_MAIN_SPEAKER_B); } else { throw new RotelException("Invalid value"); } @@ -1864,11 +1888,30 @@ public class RotelHandler extends BaseThingHandler implements RotelMessageEventL updateChannelState(CHANNEL_MAIN_MUTE); updateChannelState(CHANNEL_MAIN_BASS); updateChannelState(CHANNEL_MAIN_TREBLE); + updateChannelState(CHANNEL_MAIN_SPEAKER_A); + updateChannelState(CHANNEL_MAIN_SPEAKER_B); updateChannelState(CHANNEL_ALL_POWER); updateChannelState(CHANNEL_ALL_BRIGHTNESS); } + /** + * Handle the received information that at least one zone power is ON or all zones power is OFF + */ + private void handlePowerZones(boolean power) { + Boolean prev = powerZones; + powerZones = power; + if (prev == null && power) { + // We know that at least one zone is ON but we don't know which ones + scheduleCheckPowerZonesJob(); + } else if ((prev == null || prev.booleanValue() != power) && !power) { + cancelCheckPowerZonesJob(); + for (int zone = 1; zone <= model.getNumberOfZones(); zone++) { + handlePowerOffZone(zone); + } + } + } + /** * Handle the received information that a zone power is ON */ @@ -2178,6 +2221,41 @@ public class RotelHandler extends BaseThingHandler implements RotelMessageEventL } } + /** + * Schedule the job to run with a few seconds delay + */ + private void scheduleCheckPowerZonesJob() { + logger.debug("Schedule check power zones job"); + cancelCheckPowerZonesJob(); + powerZonesJob = scheduler.schedule(() -> { + synchronized (sequenceLock) { + logger.debug("Check power zones job"); + try { + selectZone(model.getNumberOfZones(), model.getZoneSelectCmd()); + } catch (RotelException e) { + logger.debug("Check power zones sequence failed: {}", e.getMessage()); + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, + "@text/offline.comm-error-check-power-zones-sequence"); + closeConnection(); + } catch (InterruptedException e) { + logger.debug("Check power zones sequence interrupted: {}", e.getMessage()); + Thread.currentThread().interrupt(); + } + } + }, 2500, TimeUnit.MILLISECONDS); + } + + /** + * Cancel the job scheduled when the device power (main zone) or a zone power switched ON + */ + private void cancelCheckPowerZonesJob() { + ScheduledFuture job = powerZonesJob; + if (job != null && !job.isCancelled()) { + job.cancel(true); + powerZonesJob = null; + } + } + /** * Schedule the reconnection job */ @@ -2443,11 +2521,13 @@ public class RotelHandler extends BaseThingHandler implements RotelMessageEventL } break; case CHANNEL_SPEAKER_A: + case CHANNEL_MAIN_SPEAKER_A: if (isPowerOn()) { state = OnOffType.from(speakera); } break; case CHANNEL_SPEAKER_B: + case CHANNEL_MAIN_SPEAKER_B: if (isPowerOn()) { state = OnOffType.from(speakerb); } @@ -2536,7 +2616,11 @@ public class RotelHandler extends BaseThingHandler implements RotelMessageEventL private RotelCommand getVolumeUpCommand(int numZone) { switch (numZone) { case 0: - return model.hasOtherThanPrimaryCommands() ? RotelCommand.MAIN_ZONE_VOLUME_UP : RotelCommand.VOLUME_UP; + // Spec for RX-1052 defines an unusual code for main zone volume up. + // An error in the spec is suspected. The general volume up code is preferred. + return (model.hasOtherThanPrimaryCommands() && model != RotelModel.RX1052) + ? RotelCommand.MAIN_ZONE_VOLUME_UP + : RotelCommand.VOLUME_UP; case 1: return RotelCommand.ZONE1_VOLUME_UP; case 2: @@ -2560,7 +2644,10 @@ public class RotelHandler extends BaseThingHandler implements RotelMessageEventL private RotelCommand getVolumeDownCommand(int numZone) { switch (numZone) { case 0: - return model.hasOtherThanPrimaryCommands() ? RotelCommand.MAIN_ZONE_VOLUME_DOWN + // Spec for RX-1052 defines an unusual code for main zone volume down. + // An error in the spec is suspected. The general volume down code is preferred. + return (model.hasOtherThanPrimaryCommands() && model != RotelModel.RX1052) + ? RotelCommand.MAIN_ZONE_VOLUME_DOWN : RotelCommand.VOLUME_DOWN; case 1: return RotelCommand.ZONE1_VOLUME_DOWN; 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 5f9ee4f436d..a87b81268f4 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 @@ -17,6 +17,7 @@ import static org.openhab.binding.rotel.internal.RotelBindingConstants.*; import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.Map; +import java.util.regex.Pattern; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; @@ -24,6 +25,7 @@ import org.openhab.binding.rotel.internal.RotelException; import org.openhab.binding.rotel.internal.RotelModel; import org.openhab.binding.rotel.internal.communication.RotelCommand; import org.openhab.binding.rotel.internal.communication.RotelDsp; +import org.openhab.binding.rotel.internal.communication.RotelFlagInfoType; import org.openhab.binding.rotel.internal.communication.RotelFlagsMapping; import org.openhab.binding.rotel.internal.communication.RotelSource; import org.openhab.binding.rotel.internal.protocol.RotelAbstractProtocolHandler; @@ -93,11 +95,17 @@ public class RotelHexProtocolHandler extends RotelAbstractProtocolHandler { private static final String KEY_HEX_BYPASS = "bypass"; private static final String KEY1_HEX_ZONE2 = "zone "; private static final String KEY2_HEX_ZONE2 = "zone2 "; + private static final String KEY3_HEX_ZONE2 = "z2 "; private static final String KEY_HEX_ZONE3 = "zone3 "; + private static final String KEY2_HEX_ZONE3 = "z3 "; private static final String KEY_HEX_ZONE4 = "zone4 "; + private static final String KEY2_HEX_ZONE4 = "z4 "; private static final String KEY_HEX_RECORD = "rec "; private static final String SOURCE = "source"; + private static final Pattern PATTERN_TUNER_FREQ_FM = Pattern.compile("\\d{2,3}[\\.,]\\d{1,2}M.*"); + private static final Pattern PATTERN_TUNER_FREQ_AM = Pattern.compile("\\d{3,4}K.*"); + private final Logger logger = LoggerFactory.getLogger(RotelHexProtocolHandler.class); private final Map sourcesLabels; @@ -325,25 +333,59 @@ public class RotelHexProtocolHandler extends RotelAbstractProtocolHandler { } } } - try { - dispatchKeyValue(KEY_POWER_ZONE2, model.isZone2On(flags) ? POWER_ON : STANDBY); - } catch (RotelException e1) { - // Can't get zone power information from flags data, so we just do not notify of this information that way + if (model.isInfoPresentInFlags(RotelFlagInfoType.ZONE2)) { + try { + dispatchKeyValue(KEY_POWER_ZONE2, + model.isInfoOnInFlags(RotelFlagInfoType.ZONE2, flags) ? POWER_ON : STANDBY); + } catch (RotelException e1) { + // Ignore it + } } - try { - dispatchKeyValue(KEY_POWER_ZONE3, model.isZone3On(flags) ? POWER_ON : STANDBY); - } catch (RotelException e1) { - // Can't get zone power information from flags data, so we just do not notify of this information that way + if (model.isInfoPresentInFlags(RotelFlagInfoType.ZONE3)) { + try { + dispatchKeyValue(KEY_POWER_ZONE3, + model.isInfoOnInFlags(RotelFlagInfoType.ZONE3, flags) ? POWER_ON : STANDBY); + } catch (RotelException e1) { + // Ignore it + } } - try { - dispatchKeyValue(KEY_POWER_ZONE4, model.isZone4On(flags) ? POWER_ON : STANDBY); - } catch (RotelException e1) { - // Can't get zone power information from flags data, so we just do not notify of this information that way + if (model.isInfoPresentInFlags(RotelFlagInfoType.ZONE4)) { + try { + dispatchKeyValue(KEY_POWER_ZONE4, + model.isInfoOnInFlags(RotelFlagInfoType.ZONE4, flags) ? POWER_ON : STANDBY); + } catch (RotelException e1) { + // Ignore it + } + } + if (model.isInfoPresentInFlags(RotelFlagInfoType.ZONE)) { + try { + dispatchKeyValue(KEY_POWER_ZONES, + model.isInfoOnInFlags(RotelFlagInfoType.ZONE, flags) ? POWER_ON : STANDBY); + } catch (RotelException e1) { + // Ignore it + } + } + if (model.isInfoPresentInFlags(RotelFlagInfoType.SPEAKER_A) + && model.isInfoPresentInFlags(RotelFlagInfoType.SPEAKER_B)) { + try { + String speakerValue = MSG_VALUE_OFF; + if (model.isInfoOnInFlags(RotelFlagInfoType.SPEAKER_A, flags) + && model.isInfoOnInFlags(RotelFlagInfoType.SPEAKER_B, flags)) { + speakerValue = MSG_VALUE_SPEAKER_AB; + } else if (model.isInfoOnInFlags(RotelFlagInfoType.SPEAKER_A, flags)) { + speakerValue = MSG_VALUE_SPEAKER_A; + } else if (model.isInfoOnInFlags(RotelFlagInfoType.SPEAKER_B, flags)) { + speakerValue = MSG_VALUE_SPEAKER_B; + } + dispatchKeyValue(KEY_SPEAKER, speakerValue); + } catch (RotelException e1) { + // Ignore it + } } boolean checkMultiIn = false; boolean checkSource = true; try { - if (model.isMultiInputOn(flags)) { + if (model.isInfoOnInFlags(RotelFlagInfoType.MULTI_INPUT, flags)) { checkSource = false; try { RotelSource source = model.getSourceFromName(RotelSource.CAT1_MULTI.getName()); @@ -373,8 +415,9 @@ public class RotelHexProtocolHandler extends RotelAbstractProtocolHandler { String valueLowerCase = value.trim().toLowerCase(); if (!valueLowerCase.isEmpty() && !valueLowerCase.startsWith(KEY1_HEX_ZONE2) - && !valueLowerCase.startsWith(KEY2_HEX_ZONE2) && !valueLowerCase.startsWith(KEY_HEX_ZONE3) - && !valueLowerCase.startsWith(KEY_HEX_ZONE4)) { + && !valueLowerCase.startsWith(KEY2_HEX_ZONE2) && !valueLowerCase.startsWith(KEY3_HEX_ZONE2) + && !valueLowerCase.startsWith(KEY_HEX_ZONE3) && !valueLowerCase.startsWith(KEY2_HEX_ZONE3) + && !valueLowerCase.startsWith(KEY_HEX_ZONE4) && !valueLowerCase.startsWith(KEY2_HEX_ZONE4)) { dispatchKeyValue(KEY_POWER, POWER_ON); } @@ -384,12 +427,12 @@ public class RotelHexProtocolHandler extends RotelAbstractProtocolHandler { // Line 1 left value = new String(incomingMessage, idxChars, 14, StandardCharsets.US_ASCII); logger.debug("handleValidHexMessage: line 1 left *{}*", value); - parseText(value, checkSource, checkMultiIn, false, false, false, false, false, true); + parseText(value, checkSource, checkMultiIn, false, false, false, false, false, false, true, false); // Line 1 right value = new String(incomingMessage, idxChars + 14, 7, StandardCharsets.US_ASCII); logger.debug("handleValidHexMessage: line 1 right *{}*", value); - parseText(value, false, false, false, false, false, false, false, true); + parseText(value, false, false, false, false, false, false, false, false, true, false); // Full line 1 value = new String(incomingMessage, idxChars, 21, StandardCharsets.US_ASCII); @@ -398,16 +441,18 @@ public class RotelHexProtocolHandler extends RotelAbstractProtocolHandler { // Line 2 right value = new String(incomingMessage, idxChars + 35, 7, StandardCharsets.US_ASCII); logger.debug("handleValidHexMessage: line 2 right *{}*", value); - parseText(value, false, false, false, false, false, false, false, true); + parseText(value, false, false, false, false, false, false, false, false, true, false); // Full line 2 value = new String(incomingMessage, idxChars + 21, 21, StandardCharsets.US_ASCII); logger.debug("handleValidHexMessage: line 2 *{}*", value); - parseText(value, false, false, true, true, false, true, true, true); + parseText(value, false, false, true, true, false, false, true, true, true, false); dispatchKeyValue(KEY_LINE2, value); } else { value = new String(incomingMessage, idxChars, model.getRespNbChars(), StandardCharsets.US_ASCII); - parseText(value, checkSource, checkMultiIn, true, false, true, true, checkStereo, false); + parseText(value, checkSource, checkMultiIn, true, model.getRespNbChars() == 11, + model.getRespNbChars() != 11, model.getRespNbChars() == 11, model.hasDspControl(), checkStereo, + false, model.getRespNbChars() == 11); dispatchKeyValue(KEY_LINE1, value); } @@ -424,25 +469,28 @@ public class RotelHexProtocolHandler extends RotelAbstractProtocolHandler { * @param searchMultiIn true if MULTI IN indication has to be searched in the text * @param searchZone true if a zone information has to be searched in the text * @param searchRecord true if a record source has to be searched in the text - * @param searchRecordAfterSource true if a record source has to be searched in the text after the a found source + * @param searchRecordAfterSource true if a record source has to be searched in the text after the found source + * @param searchVolumeAfterSource true if a volume value has to be searched in the text after the found source * @param searchDsp true if a DSP mode has to be searched in the text * @param searchStereo true if a STEREO has to be considered in the search * @param multipleInfo true if source and volume/mute are provided separately + * @param searchTunerFreq true if a tuner frequency has to be searched in the text */ private void parseText(String text, boolean searchSource, boolean searchMultiIn, boolean searchZone, - boolean searchRecord, boolean searchRecordAfterSource, boolean searchDsp, boolean searchStereo, - boolean multipleInfo) { + boolean searchRecord, boolean searchRecordAfterSource, boolean searchVolumeAfterSource, boolean searchDsp, + boolean searchStereo, boolean multipleInfo, boolean searchTunerFreq) { String value = text.trim(); String valueLowerCase = value.toLowerCase(); if (searchRecord) { dispatchKeyValue(KEY_RECORD_SEL, valueLowerCase.startsWith(KEY_HEX_RECORD) ? MSG_VALUE_ON : MSG_VALUE_OFF); } if (searchZone) { - if (valueLowerCase.startsWith(KEY1_HEX_ZONE2) || valueLowerCase.startsWith(KEY2_HEX_ZONE2)) { + if (valueLowerCase.startsWith(KEY1_HEX_ZONE2) || valueLowerCase.startsWith(KEY2_HEX_ZONE2) + || valueLowerCase.startsWith(KEY3_HEX_ZONE2)) { dispatchKeyValue(KEY_ZONE, "2"); - } else if (valueLowerCase.startsWith(KEY_HEX_ZONE3)) { + } else if (valueLowerCase.startsWith(KEY_HEX_ZONE3) || valueLowerCase.startsWith(KEY2_HEX_ZONE3)) { dispatchKeyValue(KEY_ZONE, "3"); - } else if (valueLowerCase.startsWith(KEY_HEX_ZONE4)) { + } else if (valueLowerCase.startsWith(KEY_HEX_ZONE4) || valueLowerCase.startsWith(KEY2_HEX_ZONE4)) { dispatchKeyValue(KEY_ZONE, "4"); } else { dispatchKeyValue(KEY_ZONE, "1"); @@ -555,19 +603,41 @@ public class RotelHexProtocolHandler extends RotelAbstractProtocolHandler { } else if (searchDsp && valueLowerCase.startsWith(KEY_HEX_MPEG)) { logger.debug("MPEG"); dispatchKeyValue(KEY_DSP_MODE, RotelDsp.CAT4_NONE.getFeedback()); - } else if (searchZone - && (valueLowerCase.startsWith(KEY1_HEX_ZONE2) || valueLowerCase.startsWith(KEY2_HEX_ZONE2))) { - value = value.substring( - valueLowerCase.startsWith(KEY1_HEX_ZONE2) ? KEY1_HEX_ZONE2.length() : KEY2_HEX_ZONE2.length()); - parseZone2(value, multipleInfo); + } else if (searchZone && valueLowerCase.startsWith(KEY1_HEX_ZONE2)) { + parseZone2(value.substring(KEY1_HEX_ZONE2.length()), multipleInfo); + } else if (searchZone && valueLowerCase.startsWith(KEY2_HEX_ZONE2)) { + parseZone2(value.substring(KEY2_HEX_ZONE2.length()), multipleInfo); + } else if (searchZone && valueLowerCase.startsWith(KEY3_HEX_ZONE2)) { + parseZone2(value.substring(KEY3_HEX_ZONE2.length()), multipleInfo); } else if (searchZone && valueLowerCase.startsWith(KEY_HEX_ZONE3)) { parseZone3(value.substring(KEY_HEX_ZONE3.length()), multipleInfo); + } else if (searchZone && valueLowerCase.startsWith(KEY2_HEX_ZONE3)) { + parseZone3(value.substring(KEY2_HEX_ZONE3.length()), multipleInfo); } else if (searchZone && valueLowerCase.startsWith(KEY_HEX_ZONE4)) { parseZone4(value.substring(KEY_HEX_ZONE4.length()), multipleInfo); + } else if (searchZone && valueLowerCase.startsWith(KEY2_HEX_ZONE4)) { + parseZone4(value.substring(KEY2_HEX_ZONE4.length()), multipleInfo); } else if (searchRecord && valueLowerCase.startsWith(KEY_HEX_RECORD)) { parseRecord(value.substring(KEY_HEX_RECORD.length())); - } else if (searchSource || searchRecordAfterSource) { - parseSourceAndRecord(value, searchSource, searchRecordAfterSource, multipleInfo); + } else if (searchSource && searchTunerFreq + && (PATTERN_TUNER_FREQ_FM.matcher(value).matches() || PATTERN_TUNER_FREQ_AM.matcher(value).matches())) { + try { + RotelSource source = model.getSourceFromName("TUNER"); + RotelCommand cmd = source.getCommand(); + if (cmd != null) { + String value2 = cmd.getAsciiCommandV2(); + if (value2 != null) { + dispatchKeyValue(KEY_SOURCE, value2); + if (!multipleInfo) { + dispatchKeyValue(KEY_MUTE, MSG_VALUE_OFF); + } + } + } + } catch (RotelException e) { + // Ignore it, no tuner source found for this model + } + } else if (searchSource || searchRecordAfterSource || searchVolumeAfterSource) { + parseSourceAndOther(value, searchSource, searchRecordAfterSource, searchVolumeAfterSource, multipleInfo); } } @@ -603,8 +673,8 @@ public class RotelHexProtocolHandler extends RotelAbstractProtocolHandler { return source; } - private void parseSourceAndRecord(String text, boolean searchSource, boolean searchRecordAfterSource, - boolean multipleInfo) { + private void parseSourceAndOther(String text, boolean searchSource, boolean searchRecordAfterSource, + boolean searchVolumeAfterSource, boolean multipleInfo) { RotelSource source = parseSource(text, false); if (source != null) { if (searchSource) { @@ -620,6 +690,16 @@ public class RotelHexProtocolHandler extends RotelAbstractProtocolHandler { } } + if (searchVolumeAfterSource) { + String value = extractNumber(text, getSourceLabel(source).length()); + if (!value.isEmpty()) { + dispatchKeyValue(KEY_VOLUME, value); + if (!searchSource && !multipleInfo) { + dispatchKeyValue(KEY_MUTE, MSG_VALUE_OFF); + } + } + } + if (searchRecordAfterSource) { String value = text.substring(getSourceLabel(source).length()).trim(); source = parseSource(value, true); @@ -659,6 +739,9 @@ public class RotelHexProtocolHandler extends RotelAbstractProtocolHandler { private void parseZone2(String text, boolean multipleInfo) { String value = text.trim(); + if (!model.isInfoPresentInFlags(RotelFlagInfoType.ZONE2)) { + dispatchKeyValue(KEY_POWER_ZONE2, MSG_VALUE_OFF.equalsIgnoreCase(value) ? STANDBY : MSG_VALUE_ON); + } String valueLowerCase = value.toLowerCase(); if (valueLowerCase.startsWith(KEY1_HEX_VOLUME) || valueLowerCase.startsWith(KEY2_HEX_VOLUME)) { value = extractNumber(value, @@ -693,6 +776,9 @@ public class RotelHexProtocolHandler extends RotelAbstractProtocolHandler { private void parseZone3(String text, boolean multipleInfo) { String value = text.trim(); + if (!model.isInfoPresentInFlags(RotelFlagInfoType.ZONE3)) { + dispatchKeyValue(KEY_POWER_ZONE3, MSG_VALUE_OFF.equalsIgnoreCase(value) ? STANDBY : MSG_VALUE_ON); + } String valueLowerCase = value.toLowerCase(); if (valueLowerCase.startsWith(KEY1_HEX_VOLUME) || valueLowerCase.startsWith(KEY2_HEX_VOLUME)) { value = extractNumber(value, @@ -727,6 +813,9 @@ public class RotelHexProtocolHandler extends RotelAbstractProtocolHandler { private void parseZone4(String text, boolean multipleInfo) { String value = text.trim(); + if (!model.isInfoPresentInFlags(RotelFlagInfoType.ZONE4)) { + dispatchKeyValue(KEY_POWER_ZONE4, MSG_VALUE_OFF.equalsIgnoreCase(value) ? STANDBY : MSG_VALUE_ON); + } String valueLowerCase = value.toLowerCase(); if (valueLowerCase.startsWith(KEY1_HEX_VOLUME) || valueLowerCase.startsWith(KEY2_HEX_VOLUME)) { value = extractNumber(value, diff --git a/bundles/org.openhab.binding.rotel/src/main/resources/OH-INF/config/config.xml b/bundles/org.openhab.binding.rotel/src/main/resources/OH-INF/config/config.xml index 197817913f2..1e25d49c024 100644 --- a/bundles/org.openhab.binding.rotel/src/main/resources/OH-INF/config/config.xml +++ b/bundles/org.openhab.binding.rotel/src/main/resources/OH-INF/config/config.xml @@ -320,4 +320,48 @@ + + + serial-port + false + + @text/config.serialPort.description + + + + @text/config.baudRate.description + true + + + + + 38400 + + + network-address + + @text/config.hostOverIp.description + + + + @text/config.portOverIp.description + + + + @text/config.inputLabelVideo1.description + + + + @text/config.inputLabelVideo2.description + + + + @text/config.inputLabelVideo3.description + + + + @text/config.inputLabelVideo4.description + + + 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 414a3f6e187..845a2b87257 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 @@ -89,6 +89,8 @@ thing-type.rotel.rt11.label = RT-11 Tuner thing-type.rotel.rt11.description = Connection to the Rotel RT-11 tuner thing-type.rotel.rt1570.label = RT-1570 Tuner thing-type.rotel.rt1570.description = Connection to the Rotel RT-1570 tuner +thing-type.rotel.rx1052.label = RX-1052 Stereo Receiver +thing-type.rotel.rx1052.description = Connection to the Rotel RX-1052 stereo receiver thing-type.rotel.s5.label = S5 Stereo Amplifier thing-type.rotel.s5.description = Connection to the Rotel Michi S5 stereo amplifier thing-type.rotel.t11.label = T11 Tuner @@ -144,6 +146,8 @@ channel-type.rotel.volumeUpDown.description = Increase or decrease the volume # thing type configuration +config.baudRate.label = Baud Rate +config.baudRate.description = Baud rate to use for connecting to the Rotel device. Select the highest baud rate if your black unit has at least serial number 090-6441001 or your silver unit has at least serial number 990-6441001. config.host.label = Address config.host.description = Host name or IP address of the Rotel device (IP connection) or the machine connected to the Rotel device (serial over IP) config.hostOverIp.label = Address @@ -428,6 +432,7 @@ offline.config-error-invalid-port = Invalid port configuration setting offline.config-error-invalid-serial-over-ip = Use host and port configuration settings for a serial over IP connection offline.comm-error-init-sequence = Init sequence failed offline.comm-error-init-sequence-zone = Init sequence zone {0} failed +offline.comm-error-check-power-zones-sequence = Check power zones sequence failed offline.comm-error-reading-thread = Reading thread ended offline.comm-error-sending-command = Sending command failed offline.comm-error-reconnection = Reconnection failed 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 e44c50f9ca0..ec9a2076f7f 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 @@ -90,6 +90,24 @@ + + + @text/channel-group.mainZone.description + + + + + + + + + + + + + + + @text/channel-group.zone2.description diff --git a/bundles/org.openhab.binding.rotel/src/main/resources/OH-INF/thing/rx1052.xml b/bundles/org.openhab.binding.rotel/src/main/resources/OH-INF/thing/rx1052.xml new file mode 100644 index 00000000000..959bfd23383 --- /dev/null +++ b/bundles/org.openhab.binding.rotel/src/main/resources/OH-INF/thing/rx1052.xml @@ -0,0 +1,26 @@ + + + + + + + Connection to the Rotel RX-1052 stereo receiver + + + + + + + + + + HEX + + + + + +