]> git.basschouten.com Git - openhab-addons.git/commitdiff
[monopriceaudio] Add support for additional amplifiers (#13936)
authormlobstein <michael.lobstein@gmail.com>
Thu, 11 May 2023 14:14:55 +0000 (09:14 -0500)
committerGitHub <noreply@github.com>
Thu, 11 May 2023 14:14:55 +0000 (16:14 +0200)
* Add support for additional models
* Clarify supported models and documentation
* Add notes for amps with built-in serial over IP
* Improve configuration validation and add i18n to status messages
* Remove default values for port configuration

Signed-off-by: Michael Lobstein <michael.lobstein@gmail.com>
21 files changed:
bundles/org.openhab.binding.monopriceaudio/README.md
bundles/org.openhab.binding.monopriceaudio/src/main/java/org/openhab/binding/monopriceaudio/internal/MonopriceAudioBindingConstants.java
bundles/org.openhab.binding.monopriceaudio/src/main/java/org/openhab/binding/monopriceaudio/internal/MonopriceAudioHandlerFactory.java
bundles/org.openhab.binding.monopriceaudio/src/main/java/org/openhab/binding/monopriceaudio/internal/communication/AmplifierModel.java [new file with mode: 0644]
bundles/org.openhab.binding.monopriceaudio/src/main/java/org/openhab/binding/monopriceaudio/internal/communication/MonopriceAudioCommand.java [deleted file]
bundles/org.openhab.binding.monopriceaudio/src/main/java/org/openhab/binding/monopriceaudio/internal/communication/MonopriceAudioConnector.java
bundles/org.openhab.binding.monopriceaudio/src/main/java/org/openhab/binding/monopriceaudio/internal/communication/MonopriceAudioDefaultConnector.java
bundles/org.openhab.binding.monopriceaudio/src/main/java/org/openhab/binding/monopriceaudio/internal/communication/MonopriceAudioIpConnector.java
bundles/org.openhab.binding.monopriceaudio/src/main/java/org/openhab/binding/monopriceaudio/internal/communication/MonopriceAudioReaderThread.java
bundles/org.openhab.binding.monopriceaudio/src/main/java/org/openhab/binding/monopriceaudio/internal/communication/MonopriceAudioSerialConnector.java
bundles/org.openhab.binding.monopriceaudio/src/main/java/org/openhab/binding/monopriceaudio/internal/communication/MonopriceAudioZone.java [deleted file]
bundles/org.openhab.binding.monopriceaudio/src/main/java/org/openhab/binding/monopriceaudio/internal/configuration/MonopriceAudioThingConfiguration.java
bundles/org.openhab.binding.monopriceaudio/src/main/java/org/openhab/binding/monopriceaudio/internal/dto/MonopriceAudioZoneDTO.java
bundles/org.openhab.binding.monopriceaudio/src/main/java/org/openhab/binding/monopriceaudio/internal/handler/MonopriceAudioHandler.java
bundles/org.openhab.binding.monopriceaudio/src/main/resources/OH-INF/addon/addon.xml
bundles/org.openhab.binding.monopriceaudio/src/main/resources/OH-INF/i18n/monopriceaudio.properties
bundles/org.openhab.binding.monopriceaudio/src/main/resources/OH-INF/thing/channels.xml [deleted file]
bundles/org.openhab.binding.monopriceaudio/src/main/resources/OH-INF/thing/dax88.xml [new file with mode: 0644]
bundles/org.openhab.binding.monopriceaudio/src/main/resources/OH-INF/thing/monoprice.xml [new file with mode: 0644]
bundles/org.openhab.binding.monopriceaudio/src/main/resources/OH-INF/thing/monoprice70.xml [new file with mode: 0644]
bundles/org.openhab.binding.monopriceaudio/src/main/resources/OH-INF/thing/xantech.xml [new file with mode: 0644]

index c8561f4bad778f76994bd063fd600731ec1808d9..f6a866fde77645e6580e59509acde9b0e9bec7e9 100644 (file)
 # Monoprice Whole House Audio Binding
 
-This binding can be used to control a Monoprice MPR-SG6Z (10761), Monoprice Passive Matrix (39261) & Dayton Audio DAX66 whole house multi-zone amplifier system.
-All amplifier functions available through the serial port interface can be controlled by the binding.
-Up to 18 zones can be controlled when 3 amps are connected together (if not all zones on the amp are used they can be excluded via configuration).
-Activating the 'Page All Zones' feature can only be done through the +12v trigger input on the back of the amplifier.
+This binding can be used to control the following types of whole house multi-zone amplifier systems:
+
+- Monoprice MPR-SG6Z (10761), Monoprice Passive Matrix (39261), Dayton Audio DAX66 or compatible clones
+- Monoprice 31028 or OSD Audio PAM1270 **(untested)**
+- Dayton Audio DAX88 **(untested)**
+- Xantech MRC88, MX88, MRAUDIO8X8 or CM8X8 **(untested)**
 
 The binding supports two different kinds of connections:
 
-- serial connection,
+- serial port connection
 - serial over IP connection
 
 For users without a serial port on the server side, you can use a USB to serial adapter.
 
 You don't need to have your whole house amplifier device directly connected to your openHAB server.
-Some newer versions of the amplifier have a built-in ethernet port that supports serial over IP.
+Some newer versions of the amplifier have a built-in Ethernet port that supports serial over IP.
 Or you can connect it for example to a Raspberry Pi and use [ser2net Linux tool](https://sourceforge.net/projects/ser2net/) to make the serial connection available on the LAN (serial over IP).
 
 ## Supported Things
 
-Monoprice 10761 & 39261 and Dayton Audio DAX66 Amplifiers use the `amplifier` thing id. Up to 18 zones with 3 linked amps, 6 source inputs.
-Note: Compatible clones (including 4 zone versions) from McLELLAND, Factor, Soundavo, etc. should work as well.
+Monoprice 10761 & 39261 or Dayton Audio DAX66 amplifiers use the `amplifier` thing id. Up to 18 zones with 3 linked amps and 6 source inputs are supported.
+Note: Compatible clones (including 4 zone versions) from McLELLAND, Factor, Soundavo, etc. should work as well.  
+
+***The following three thing types were implemented via available documentation only and have not been tested. Please open an issue for any bugs found when using these thing types.***  
+
+Monoprice 31028 or OSD Audio PAM1270 70 volt amplifiers use the `monoprice70` thing id. 6 zones per amp (not linkable) and 2 source inputs are supported.  
+
+Dayton Audio DAX88 amplifiers use the `dax88` thing id. 8 zones (2 un-amplified) per amp (not linkable) and 8 source inputs are supported.  
+
+Xantech MRC88, MX88, MRAUDIO8X8 or CM8X8 amplifiers use the `xantech` thing id. Up to 16 zones with 2 linked amps and 8 source inputs are supported.
+Some Xantech amps provide unsolicited zone updates for keypad actions and may work with the `disableKeypadPolling` option set to true which will prevent un-necessary polling of the amplifier. 
+Note: MRC44 amps do not support serial control.  
 
 ## Discovery
 
 Discovery is not supported.
 You have to add all things manually.
 
-## Binding Configuration
-
-There are no overall binding configuration settings that need to be set.
-All settings are through thing configuration parameters.
-
 ## Thing Configuration
 
-The thing has the following configuration parameters:
-
-| Parameter Label      | Parameter ID     | Description                                                                                                                    | Accepted values  |
-|----------------------|------------------|--------------------------------------------------------------------------------------------------------------------------------|------------------|
-| Serial Port          | serialPort       | Serial port to use for connecting to the Monoprice whole house amplifier device                                                | Serial port name |
-| Address              | host             | Host name or IP address of the amplifier or serial over IP device                                                              | Host name or IP  |
-| Port                 | port             | Communication port (default 8080 for newer amps with built-in serial over IP)                                                  | TCP port number  |
-| Number of Zones      | numZones         | (Optional) Number of amplifier zones to utilize in the binding (up to 18 zones with 3 amplifiers connected together)           | 1-18; default 6  |
-| Polling Interval     | pollingInterval  | (Optional) Configures how often (in seconds) to poll the amplifier to check for zone updates                                   | 5-60; default 15 |
-| Ignore Zones         | ignoreZones      | (Optional) A comma seperated list of Zone numbers that will ignore the 'All Zone' (except All Off) commands                    | ie: "1,6,10"     |
-| Initial All Volume   | initialAllVolume | (Optional) When 'All' zones are activated, the volume will reset to this value to prevent excessive blaring of sound ;)        | 1-30; default 10 |
-| Source 1 Input Label | inputLabel1      | (Optional) Friendly name for the input source to be displayed in the UI (ie: Chromecast, Radio, CD, etc.) (default "Source 1") | A free text name |
-| Source 2 Input Label | inputLabel2      | (Optional) Friendly name for the input source to be displayed in the UI (ie: Chromecast, Radio, CD, etc.) (default "Source 2") | A free text name |
-| Source 3 Input Label | inputLabel3      | (Optional) Friendly name for the input source to be displayed in the UI (ie: Chromecast, Radio, CD, etc.) (default "Source 3") | A free text name |
-| Source 4 Input Label | inputLabel4      | (Optional) Friendly name for the input source to be displayed in the UI (ie: Chromecast, Radio, CD, etc.) (default "Source 4") | A free text name |
-| Source 5 Input Label | inputLabel5      | (Optional) Friendly name for the input source to be displayed in the UI (ie: Chromecast, Radio, CD, etc.) (default "Source 5") | A free text name |
-| Source 6 Input Label | inputLabel6      | (Optional) Friendly name for the input source to be displayed in the UI (ie: Chromecast, Radio, CD, etc.) (default "Source 6") | A free text name |
+The thing has the following configuration parameters (number of sources and zones is amplifier dependent):
+
+| Parameter Label        | Parameter ID         | Description                                                                                                                    | Accepted values  |
+|------------------------|----------------------|--------------------------------------------------------------------------------------------------------------------------------|------------------|
+| Serial Port            | serialPort           | Serial port to use for connecting to the whole house amplifier device                                                          | Serial port name |
+| Address                | host                 | Host name or IP address of the amplifier or serial over IP device                                                              | Host name or IP  |
+| Port                   | port                 | Communication port (8080 for newer amps with built-in serial over IP)                                                          | TCP port number  |
+| Number of Zones        | numZones             | (Optional) Number of amplifier zones to utilize in the binding (See Supported Things for max number of zones per Thing type)   | 1-18; default 6  |
+| Polling Interval       | pollingInterval      | (Optional) Configures how often (in seconds) to poll the amplifier to check for zone updates                                   | 5-60; default 15 |
+| Ignore Zones           | ignoreZones          | (Optional) A comma separated list of Zone numbers that will ignore the 'All Zone' (except All Off) commands                    | ie: "1,6,10"     |
+| Initial All Volume     | initialAllVolume     | (Optional) When 'All' zones are activated, the volume will reset to this value to prevent excessive blaring of sound ;)        | 1-30; default 10 |
+| Source 1 Input Label   | inputLabel1          | (Optional) Friendly name for the input source to be displayed in the UI (ie: Chromecast, Radio, CD, etc.) (default "Source 1") | A free text name |
+| Source 2 Input Label   | inputLabel2          | (Optional) Friendly name for the input source to be displayed in the UI (ie: Chromecast, Radio, CD, etc.) (default "Source 2") | A free text name |
+| Source 3 Input Label   | inputLabel3          | (Optional) Friendly name for the input source to be displayed in the UI (ie: Chromecast, Radio, CD, etc.) (default "Source 3") | A free text name |
+| Source 4 Input Label   | inputLabel4          | (Optional) Friendly name for the input source to be displayed in the UI (ie: Chromecast, Radio, CD, etc.) (default "Source 4") | A free text name |
+| Source 5 Input Label   | inputLabel5          | (Optional) Friendly name for the input source to be displayed in the UI (ie: Chromecast, Radio, CD, etc.) (default "Source 5") | A free text name |
+| Source 6 Input Label   | inputLabel6          | (Optional) Friendly name for the input source to be displayed in the UI (ie: Chromecast, Radio, CD, etc.) (default "Source 6") | A free text name |
+| Source 7 Input Label   | inputLabel7          | (Optional) Friendly name for the input source to be displayed in the UI (ie: Chromecast, Radio, CD, etc.) (default "Source 7") | A free text name |
+| Source 8 Input Label   | inputLabel8          | (Optional) Friendly name for the input source to be displayed in the UI (ie: Chromecast, Radio, CD, etc.) (default "Source 8") | A free text name |
+| Disable Keypad Polling | disableKeypadPolling | Set to **true** if physical keypads are not used so the binding will not needlessly poll the amplifier zones for changes       | true or false    |
 
 Some notes:
 
+- On the 10761/DAX66 amp, activating the 'Page All Zones' feature can only be done through the +12v trigger input on the back of the amplifier.
+
 - On Linux, you may get an error stating the serial port cannot be opened when the MonopriceAudio 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`.
 - Also on Linux you may have issues with the USB if using two serial USB devices e.g. MonopriceAudio and RFXcom.
 - See the [general documentation about serial port configuration](/docs/administration/serial.html) for more on symlinking the USB ports.
-- Here is an example of ser2net.conf you can use to share your serial port /dev/ttyUSB0 on IP port 8080 using [ser2net Linux tool](https://sourceforge.net/projects/ser2net/):
+- Here is an example of ser2net.conf (for ser2net version < 4) you can use to share your serial port /dev/ttyUSB0 on IP port 8080 using [ser2net Linux tool](https://sourceforge.net/projects/ser2net/):
 
 ```text
 8080:raw:0:/dev/ttyUSB0:9600 8DATABITS NONE 1STOPBIT LOCAL
 ```
 
+- Here is an example of ser2net.yaml (for ser2net version >= 4) you can use to share your serial port /dev/ttyUSB0 on IP port 8080 using [ser2net Linux tool](https://sourceforge.net/projects/ser2net/):
+
+```yaml
+connection: &conMono
+    accepter: tcp,8080
+    enable: on
+    options:
+      kickolduser: true
+    connector: serialdev,
+              /dev/ttyUSB0,
+              9600n81,local
+```
+
 ## Channels
 
 The following channels are available:
-
-| Channel ID                    | Item Type | Description                                                                                                   |
-|-------------------------------|-----------|---------------------------------------------------------------------------------------------------------------|
-| all#allpower                  | Switch    | Turn all zones on or off simultaneously (those specified by the ignoreZones config option will not turn on)   |
-| all#allsource                 | Number    | Select the input source for all zones simultaneously (1-6) (except ignoreZones)                               |
-| all#allvolume                 | Dimmer    | Control the volume for all zones simultaneously (0-100%) [translates to 0-38] (except ignoreZones)            |
-| all#allmute                   | Switch    | Mute or unmute all zones simultaneously (except ignoreZones)                                                  |
-| zoneN#power (where N= 1-18)   | Switch    | Turn the power for a zone on or off                                                                           |
-| zoneN#source (where N= 1-18)  | Number    | Select the input source for a zone (1-6)                                                                      |
-| zoneN#volume (where N= 1-18)  | Dimmer    | Control the volume for a zone (0-100%) [translates to 0-38]                                                   |
-| zoneN#mute (where N= 1-18)    | Switch    | Mute or unmute a zone                                                                                         |
-| zoneN#treble (where N= 1-18)  | Number    | Adjust the treble control for a zone (-7 to 7) -7=none, 0=flat, 7=full                                        |
-| zoneN#bass (where N= 1-18)    | Number    | Adjust the bass control for a zone (-7 to 7) -7=none, 0=flat, 7=full                                          |
-| zoneN#balance (where N= 1-18) | Number    | Adjust the balance control for a zone (-10 to 10) -10=left, 0=center, 10=right                                |
-| zoneN#dnd (where N= 1-18)     | Switch    | Turn on or off the Do Not Disturb for the zone (for when the amplifier's external page trigger is activated)  |
-| zoneN#page (where N= 1-18)    | Contact   | Indicates if the page input is activated for the zone                                                         |
-| zoneN#keypad (where N= 1-18)  | Contact   | Indicates if the physical keypad is attached to a zone                                                        |
+Note that `dnd`, `page` and `keypad` are not available on all thing types.
+
+| Channel ID                    | Item Type | Description                                                                                                                           |
+|-------------------------------|-----------|---------------------------------------------------------------------------------------------------------------------------------------|
+| all#allpower                  | Switch    | Turn all zones on or off simultaneously (those specified by the ignoreZones config option will not turn on)                           |
+| all#allsource                 | Number    | Select the input source for all zones simultaneously (1-8) [number of sources is amplifier dependent] (except ignoreZones)            |
+| all#allvolume                 | Dimmer    | Control the volume for all zones simultaneously (0-100%) [translates to the particular amplifier's volume range] (except ignoreZones) |
+| all#allmute                   | Switch    | Mute or unmute all zones simultaneously (except ignoreZones)                                                                          |
+| zoneN#power (where N= 1-18)   | Switch    | Turn the power for a zone on or off                                                                                                   |
+| zoneN#source (where N= 1-18)  | Number    | Select the input source for a zone (1-8) [number of sources is amplifier dependent]                                                   |
+| zoneN#volume (where N= 1-18)  | Dimmer    | Control the volume for a zone (0-100%) [translates to the particular amplifier's volume range]                                        |
+| zoneN#mute (where N= 1-18)    | Switch    | Mute or unmute a zone                                                                                                                 |
+| zoneN#treble (where N= 1-18)  | Number    | Adjust the treble control for a zone [range is amplifier dependent]                                                                   |
+| zoneN#bass (where N= 1-18)    | Number    | Adjust the bass control for a zone [range is amplifier dependent]                                                                     |
+| zoneN#balance (where N= 1-18) | Number    | Adjust the balance control for a zone [0=center, range is amplifier dependent]                                                        |
+| zoneN#dnd (where N= 1-18)     | Switch    | Turn on or off the Do Not Disturb for the zone (for when the amplifier's external page trigger is activated)                          |
+| zoneN#page (where N= 1-18)    | Contact   | Indicates if the page input is activated for the zone                                                                                 |
+| zoneN#keypad (where N= 1-18)  | Contact   | Indicates if the physical keypad is attached to a zone                                                                                |
 
 ## Full Example
 
 monoprice.things:
 
 ```java
-// serial port connection
-monopriceaudio:amplifier:myamp "Monoprice WHA" [ serialPort="COM5", pollingInterval=15, numZones=6, inputLabel1="Chromecast", inputLabel2="Radio", inputLabel3="CD Player", inputLabel4="Bluetooth Audio", inputLabel5="HTPC", inputLabel6="Phono"]
+// Monoprice 10761, 39261 / DAX66 (serial port connection)
+monopriceaudio:amplifier:myamp "Monoprice WHA" [ serialPort="COM5", pollingInterval=15, numZones=6, inputLabel1="Chromecast", inputLabel2="Radio", inputLabel3="CD Player", inputLabel4="Bluetooth Audio", inputLabel5="HTPC", inputLabel6="Phono" ]
+
+// Monoprice 10761, 39261 / DAX66 (serial over IP connection)
+monopriceaudio:amplifier:myamp "Monoprice WHA" [ host="192.168.0.10", port=8080, pollingInterval=15, numZones=6, inputLabel1="Chromecast", inputLabel2="Radio", inputLabel3="CD Player", inputLabel4="Bluetooth Audio", inputLabel5="HTPC", inputLabel6="Phono" ]
 
-// serial over IP connection
-monopriceaudio:amplifier:myamp "Monoprice WHA" [ host="192.168.0.10", port=8080, pollingInterval=15, numZones=6, inputLabel1="Chromecast", inputLabel2="Radio", inputLabel3="CD Player", inputLabel4="Bluetooth Audio", inputLabel5="HTPC", inputLabel6="Phono"]
+// Monoprice 31028 or OSD Audio PAM1270
+monopriceaudio:monoprice70:myamp "Monoprice WHA" [ serialPort="COM5", pollingInterval=30, numZones=6, inputLabel1="Source 0 - Bus", inputLabel2="Source 1 - Line" ]
 
+// Dayton DAX88
+monopriceaudio:dax88:myamp "Dayton WHA" [ serialPort="COM5", pollingInterval=15, numZones=8, inputLabel1="Chromecast", inputLabel2="Radio", inputLabel3="CD Player", inputLabel4="Bluetooth Audio", inputLabel5="HTPC", inputLabel6="Phono", inputLabel7="Ipod", inputLabel8="Streaming" ]
+
+// Xantech 8x8
+monopriceaudio:xantech:myamp "Xantech WHA" [ serialPort="COM5", pollingInterval=30, numZones=8, inputLabel1="Chromecast", inputLabel2="Radio", inputLabel3="CD Player", inputLabel4="Bluetooth Audio", inputLabel5="HTPC", inputLabel6="Phono", inputLabel7="Ipod", inputLabel8="Sirius" ]
+
+// Note that host and port can be used with any of the thing types to connect as serial over IP
 ```
 
 monoprice.items:
 
 ```java
+// substitute 'amplifier' for the appropriate thing id if using 31028, DAX88 or Xantech amplifier
+
 Switch all_allpower "All Zones Power" { channel="monopriceaudio:amplifier:myamp:all#allpower" }
 Number all_source "Source Input [%s]" { channel="monopriceaudio:amplifier:myamp:all#allsource" }
 Dimmer all_volume "Volume [%d %%]" { channel="monopriceaudio:amplifier:myamp:all#allvolume" }
@@ -116,7 +154,7 @@ Switch z1_dnd "Do Not Disturb" { channel="monopriceaudio:amplifier:myamp:zone1#d
 Switch z1_page "Page Active: [%s]" { channel="monopriceaudio:amplifier:myamp:zone1#page" }
 Switch z1_keypad "Keypad Connected: [%s]" { channel="monopriceaudio:amplifier:myamp:zone1#keypad" }
 
-// repeat for zones 2-18 (substitute z1 and zone1)
+// repeat for total number of zones used (substitute z1 and zone1)
 ```
 
 monoprice.sitemap:
@@ -137,6 +175,7 @@ sitemap monoprice label="Audio Control" {
         // Volume can be a Slider also
         Setpoint item=z1_volume minValue=0 maxValue=100 step=1 visibility=[z1_power==ON]
         Switch item=z1_mute visibility=[z1_power==ON]
+        // Min and Max values are for the 10761 amp, adjust if using a different model
         Setpoint item=z1_treble label="Treble Adjustment [%d]" minValue=-7 maxValue=7 step=1 visibility=[z1_power==ON]
         Setpoint item=z1_bass label="Bass Adjustment [%d]" minValue=-7 maxValue=7 step=1 visibility=[z1_power==ON]
         Setpoint item=z1_balance label="Balance Adjustment [%d]" minValue=-10 maxValue=10 step=1 visibility=[z1_power==ON]
@@ -145,6 +184,6 @@ sitemap monoprice label="Audio Control" {
         Text item=z1_keypad label="Keypad Connected: [%s]" visibility=[z1_power==ON]
     }
 
-    // repeat for zones 2-18 (substitute z1)
+    // repeat for total number of zones used (substitute z1)
 }
 ```
index 06321889719ef1a0ed9ad4fb731ee0573cc11ab8..2f08c00de325a14ce69501d6a1ffaef7168f9ba4 100644 (file)
@@ -20,6 +20,7 @@ import org.openhab.core.thing.ThingTypeUID;
  * used across the whole binding.
  *
  * @author Michael Lobstein - Initial contribution
+ * @author Michael Lobstein - Add support for additional amplifier types
  */
 @NonNullByDefault
 public class MonopriceAudioBindingConstants {
@@ -27,7 +28,11 @@ public class MonopriceAudioBindingConstants {
     public static final String BINDING_ID = "monopriceaudio";
 
     // List of all Thing Type UIDs
-    public static final ThingTypeUID THING_TYPE_AMP = new ThingTypeUID(BINDING_ID, "amplifier");
+    // to avoid breaking existing installations, the 10761/DAX66 will still be known as 'amplifier'
+    public static final ThingTypeUID THING_TYPE_MP = new ThingTypeUID(BINDING_ID, "amplifier");
+    public static final ThingTypeUID THING_TYPE_MP70 = new ThingTypeUID(BINDING_ID, "monoprice70");
+    public static final ThingTypeUID THING_TYPE_DAX88 = new ThingTypeUID(BINDING_ID, "dax88");
+    public static final ThingTypeUID THING_TYPE_XT = new ThingTypeUID(BINDING_ID, "xantech");
 
     // List of all Channel types
     public static final String CHANNEL_TYPE_POWER = "power";
@@ -44,4 +49,10 @@ public class MonopriceAudioBindingConstants {
     public static final String CHANNEL_TYPE_ALLSOURCE = "allsource";
     public static final String CHANNEL_TYPE_ALLVOLUME = "allvolume";
     public static final String CHANNEL_TYPE_ALLMUTE = "allmute";
+
+    // misc
+    public static final String ONE = "1";
+    public static final String ZERO = "0";
+    public static final String EMPTY = "";
+    public static final int NIL = -1;
 }
index df6a58edd482afe13fae9d7d9c3c994bebbe5b9d..b8e91bd8bec63660624256426ab24d1e4e8944b7 100644 (file)
@@ -14,11 +14,11 @@ package org.openhab.binding.monopriceaudio.internal;
 
 import static org.openhab.binding.monopriceaudio.internal.MonopriceAudioBindingConstants.*;
 
-import java.util.Collections;
 import java.util.Set;
 
 import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.monopriceaudio.internal.communication.AmplifierModel;
 import org.openhab.binding.monopriceaudio.internal.handler.MonopriceAudioHandler;
 import org.openhab.core.io.transport.serial.SerialPortManager;
 import org.openhab.core.thing.Thing;
@@ -29,23 +29,29 @@ import org.openhab.core.thing.binding.ThingHandlerFactory;
 import org.osgi.service.component.annotations.Activate;
 import org.osgi.service.component.annotations.Component;
 import org.osgi.service.component.annotations.Reference;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 /**
  * The {@link MonopriceAudioHandlerFactory} is responsible for creating things and thing
  * handlers.
  *
  * @author Michael Lobstein - Initial contribution
+ * @author Michael Lobstein - Add support for additional amplifier types
  */
 @NonNullByDefault
 @Component(configurationPid = "binding.monopriceaudio", service = ThingHandlerFactory.class)
 public class MonopriceAudioHandlerFactory extends BaseThingHandlerFactory {
 
-    private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Collections.singleton(THING_TYPE_AMP);
+    private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_MP, THING_TYPE_MP70,
+            THING_TYPE_DAX88, THING_TYPE_XT);
 
     private final SerialPortManager serialPortManager;
 
     private final MonopriceAudioStateDescriptionOptionProvider stateDescriptionProvider;
 
+    private final Logger logger = LoggerFactory.getLogger(MonopriceAudioHandlerFactory.class);
+
     @Activate
     public MonopriceAudioHandlerFactory(final @Reference MonopriceAudioStateDescriptionOptionProvider provider,
             final @Reference SerialPortManager serialPortManager) {
@@ -62,10 +68,26 @@ public class MonopriceAudioHandlerFactory extends BaseThingHandlerFactory {
     protected @Nullable ThingHandler createHandler(Thing thing) {
         ThingTypeUID thingTypeUID = thing.getThingTypeUID();
 
-        if (SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID)) {
-            return new MonopriceAudioHandler(thing, stateDescriptionProvider, serialPortManager);
+        if (THING_TYPE_MP.equals(thingTypeUID)) {
+            return new MonopriceAudioHandler(thing, AmplifierModel.MONOPRICE, stateDescriptionProvider,
+                    serialPortManager);
+        }
+
+        if (THING_TYPE_MP70.equals(thingTypeUID)) {
+            return new MonopriceAudioHandler(thing, AmplifierModel.MONOPRICE70, stateDescriptionProvider,
+                    serialPortManager);
+        }
+
+        if (THING_TYPE_DAX88.equals(thingTypeUID)) {
+            return new MonopriceAudioHandler(thing, AmplifierModel.DAX88, stateDescriptionProvider, serialPortManager);
+        }
+
+        if (THING_TYPE_XT.equals(thingTypeUID)) {
+            return new MonopriceAudioHandler(thing, AmplifierModel.XANTECH, stateDescriptionProvider,
+                    serialPortManager);
         }
 
+        logger.warn("Unknown thing type: {}: {}", thingTypeUID.getId(), thingTypeUID.getBindingId());
         return null;
     }
 }
diff --git a/bundles/org.openhab.binding.monopriceaudio/src/main/java/org/openhab/binding/monopriceaudio/internal/communication/AmplifierModel.java b/bundles/org.openhab.binding.monopriceaudio/src/main/java/org/openhab/binding/monopriceaudio/internal/communication/AmplifierModel.java
new file mode 100644 (file)
index 0000000..6fc2dc6
--- /dev/null
@@ -0,0 +1,388 @@
+/**
+ * Copyright (c) 2010-2023 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.monopriceaudio.internal.communication;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.monopriceaudio.internal.configuration.MonopriceAudioThingConfiguration;
+import org.openhab.binding.monopriceaudio.internal.dto.MonopriceAudioZoneDTO;
+import org.openhab.core.types.StateOption;
+
+/**
+ * The {@link AmplifierModel} is responsible for mapping low level communications for each supported amplifier model.
+ *
+ * @author Michael Lobstein - Initial contribution
+ */
+@NonNullByDefault
+public enum AmplifierModel {
+
+    // Monoprice 10761/Dayton Audio DAX66
+    MONOPRICE("<", "\r", "?", "", "#>", "PR", "CH", "VO", "MU", "TR", "BS", "BL", "DT", 38, -7, 7, 7, -10, 10, 10, 18,
+            6, true, List.of("11", "12", "13", "14", "15", "16", "21", "22", "23", "24", "25", "26", "31", "32", "33",
+                    "34", "35", "36")) {
+        @Override
+        public MonopriceAudioZoneDTO getZoneData(String newZoneData) {
+            return getMonopriceZoneData(newZoneData);
+        }
+
+        @Override
+        public List<StateOption> getSourceLabels(MonopriceAudioThingConfiguration config) {
+            return List.of(new StateOption("1", config.inputLabel1), new StateOption("2", config.inputLabel2),
+                    new StateOption("3", config.inputLabel3), new StateOption("4", config.inputLabel4),
+                    new StateOption("5", config.inputLabel5), new StateOption("6", config.inputLabel6));
+        }
+    },
+    // Dayton Audio DAX88
+    DAX88("<", "\r", "?", "", ">", "PR", "CH", "VO", "MU", "TR", "BS", "BL", "DT", 38, -12, 12, 12, -10, 10, 10, 8, 8,
+            true, List.of("01", "02", "03", "04", "05", "06", "07", "08")) {
+        @Override
+        public MonopriceAudioZoneDTO getZoneData(String newZoneData) {
+            return getMonopriceZoneData(newZoneData);
+        }
+
+        @Override
+        public List<StateOption> getSourceLabels(MonopriceAudioThingConfiguration config) {
+            return List.of(new StateOption("1", config.inputLabel1), new StateOption("2", config.inputLabel2),
+                    new StateOption("3", config.inputLabel3), new StateOption("4", config.inputLabel4),
+                    new StateOption("5", config.inputLabel5), new StateOption("6", config.inputLabel6),
+                    new StateOption("7", config.inputLabel7), new StateOption("8", config.inputLabel8));
+        }
+    },
+    MONOPRICE70("!", "+\r", "?", "ZS", "?", "PR", "IS", "VO", "MU", "TR", "BS", "BA", "", 38, -7, 7, 7, -32, 31, 32, 6,
+            2, false, List.of("1", "2", "3", "4", "5", "6")) {
+        @Override
+        public MonopriceAudioZoneDTO getZoneData(String newZoneData) {
+            MonopriceAudioZoneDTO zoneData = new MonopriceAudioZoneDTO();
+
+            Matcher matcher = MONOPRICE70_PATTERN.matcher(newZoneData);
+            if (matcher.find()) {
+                zoneData.setZone(matcher.group(1));
+                zoneData.setVolume(Integer.parseInt(matcher.group(2)));
+                zoneData.setPower(matcher.group(3));
+                zoneData.setMute(matcher.group(4));
+                zoneData.setSource(matcher.group(5));
+                return zoneData;
+            }
+
+            matcher = MONOPRICE70_TREB_PATTERN.matcher(newZoneData);
+            if (matcher.find()) {
+                zoneData.setZone(matcher.group(1));
+                zoneData.setTreble(Integer.parseInt(matcher.group(2)));
+                return zoneData;
+            }
+
+            matcher = MONOPRICE70_BASS_PATTERN.matcher(newZoneData);
+            if (matcher.find()) {
+                zoneData.setZone(matcher.group(1));
+                zoneData.setBass(Integer.parseInt(matcher.group(2)));
+                return zoneData;
+            }
+
+            matcher = MONOPRICE70_BALN_PATTERN.matcher(newZoneData);
+            if (matcher.find()) {
+                zoneData.setZone(matcher.group(1));
+                zoneData.setBalance(Integer.parseInt(matcher.group(2)));
+                return zoneData;
+            }
+
+            return zoneData;
+        }
+
+        @Override
+        public List<StateOption> getSourceLabels(MonopriceAudioThingConfiguration config) {
+            return List.of(new StateOption("0", config.inputLabel1), new StateOption("1", config.inputLabel2));
+        }
+    },
+    XANTECH("!", "+\r", "?", "ZD", "#", "PR", "SS", "VO", "MU", "TR", "BS", "BL", "", 38, -7, 7, 7, -32, 31, 32, 16, 8,
+            false, List.of("1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16")) {
+        @Override
+        public MonopriceAudioZoneDTO getZoneData(String newZoneData) {
+            MonopriceAudioZoneDTO zoneData = new MonopriceAudioZoneDTO();
+            Matcher matcher = XANTECH_PATTERN.matcher(newZoneData);
+
+            if (matcher.find()) {
+                zoneData.setZone(matcher.group(1));
+                zoneData.setPower(matcher.group(2));
+                zoneData.setSource(matcher.group(3));
+                zoneData.setVolume(Integer.parseInt(matcher.group(4)));
+                zoneData.setMute(matcher.group(5));
+                zoneData.setTreble(Integer.parseInt(matcher.group(6)));
+                zoneData.setBass(Integer.parseInt(matcher.group(7)));
+                zoneData.setBalance(Integer.parseInt(matcher.group(8)));
+                zoneData.setKeypad(matcher.group(9));
+                zoneData.setPage(matcher.group(10));
+            }
+            return zoneData;
+        }
+
+        @Override
+        public List<StateOption> getSourceLabels(MonopriceAudioThingConfiguration config) {
+            return List.of(new StateOption("1", config.inputLabel1), new StateOption("2", config.inputLabel2),
+                    new StateOption("3", config.inputLabel3), new StateOption("4", config.inputLabel4),
+                    new StateOption("5", config.inputLabel5), new StateOption("6", config.inputLabel6),
+                    new StateOption("7", config.inputLabel7), new StateOption("8", config.inputLabel8));
+        }
+    };
+
+    // Used by 10761/DAX66 and DAX88
+    private static MonopriceAudioZoneDTO getMonopriceZoneData(String newZoneData) {
+        MonopriceAudioZoneDTO zoneData = new MonopriceAudioZoneDTO();
+        Matcher matcher = MONOPRICE_PATTERN.matcher(newZoneData);
+
+        if (matcher.find()) {
+            zoneData.setZone(matcher.group(1));
+            zoneData.setPage(matcher.group(2));
+            zoneData.setPower(matcher.group(3));
+            zoneData.setMute(matcher.group(4));
+            zoneData.setDnd(matcher.group(5));
+            zoneData.setVolume(Integer.parseInt(matcher.group(6)));
+            zoneData.setTreble(Integer.parseInt(matcher.group(7)));
+            zoneData.setBass(Integer.parseInt(matcher.group(8)));
+            zoneData.setBalance(Integer.parseInt(matcher.group(9)));
+            zoneData.setSource(matcher.group(10));
+            zoneData.setKeypad(matcher.group(11));
+        }
+        return zoneData;
+    }
+
+    // Monoprice 10761/DAX66 status string: #>1200010000130809100601
+    // DAX88 status string is the same but does not have leading '#': >xxaabbccddeeffgghhiijj
+    private static final Pattern MONOPRICE_PATTERN = Pattern
+            .compile("^#?>(\\d{2})(\\d{2})(\\d{2})(\\d{2})(\\d{2})(\\d{2})(\\d{2})(\\d{2})(\\d{2})(\\d{2})(\\d{2})");
+
+    // Monoprice 31028 / PAM1270 status string: ?6ZS VO8 PO1 MU0 IS0+ (does not include treble, bass & balance)
+    private static final Pattern MONOPRICE70_PATTERN = Pattern
+            .compile("^\\?(\\d{1})ZS VO(\\d{1,2}) PO(\\d{1}) MU(\\d{1}) IS(\\d{1})+");
+    private static final Pattern MONOPRICE70_TREB_PATTERN = Pattern.compile("^\\?(\\d{1})TR(\\d{1,2})+");
+    private static final Pattern MONOPRICE70_BASS_PATTERN = Pattern.compile("^\\?(\\d{1})BS(\\d{1,2})+");
+    private static final Pattern MONOPRICE70_BALN_PATTERN = Pattern.compile("^\\?(\\d{1})BA(\\d{1,2})+");
+
+    // Xantech status string: #1ZS PR0 SS1 VO0 MU1 TR7 BS7 BA32 LS0 PS0+
+    private static final Pattern XANTECH_PATTERN = Pattern.compile(
+            "^#(\\d{1,2})ZS PR(\\d{1}) SS(\\d{1}) VO(\\d{1,2}) MU(\\d{1}) TR(\\d{1,2}) BS(\\d{1,2}) BA(\\d{1,2}) LS(\\d{1}) PS(\\d{1})+");
+
+    private String cmdPrefix;
+    private String cmdSuffix;
+    private String queryPrefix;
+    private String querySuffix;
+    private String respPrefix;
+    private String powerCmd;
+    private String sourceCmd;
+    private String volumeCmd;
+    private String muteCmd;
+    private String trebleCmd;
+    private String bassCmd;
+    private String balanceCmd;
+    private String dndCmd;
+    private int maxVol;
+    private int minTone;
+    private int maxTone;
+    private int toneOffset;
+    private int minBal;
+    private int maxBal;
+    private int balOffset;
+    private int maxZones;
+    private int numSources;
+    private boolean padNumbers;
+    private List<String> zoneIds;
+    private Map<String, String> zoneIdMap = new HashMap<>();
+
+    private static final String ON_STR = "1";
+    private static final String OFF_STR = "0";
+
+    private static final String ON_STR_PAD = "01";
+    private static final String OFF_STR_PAD = "00";
+
+    /**
+     * Constructor for all the enum parameters
+     *
+     **/
+    AmplifierModel(String cmdPrefix, String cmdSuffix, String queryPrefix, String querySuffix, String respPrefix,
+            String powerCmd, String sourceCmd, String volumeCmd, String muteCmd, String trebleCmd, String bassCmd,
+            String balanceCmd, String dndCmd, int maxVol, int minTone, int maxTone, int toneOffset, int minBal,
+            int maxBal, int balOffset, int maxZones, int numSources, boolean padNumbers, List<String> zoneIds) {
+        this.cmdPrefix = cmdPrefix;
+        this.cmdSuffix = cmdSuffix;
+        this.queryPrefix = queryPrefix;
+        this.querySuffix = querySuffix;
+        this.respPrefix = respPrefix;
+        this.powerCmd = powerCmd;
+        this.sourceCmd = sourceCmd;
+        this.volumeCmd = volumeCmd;
+        this.muteCmd = muteCmd;
+        this.trebleCmd = trebleCmd;
+        this.bassCmd = bassCmd;
+        this.balanceCmd = balanceCmd;
+        this.dndCmd = dndCmd;
+        this.maxVol = maxVol;
+        this.minTone = minTone;
+        this.maxTone = maxTone;
+        this.toneOffset = toneOffset;
+        this.minBal = minBal;
+        this.maxBal = maxBal;
+        this.balOffset = balOffset;
+        this.maxZones = maxZones;
+        this.numSources = numSources;
+        this.padNumbers = padNumbers;
+        this.zoneIds = zoneIds;
+
+        int i = 1;
+        for (String zoneId : zoneIds) {
+            zoneIdMap.put(zoneId, "zone" + i);
+            i++;
+        }
+    }
+
+    public abstract MonopriceAudioZoneDTO getZoneData(String newZoneData);
+
+    public abstract List<StateOption> getSourceLabels(MonopriceAudioThingConfiguration config);
+
+    public String getZoneIdFromZoneName(String zoneName) {
+        for (String zoneId : zoneIdMap.keySet()) {
+            if (zoneIdMap.get(zoneId).equals(zoneName)) {
+                return zoneId;
+            }
+        }
+        return "";
+    }
+
+    public String getZoneName(String zoneId) {
+        String zoneName = zoneIdMap.get(zoneId);
+        if (zoneName != null) {
+            return zoneName;
+        } else {
+            return "";
+        }
+    }
+
+    public String getCmdPrefix() {
+        return cmdPrefix;
+    }
+
+    public String getQueryPrefix() {
+        return queryPrefix;
+    }
+
+    public String getQuerySuffix() {
+        return querySuffix;
+    }
+
+    public String getRespPrefix() {
+        return respPrefix;
+    }
+
+    public String getPowerCmd() {
+        return powerCmd;
+    }
+
+    public String getSourceCmd() {
+        return sourceCmd;
+    }
+
+    public String getVolumeCmd() {
+        return volumeCmd;
+    }
+
+    public String getMuteCmd() {
+        return muteCmd;
+    }
+
+    public String getTrebleCmd() {
+        return trebleCmd;
+    }
+
+    public String getBassCmd() {
+        return bassCmd;
+    }
+
+    public String getBalanceCmd() {
+        return balanceCmd;
+    }
+
+    public String getDndCmd() {
+        return dndCmd;
+    }
+
+    public int getMaxVol() {
+        return maxVol;
+    }
+
+    public int getMinTone() {
+        return minTone;
+    }
+
+    public int getMaxTone() {
+        return maxTone;
+    }
+
+    public int getMinBal() {
+        return minBal;
+    }
+
+    public int getMaxBal() {
+        return maxBal;
+    }
+
+    public int getBalOffset() {
+        return balOffset;
+    }
+
+    public int getToneOffset() {
+        return toneOffset;
+    }
+
+    public int getMaxZones() {
+        return maxZones;
+    }
+
+    public int getNumSources() {
+        return numSources;
+    }
+
+    public String getCmdSuffix() {
+        return cmdSuffix;
+    }
+
+    public List<String> getZoneIds() {
+        return zoneIds;
+    }
+
+    public String getFormattedValue(Integer value) {
+        if (padNumbers) {
+            return String.format("%02d", value);
+        } else {
+            return value.toString();
+        }
+    }
+
+    public String getOnStr() {
+        if (padNumbers) {
+            return ON_STR_PAD;
+        } else {
+            return ON_STR;
+        }
+    }
+
+    public String getOffStr() {
+        if (padNumbers) {
+            return OFF_STR_PAD;
+        } else {
+            return OFF_STR;
+        }
+    }
+}
diff --git a/bundles/org.openhab.binding.monopriceaudio/src/main/java/org/openhab/binding/monopriceaudio/internal/communication/MonopriceAudioCommand.java b/bundles/org.openhab.binding.monopriceaudio/src/main/java/org/openhab/binding/monopriceaudio/internal/communication/MonopriceAudioCommand.java
deleted file mode 100644 (file)
index 58fc0f4..0000000
+++ /dev/null
@@ -1,48 +0,0 @@
-/**
- * Copyright (c) 2010-2023 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.monopriceaudio.internal.communication;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-
-/**
- * Represents the different kinds of commands
- *
- * @author Michael Lobstein - Initial contribution
- */
-@NonNullByDefault
-public enum MonopriceAudioCommand {
-    QUERY("?"),
-    POWER("PR"),
-    SOURCE("CH"),
-    VOLUME("VO"),
-    MUTE("MU"),
-    TREBLE("TR"),
-    BASS("BS"),
-    BALANCE("BL"),
-    DND("DT");
-
-    private final String value;
-
-    MonopriceAudioCommand(String value) {
-        this.value = value;
-    }
-
-    /**
-     * Get the command name
-     *
-     * @return the command name
-     */
-    public String getValue() {
-        return value;
-    }
-}
index 1ad00ef909a71f3b05b3302dbcc57378838b697b..4eb6552711d32b498ce3c1e3e1a4e5b35655e333 100644 (file)
  */
 package org.openhab.binding.monopriceaudio.internal.communication;
 
+import static org.openhab.binding.monopriceaudio.internal.MonopriceAudioBindingConstants.*;
+
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
 import java.util.List;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
 
 import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.eclipse.jdt.annotation.Nullable;
@@ -32,20 +32,13 @@ import org.slf4j.LoggerFactory;
  *
  * @author Laurent Garnier - Initial contribution
  * @author Michael Lobstein - Adapted for the MonopriceAudio binding
+ * @author Michael Lobstein - Add support for additional amplifier types
  */
 @NonNullByDefault
 public abstract class MonopriceAudioConnector {
-    public static final String READ_ERROR = "Command Error.";
-
     // Message types
     public static final String KEY_ZONE_UPDATE = "zone_update";
-    // Special keys used by the binding
-    public static final String KEY_ERROR = "error";
-    public static final String MSG_VALUE_ON = "on";
-
-    private static final Pattern PATTERN = Pattern.compile("^.*#>(\\d{22})$", Pattern.DOTALL);
-    private static final String BEGIN_CMD = "<";
-    private static final String END_CMD = "\r";
+    public static final String KEY_PING = "ping";
 
     private final Logger logger = LoggerFactory.getLogger(MonopriceAudioConnector.class);
 
@@ -57,6 +50,9 @@ public abstract class MonopriceAudioConnector {
 
     /** true if the connection is established, false if not */
     private boolean connected;
+    private boolean pingResponseOnly;
+
+    private @Nullable AmplifierModel amp;
 
     private @Nullable Thread readerThread;
 
@@ -78,6 +74,16 @@ public abstract class MonopriceAudioConnector {
      */
     protected void setConnected(boolean connected) {
         this.connected = connected;
+        this.pingResponseOnly = false;
+    }
+
+    /**
+     * Set the AmplifierModel
+     *
+     * @param amp the AmplifierModel being used
+     */
+    protected void setAmplifierModel(AmplifierModel amp) {
+        this.amp = amp;
     }
 
     /**
@@ -105,6 +111,7 @@ public abstract class MonopriceAudioConnector {
      * Stop the thread that handles the feedback messages and close the opened input and output streams
      */
     protected void cleanup() {
+        this.pingResponseOnly = false;
         Thread readerThread = this.readerThread;
         OutputStream dataOut = this.dataOut;
         if (dataOut != null) {
@@ -129,7 +136,7 @@ public abstract class MonopriceAudioConnector {
             try {
                 readerThread.join(3000);
             } catch (InterruptedException e) {
-                logger.warn("Error joining readerThread: {}", e.getMessage());
+                logger.debug("Error joining readerThread: {}", e.getMessage());
             }
             this.readerThread = null;
         }
@@ -160,6 +167,19 @@ public abstract class MonopriceAudioConnector {
         }
     }
 
+    /**
+     * Get only ping success events from the connector. If amplifier does not have keypads or supports
+     * unsolicited updates, the use of this method will cause the connector to only send ping success events until the
+     * next time the connection is reset.
+     *
+     * @throws MonopriceAudioException - In case of any problem
+     */
+    public void sendPing() throws MonopriceAudioException {
+        pingResponseOnly = true;
+        // poll zone 1 status only to see if the amp responds
+        queryZone(amp.getZoneIds().get(0));
+    }
+
     /**
      * Get the status of a zone
      *
@@ -167,33 +187,55 @@ public abstract class MonopriceAudioConnector {
      *
      * @throws MonopriceAudioException - In case of any problem
      */
-    public void queryZone(MonopriceAudioZone zone) throws MonopriceAudioException {
-        sendCommand(zone, MonopriceAudioCommand.QUERY, null);
+    public void queryZone(String zoneId) throws MonopriceAudioException {
+        sendCommand(amp.getQueryPrefix() + zoneId + amp.getQuerySuffix());
     }
 
     /**
-     * Request the MonopriceAudio controller to execute a command
+     * Monoprice 31028 and OSD Audio PAM1270 amps do not report treble, bass and balance with the main status inquiry,
+     * so we must send three extra commands to retrieve those values
+     *
+     * @param zone the zone to query for current treble, bass and balance status
+     *
+     * @throws MonopriceAudioException - In case of any problem
+     */
+    public void queryTrebBassBalance(String zoneId) throws MonopriceAudioException {
+        sendCommand(amp.getQueryPrefix() + zoneId + amp.getTrebleCmd());
+        sendCommand(amp.getQueryPrefix() + zoneId + amp.getBassCmd());
+        sendCommand(amp.getQueryPrefix() + zoneId + amp.getBalanceCmd());
+    }
+
+    /**
+     * Request the MonopriceAudio amplifier to execute a raw command
      *
-     * @param zone the zone for which the command is to be run
      * @param cmd the command to execute
-     * @param value the integer value to consider for volume, bass, treble, etc. adjustment
      *
      * @throws MonopriceAudioException - In case of any problem
      */
-    public void sendCommand(MonopriceAudioZone zone, MonopriceAudioCommand cmd, @Nullable Integer value)
+    public void sendCommand(String cmd) throws MonopriceAudioException {
+        sendCommand(null, cmd, null);
+    }
+
+    /**
+     * Request the MonopriceAudio amplifier to execute a command
+     *
+     * @param zoneId the zone for which the command is to be run
+     * @param cmd the command to execute
+     * @param value the integer value to consider for power, volume, bass, treble, etc. adjustment
+     *
+     * @throws MonopriceAudioException - In case of any problem
+     */
+    public void sendCommand(@Nullable String zoneId, String cmd, @Nullable Integer value)
             throws MonopriceAudioException {
-        String messageStr = "";
-
-        if (cmd == MonopriceAudioCommand.QUERY) {
-            // query special case (ie: ? + zoneId)
-            messageStr = cmd.getValue() + zone.getZoneId();
-        } else if (value != null) {
-            // if the command passed a value, append it to the messageStr
-            messageStr = BEGIN_CMD + zone.getZoneId() + cmd.getValue() + String.format("%02d", value);
+        String messageStr;
+
+        if (zoneId != null && value != null) {
+            // if the command passed a value, build messageStr with prefix, zoneId, command, value and suffix
+            messageStr = amp.getCmdPrefix() + zoneId + cmd + amp.getFormattedValue(value) + amp.getCmdSuffix();
         } else {
-            throw new MonopriceAudioException("Send command \"" + messageStr + "\" failed: passed in value is null");
+            // otherwise send the raw cmd from the query() methods
+            messageStr = cmd + amp.getCmdSuffix();
         }
-        messageStr += END_CMD;
         logger.debug("Send command {}", messageStr);
 
         OutputStream dataOut = this.dataOut;
@@ -204,7 +246,7 @@ public abstract class MonopriceAudioConnector {
             dataOut.write(messageStr.getBytes(StandardCharsets.US_ASCII));
             dataOut.flush();
         } catch (IOException e) {
-            throw new MonopriceAudioException("Send command \"" + cmd.getValue() + "\" failed: " + e.getMessage(), e);
+            throw new MonopriceAudioException("Send command \"" + messageStr + "\" failed: " + e.getMessage(), e);
         }
     }
 
@@ -232,20 +274,20 @@ public abstract class MonopriceAudioConnector {
      * @param incomingMessage the received message
      */
     public void handleIncomingMessage(byte[] incomingMessage) {
-        String message = new String(incomingMessage, StandardCharsets.US_ASCII).trim();
+        if (pingResponseOnly) {
+            dispatchKeyValue(KEY_PING, EMPTY);
+            return;
+        }
 
-        logger.debug("handleIncomingMessage: {}", message);
+        String message = new String(incomingMessage, StandardCharsets.US_ASCII).trim();
 
-        if (READ_ERROR.equals(message)) {
-            dispatchKeyValue(KEY_ERROR, MSG_VALUE_ON);
+        if (EMPTY.equals(message)) {
             return;
         }
 
-        // Amp controller sends status string: #>1200010000130809100601
-        Matcher matcher = PATTERN.matcher(message);
-        if (matcher.find()) {
-            // pull out just the digits and send them as an event
-            dispatchKeyValue(KEY_ZONE_UPDATE, matcher.group(1));
+        if (message.startsWith(amp.getRespPrefix())) {
+            logger.debug("handleIncomingMessage: {}", message);
+            dispatchKeyValue(KEY_ZONE_UPDATE, message);
         } else {
             logger.debug("no match on message: {}", message);
         }
index c150699140e3296b3cf1dfd93d64782bb5f8f3c1..715fe925c8059f2c7b21d5ec6cd3d08f268468e9 100644 (file)
@@ -42,7 +42,7 @@ public class MonopriceAudioDefaultConnector extends MonopriceAudioConnector {
     }
 
     @Override
-    public void sendCommand(MonopriceAudioZone zone, MonopriceAudioCommand cmd, @Nullable Integer value) {
+    public void sendCommand(@Nullable String zone, String cmd, @Nullable Integer value) {
         logger.warn(
                 "MonopriceAudio binding incorrectly configured. Please configure for Serial or IP over serial connection");
         setConnected(false);
index 3a0a850be8869e0dacfb272fe77e4c2216d1fcbc..b8421e0c548256c2f7d75aeea9dd07932dfa1c79 100644 (file)
@@ -48,11 +48,13 @@ public class MonopriceAudioIpConnector extends MonopriceAudioConnector {
      * @param address the IP address of the serial over IP device
      * @param port the TCP port to be used
      * @param uid the thing uid string
+     * @param amp the AmplifierModel being used
      */
-    public MonopriceAudioIpConnector(@Nullable String address, int port, String uid) {
+    public MonopriceAudioIpConnector(@Nullable String address, int port, String uid, AmplifierModel amp) {
         this.address = address;
         this.port = port;
         this.uid = uid;
+        setAmplifierModel(amp);
     }
 
     @Override
index 597652809290872ad9885b5103d33698e7da3b98..0a63b74b94437ebb6591c275c27f70ba684c8b9f 100644 (file)
@@ -78,7 +78,6 @@ public class MonopriceAudioReaderThread extends Thread {
             }
         } catch (MonopriceAudioException e) {
             logger.debug("Reading failed: {}", e.getMessage(), e);
-            connector.handleIncomingMessage(MonopriceAudioConnector.READ_ERROR.getBytes());
         }
 
         logger.debug("Data listener stopped");
index f086c858aa5cafe5df9f63daf7f026b28755b2d2..aae8f84ae76ef50a2b18b67e4c2eb1e239ff3617 100644 (file)
@@ -51,11 +51,14 @@ public class MonopriceAudioSerialConnector extends MonopriceAudioConnector {
      * @param serialPortManager the serial port manager
      * @param serialPortName the serial port name to be used
      * @param uid the thing uid string
+     * @param amp the AmplifierModel being used
      */
-    public MonopriceAudioSerialConnector(SerialPortManager serialPortManager, String serialPortName, String uid) {
+    public MonopriceAudioSerialConnector(SerialPortManager serialPortManager, String serialPortName, String uid,
+            AmplifierModel amp) {
         this.serialPortManager = serialPortManager;
         this.serialPortName = serialPortName;
         this.uid = uid;
+        setAmplifierModel(amp);
     }
 
     @Override
diff --git a/bundles/org.openhab.binding.monopriceaudio/src/main/java/org/openhab/binding/monopriceaudio/internal/communication/MonopriceAudioZone.java b/bundles/org.openhab.binding.monopriceaudio/src/main/java/org/openhab/binding/monopriceaudio/internal/communication/MonopriceAudioZone.java
deleted file mode 100644 (file)
index 22065c6..0000000
+++ /dev/null
@@ -1,77 +0,0 @@
-/**
- * Copyright (c) 2010-2023 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.monopriceaudio.internal.communication;
-
-import java.util.Arrays;
-import java.util.List;
-import java.util.stream.Collectors;
-
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.openhab.binding.monopriceaudio.internal.MonopriceAudioException;
-
-/**
- * Represents the different internal zone IDs of the Monoprice Whole House Amplifier
- *
- * @author Michael Lobstein - Initial contribution
- */
-@NonNullByDefault
-public enum MonopriceAudioZone {
-
-    ALL("all"),
-    ZONE1("11"),
-    ZONE2("12"),
-    ZONE3("13"),
-    ZONE4("14"),
-    ZONE5("15"),
-    ZONE6("16"),
-    ZONE7("21"),
-    ZONE8("22"),
-    ZONE9("23"),
-    ZONE10("24"),
-    ZONE11("25"),
-    ZONE12("26"),
-    ZONE13("31"),
-    ZONE14("32"),
-    ZONE15("33"),
-    ZONE16("34"),
-    ZONE17("35"),
-    ZONE18("36");
-
-    private final String zoneId;
-
-    // make a list of all valid zone names
-    public static final List<String> VALID_ZONES = Arrays.stream(values()).filter(z -> z != ALL)
-            .map(MonopriceAudioZone::name).collect(Collectors.toList());
-
-    // make a list of all valid zone ids
-    public static final List<String> VALID_ZONE_IDS = Arrays.stream(values()).filter(z -> z != ALL)
-            .map(MonopriceAudioZone::getZoneId).collect(Collectors.toList());
-
-    public static MonopriceAudioZone fromZoneId(String zoneId) throws MonopriceAudioException {
-        return Arrays.stream(values()).filter(z -> z.zoneId.equalsIgnoreCase(zoneId)).findFirst()
-                .orElseThrow(() -> new MonopriceAudioException("Invalid zoneId specified: " + zoneId));
-    }
-
-    MonopriceAudioZone(String zoneId) {
-        this.zoneId = zoneId;
-    }
-
-    /**
-     * Get the zone id
-     *
-     * @return the zone id
-     */
-    public String getZoneId() {
-        return zoneId;
-    }
-}
index f70f542d67894690589696a78628144492ec5117..d64f90d75343676fb5329d473405a158e589bc07 100644 (file)
@@ -35,4 +35,7 @@ public class MonopriceAudioThingConfiguration {
     public @Nullable String inputLabel4;
     public @Nullable String inputLabel5;
     public @Nullable String inputLabel6;
+    public @Nullable String inputLabel7;
+    public @Nullable String inputLabel8;
+    public boolean disableKeypadPolling = false;
 }
index b8fba4788fbaa0b4d1754aef4f9398ff6415f0d1..3994d18bd927982115265b0842107c4aac6d884b 100644 (file)
  */
 package org.openhab.binding.monopriceaudio.internal.dto;
 
+import static org.openhab.binding.monopriceaudio.internal.MonopriceAudioBindingConstants.*;
+
 /**
- * Represents the data elements of a single zone of the Monoprice Whole House Amplifier
+ * Represents the data elements of a single zone of a supported amplifier
  *
  * @author Michael Lobstein - Initial contribution
  */
 public class MonopriceAudioZoneDTO {
 
-    private String zone;
-    private String page;
-    private String power;
-    private String mute;
-    private String dnd;
-    private int volume;
-    private int treble;
-    private int bass;
-    private int balance;
-    private String source;
-    private String keypad;
+    private String zone = EMPTY;
+    private String page = EMPTY;
+    private String power = EMPTY;
+    private String mute = EMPTY;
+    private String dnd = EMPTY;
+    private int volume = NIL;
+    private int treble = NIL;
+    private int bass = NIL;
+    private int balance = NIL;
+    private String source = EMPTY;
+    private String keypad = EMPTY;
+
+    public MonopriceAudioZoneDTO() {
+    }
+
+    public MonopriceAudioZoneDTO(String zone) {
+        this.zone = zone;
+    }
 
     public void setZone(String zone) {
         this.zone = zone;
     }
 
+    public String getZone() {
+        return this.zone;
+    }
+
     public void setPage(String page) {
         this.page = page;
     }
@@ -44,7 +57,7 @@ public class MonopriceAudioZoneDTO {
     }
 
     public boolean isPageActive() {
-        return ("01").equals(this.page);
+        return this.page.contains(ONE);
     }
 
     public void setPower(String power) {
@@ -56,7 +69,7 @@ public class MonopriceAudioZoneDTO {
     }
 
     public boolean isPowerOn() {
-        return ("01").equals(this.power);
+        return this.power.contains(ONE);
     }
 
     public void setMute(String mute) {
@@ -68,7 +81,7 @@ public class MonopriceAudioZoneDTO {
     }
 
     public boolean isMuted() {
-        return ("01").equals(this.mute);
+        return this.mute.contains(ONE);
     }
 
     public void setDnd(String dnd) {
@@ -80,7 +93,7 @@ public class MonopriceAudioZoneDTO {
     }
 
     public boolean isDndOn() {
-        return ("01").equals(this.dnd);
+        return this.dnd.contains(ONE);
     }
 
     public int getVolume() {
@@ -132,14 +145,12 @@ public class MonopriceAudioZoneDTO {
     }
 
     public boolean isKeypadActive() {
-        return ("01").equals(this.keypad);
+        return this.keypad.contains(ONE);
     }
 
     @Override
     public String toString() {
-        // Re-construct the original status message from the controller
         // This is used to determine if something changed from the last polling update
-        return zone + page + power + mute + dnd + (String.format("%02d", volume)) + (String.format("%02d", treble))
-                + (String.format("%02d", bass)) + (String.format("%02d", balance)) + source + keypad;
+        return zone + page + power + mute + dnd + volume + treble + bass + balance + source + keypad;
     }
 }
index 9e244d9f2ebe328621d96ae13f35da2ed36cc55c..2c3a2024a9b7abf49b40d6eec07d4ef3ccafaf8c 100644 (file)
@@ -22,8 +22,6 @@ import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.ScheduledFuture;
 import java.util.concurrent.TimeUnit;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
 import java.util.stream.Collectors;
 import java.util.stream.IntStream;
 import java.util.stream.Stream;
@@ -32,14 +30,13 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.eclipse.jdt.annotation.Nullable;
 import org.openhab.binding.monopriceaudio.internal.MonopriceAudioException;
 import org.openhab.binding.monopriceaudio.internal.MonopriceAudioStateDescriptionOptionProvider;
-import org.openhab.binding.monopriceaudio.internal.communication.MonopriceAudioCommand;
+import org.openhab.binding.monopriceaudio.internal.communication.AmplifierModel;
 import org.openhab.binding.monopriceaudio.internal.communication.MonopriceAudioConnector;
 import org.openhab.binding.monopriceaudio.internal.communication.MonopriceAudioDefaultConnector;
 import org.openhab.binding.monopriceaudio.internal.communication.MonopriceAudioIpConnector;
 import org.openhab.binding.monopriceaudio.internal.communication.MonopriceAudioMessageEvent;
 import org.openhab.binding.monopriceaudio.internal.communication.MonopriceAudioMessageEventListener;
 import org.openhab.binding.monopriceaudio.internal.communication.MonopriceAudioSerialConnector;
-import org.openhab.binding.monopriceaudio.internal.communication.MonopriceAudioZone;
 import org.openhab.binding.monopriceaudio.internal.configuration.MonopriceAudioThingConfiguration;
 import org.openhab.binding.monopriceaudio.internal.dto.MonopriceAudioZoneDTO;
 import org.openhab.core.io.transport.serial.SerialPortManager;
@@ -67,37 +64,23 @@ import org.slf4j.LoggerFactory;
  * Based on the Rotel binding by Laurent Garnier
  *
  * @author Michael Lobstein - Initial contribution
+ * @author Michael Lobstein - Add support for additional amplifier types
  */
 @NonNullByDefault
 public class MonopriceAudioHandler extends BaseThingHandler implements MonopriceAudioMessageEventListener {
     private static final long RECON_POLLING_INTERVAL_SEC = 60;
-    private static final long INITIAL_POLLING_DELAY_SEC = 5;
-    private static final Pattern PATTERN = Pattern
-            .compile("^(\\d{2})(\\d{2})(\\d{2})(\\d{2})(\\d{2})(\\d{2})(\\d{2})(\\d{2})(\\d{2})(\\d{2})(\\d{2})");
+    private static final long INITIAL_POLLING_DELAY_SEC = 10;
 
-    private static final String ZONE = "ZONE";
+    private static final String ZONE = "zone";
     private static final String ALL = "all";
     private static final String CHANNEL_DELIMIT = "#";
-    private static final String ON_STR = "01";
-    private static final String OFF_STR = "00";
 
+    private static final int ZERO = 0;
     private static final int ONE = 1;
-    private static final int MAX_ZONES = 18;
-    private static final int MAX_SRC = 6;
     private static final int MIN_VOLUME = 0;
-    private static final int MAX_VOLUME = 38;
-    private static final int MIN_TONE = -7;
-    private static final int MAX_TONE = 7;
-    private static final int MIN_BALANCE = -10;
-    private static final int MAX_BALANCE = 10;
-    private static final int BALANCE_OFFSET = 10;
-    private static final int TONE_OFFSET = 7;
-
-    // build a Map with a MonopriceAudioZoneDTO for each zoneId
-    private final Map<String, MonopriceAudioZoneDTO> zoneDataMap = MonopriceAudioZone.VALID_ZONE_IDS.stream()
-            .collect(Collectors.toMap(s -> s, s -> new MonopriceAudioZoneDTO()));
 
     private final Logger logger = LoggerFactory.getLogger(MonopriceAudioHandler.class);
+    private final AmplifierModel amp;
     private final MonopriceAudioStateDescriptionOptionProvider stateDescriptionProvider;
     private final SerialPortManager serialPortManager;
 
@@ -106,17 +89,21 @@ public class MonopriceAudioHandler extends BaseThingHandler implements Monoprice
 
     private MonopriceAudioConnector connector = new MonopriceAudioDefaultConnector();
 
+    private Map<String, MonopriceAudioZoneDTO> zoneDataMap = Map.of(ZONE, new MonopriceAudioZoneDTO());
     private Set<String> ignoreZones = new HashSet<>();
     private long lastPollingUpdate = System.currentTimeMillis();
-    private long pollingInterval = 0;
-    private int numZones = 0;
-    private int allVolume = 1;
-    private int initialAllVolume = 0;
+    private long pollingInterval = ZERO;
+    private int numZones = ZERO;
+    private int allVolume = ONE;
+    private int initialAllVolume = ZERO;
+    private boolean disableKeypadPolling = false;
     private Object sequenceLock = new Object();
 
-    public MonopriceAudioHandler(Thing thing, MonopriceAudioStateDescriptionOptionProvider stateDescriptionProvider,
+    public MonopriceAudioHandler(Thing thing, AmplifierModel amp,
+            MonopriceAudioStateDescriptionOptionProvider stateDescriptionProvider,
             SerialPortManager serialPortManager) {
         super(thing);
+        this.amp = amp;
         this.stateDescriptionProvider = stateDescriptionProvider;
         this.serialPortManager = serialPortManager;
     }
@@ -128,89 +115,79 @@ public class MonopriceAudioHandler extends BaseThingHandler implements Monoprice
         final String serialPort = config.serialPort;
         final String host = config.host;
         final Integer port = config.port;
+        numZones = config.numZones;
         final String ignoreZonesConfig = config.ignoreZones;
+        disableKeypadPolling = config.disableKeypadPolling || amp == AmplifierModel.MONOPRICE70;
+
+        // build a Map with a MonopriceAudioZoneDTO for each zoneId
+        zoneDataMap = amp.getZoneIds().stream().limit(numZones)
+                .collect(Collectors.toMap(s -> s, s -> new MonopriceAudioZoneDTO(s)));
 
         // Check configuration settings
-        String configError = null;
-        if ((serialPort == null || serialPort.isEmpty()) && (host == null || host.isEmpty())) {
-            configError = "undefined serialPort and host configuration settings; please set one of them";
-        } else if (serialPort != null && (host == null || host.isEmpty())) {
+        if (serialPort != null && host == null && port == null) {
             if (serialPort.toLowerCase().startsWith("rfc2217")) {
-                configError = "use host and port configuration settings for a serial over IP connection";
-            }
-        } else {
-            if (port == null) {
-                configError = "undefined port configuration setting";
-            } else if (port <= 0) {
-                configError = "invalid port configuration setting";
+                updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
+                        "@text/offline.configuration-error-rfc2217");
+                return;
             }
-        }
-
-        if (configError != null) {
-            updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, configError);
+        } else if (serialPort != null && (host != null || port != null)) {
+            updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
+                    "@text/offline.configuration-error-conflict");
             return;
         }
 
         if (serialPort != null) {
-            connector = new MonopriceAudioSerialConnector(serialPortManager, serialPort, uid);
-        } else if (port != null) {
-            connector = new MonopriceAudioIpConnector(host, port, uid);
+            connector = new MonopriceAudioSerialConnector(serialPortManager, serialPort, uid, amp);
+        } else if (host != null && (port != null && port > ZERO)) {
+            connector = new MonopriceAudioIpConnector(host, port, uid, amp);
         } else {
             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
-                    "Either Serial port or Host & Port must be specifed");
+                    "@text/offline.configuration-error-missing");
             return;
         }
 
         pollingInterval = config.pollingInterval;
-        numZones = config.numZones;
         initialAllVolume = config.initialAllVolume;
 
         // If zones were specified to be ignored by the 'all*' commands, use the specified binding
-        // zone ids to get the controller's internal zone ids and save those to a list
+        // zone ids to get the amplifier's internal zone ids and save those to a list
         if (ignoreZonesConfig != null) {
             for (String zone : ignoreZonesConfig.split(",")) {
                 try {
                     int zoneInt = Integer.parseInt(zone);
-                    if (zoneInt >= ONE && zoneInt <= MAX_ZONES) {
+                    if (zoneInt >= ONE && zoneInt <= amp.getMaxZones()) {
                         ignoreZones.add(ZONE + zoneInt);
                     } else {
-                        logger.warn("Invalid ignore zone value: {}, value must be between {} and {}", zone, ONE,
-                                MAX_ZONES);
+                        logger.debug("Invalid ignore zone value: {}, value must be between {} and {}", zone, ONE,
+                                amp.getMaxZones());
                     }
                 } catch (NumberFormatException nfe) {
-                    logger.warn("Invalid ignore zone value: {}", zone);
+                    logger.debug("Invalid ignore zone value: {}", zone);
                 }
             }
         }
 
-        // Build a state option list for the source labels
-        List<StateOption> sourcesLabels = new ArrayList<>();
-        sourcesLabels.add(new StateOption("1", config.inputLabel1));
-        sourcesLabels.add(new StateOption("2", config.inputLabel2));
-        sourcesLabels.add(new StateOption("3", config.inputLabel3));
-        sourcesLabels.add(new StateOption("4", config.inputLabel4));
-        sourcesLabels.add(new StateOption("5", config.inputLabel5));
-        sourcesLabels.add(new StateOption("6", config.inputLabel6));
-
         // Put the source labels on all active zones
         List<Integer> activeZones = IntStream.range(1, numZones + 1).boxed().collect(Collectors.toList());
 
+        List<StateOption> sourceLabels = amp.getSourceLabels(config);
         stateDescriptionProvider.setStateOptions(
-                new ChannelUID(getThing().getUID(), ALL + CHANNEL_DELIMIT + CHANNEL_TYPE_ALLSOURCE), sourcesLabels);
+                new ChannelUID(getThing().getUID(), ALL + CHANNEL_DELIMIT + CHANNEL_TYPE_ALLSOURCE), sourceLabels);
         activeZones.forEach(zoneNum -> {
-            stateDescriptionProvider.setStateOptions(new ChannelUID(getThing().getUID(),
-                    ZONE.toLowerCase() + zoneNum + CHANNEL_DELIMIT + CHANNEL_TYPE_SOURCE), sourcesLabels);
+            stateDescriptionProvider.setStateOptions(
+                    new ChannelUID(getThing().getUID(), ZONE + zoneNum + CHANNEL_DELIMIT + CHANNEL_TYPE_SOURCE),
+                    sourceLabels);
         });
 
         // remove the channels for the zones we are not using
-        if (numZones < MAX_ZONES) {
+        if (numZones < amp.getMaxZones()) {
             List<Channel> channels = new ArrayList<>(this.getThing().getChannels());
 
-            List<Integer> zonesToRemove = IntStream.range(numZones + 1, MAX_ZONES + 1).boxed()
+            List<Integer> zonesToRemove = IntStream.range(numZones + 1, amp.getMaxZones() + 1).boxed()
                     .collect(Collectors.toList());
 
             zonesToRemove.forEach(zone -> {
-                channels.removeIf(c -> (c.getUID().getId().contains(ZONE.toLowerCase() + zone)));
+                channels.removeIf(c -> (c.getUID().getId().contains(ZONE + zone)));
             });
             updateThing(editThing().withChannels(channels).build());
         }
@@ -218,7 +195,7 @@ public class MonopriceAudioHandler extends BaseThingHandler implements Monoprice
         // initialize the all volume state
         allVolume = initialAllVolume;
         long allVolumePct = Math
-                .round((double) (initialAllVolume - MIN_VOLUME) / (double) (MAX_VOLUME - MIN_VOLUME) * 100.0);
+                .round((initialAllVolume - MIN_VOLUME) / (double) (amp.getMaxVol() - MIN_VOLUME) * 100.0);
         updateState(ALL + CHANNEL_DELIMIT + CHANNEL_TYPE_ALLVOLUME, new PercentType(BigDecimal.valueOf(allVolumePct)));
 
         scheduleReconnectJob();
@@ -239,8 +216,9 @@ public class MonopriceAudioHandler extends BaseThingHandler implements Monoprice
     public void handleCommand(ChannelUID channelUID, Command command) {
         String channel = channelUID.getId();
         String[] channelSplit = channel.split(CHANNEL_DELIMIT);
-        MonopriceAudioZone zone = MonopriceAudioZone.valueOf(channelSplit[0].toUpperCase());
         String channelType = channelSplit[1];
+        String zoneName = channelSplit[0];
+        String zoneId = amp.getZoneIdFromZoneName(zoneName);
 
         if (getThing().getStatus() != ThingStatus.ONLINE) {
             logger.debug("Thing is not ONLINE; command {} from channel {} is ignored", command, channel);
@@ -255,100 +233,100 @@ public class MonopriceAudioHandler extends BaseThingHandler implements Monoprice
             }
 
             if (command instanceof RefreshType) {
-                MonopriceAudioZoneDTO zoneDTO = zoneDataMap.get(zone.getZoneId());
-                if (zoneDTO != null) {
-                    updateChannelState(zone, channelType, zoneDTO);
-                } else {
-                    logger.info("Could not execute REFRESH command for zone {}: null", zone.getZoneId());
-                }
+                updateChannelState(zoneId, channelType);
                 return;
             }
 
-            Stream<String> zoneStream = MonopriceAudioZone.VALID_ZONES.stream().limit(numZones);
+            Stream<String> zoneStream = amp.getZoneIds().stream().limit(numZones);
             try {
                 switch (channelType) {
                     case CHANNEL_TYPE_POWER:
                         if (command instanceof OnOffType) {
-                            connector.sendCommand(zone, MonopriceAudioCommand.POWER, command == OnOffType.ON ? 1 : 0);
-                            zoneDataMap.get(zone.getZoneId()).setPower(command == OnOffType.ON ? ON_STR : OFF_STR);
+                            connector.sendCommand(zoneId, amp.getPowerCmd(), command == OnOffType.ON ? ONE : ZERO);
+                            zoneDataMap.get(zoneId)
+                                    .setPower(command == OnOffType.ON ? amp.getOnStr() : amp.getOffStr());
                         }
                         break;
                     case CHANNEL_TYPE_SOURCE:
                         if (command instanceof DecimalType) {
-                            int value = ((DecimalType) command).intValue();
-                            if (value >= ONE && value <= MAX_SRC) {
-                                logger.debug("Got source command {} zone {}", value, zone);
-                                connector.sendCommand(zone, MonopriceAudioCommand.SOURCE, value);
-                                zoneDataMap.get(zone.getZoneId()).setSource(String.format("%02d", value));
+                            final int value = ((DecimalType) command).intValue();
+                            if (value >= ONE && value <= amp.getNumSources()) {
+                                logger.debug("Got source command {} zone {}", value, zoneId);
+                                connector.sendCommand(zoneId, amp.getSourceCmd(), value);
+                                zoneDataMap.get(zoneId).setSource(amp.getFormattedValue(value));
                             }
                         }
                         break;
                     case CHANNEL_TYPE_VOLUME:
                         if (command instanceof PercentType) {
-                            int value = (int) Math
-                                    .round(((PercentType) command).doubleValue() / 100.0 * (MAX_VOLUME - MIN_VOLUME))
+                            final int value = (int) Math.round(
+                                    ((PercentType) command).doubleValue() / 100.0 * (amp.getMaxVol() - MIN_VOLUME))
                                     + MIN_VOLUME;
-                            logger.debug("Got volume command {} zone {}", value, zone);
-                            connector.sendCommand(zone, MonopriceAudioCommand.VOLUME, value);
-                            zoneDataMap.get(zone.getZoneId()).setVolume(value);
+                            logger.debug("Got volume command {} zone {}", value, zoneId);
+                            connector.sendCommand(zoneId, amp.getVolumeCmd(), value);
+                            zoneDataMap.get(zoneId).setVolume(value);
                         }
                         break;
                     case CHANNEL_TYPE_MUTE:
                         if (command instanceof OnOffType) {
-                            connector.sendCommand(zone, MonopriceAudioCommand.MUTE, command == OnOffType.ON ? 1 : 0);
-                            zoneDataMap.get(zone.getZoneId()).setMute(command == OnOffType.ON ? ON_STR : OFF_STR);
+                            connector.sendCommand(zoneId, amp.getMuteCmd(), command == OnOffType.ON ? ONE : ZERO);
+                            zoneDataMap.get(zoneId).setMute(command == OnOffType.ON ? amp.getOnStr() : amp.getOffStr());
                         }
                         break;
                     case CHANNEL_TYPE_TREBLE:
                         if (command instanceof DecimalType) {
-                            int value = ((DecimalType) command).intValue();
-                            if (value >= MIN_TONE && value <= MAX_TONE) {
-                                logger.debug("Got treble command {} zone {}", value, zone);
-                                connector.sendCommand(zone, MonopriceAudioCommand.TREBLE, value + TONE_OFFSET);
-                                zoneDataMap.get(zone.getZoneId()).setTreble(value + TONE_OFFSET);
+                            final int value = ((DecimalType) command).intValue();
+                            if (value >= amp.getMinTone() && value <= amp.getMaxTone()) {
+                                logger.debug("Got treble command {} zone {}", value, zoneId);
+                                connector.sendCommand(zoneId, amp.getTrebleCmd(), value + amp.getToneOffset());
+                                zoneDataMap.get(zoneId).setTreble(value + amp.getToneOffset());
                             }
                         }
                         break;
                     case CHANNEL_TYPE_BASS:
                         if (command instanceof DecimalType) {
-                            int value = ((DecimalType) command).intValue();
-                            if (value >= MIN_TONE && value <= MAX_TONE) {
-                                logger.debug("Got bass command {} zone {}", value, zone);
-                                connector.sendCommand(zone, MonopriceAudioCommand.BASS, value + TONE_OFFSET);
-                                zoneDataMap.get(zone.getZoneId()).setBass(value + TONE_OFFSET);
+                            final int value = ((DecimalType) command).intValue();
+                            if (value >= amp.getMinTone() && value <= amp.getMaxTone()) {
+                                logger.debug("Got bass command {} zone {}", value, zoneId);
+                                connector.sendCommand(zoneId, amp.getBassCmd(), value + amp.getToneOffset());
+                                zoneDataMap.get(zoneId).setBass(value + amp.getToneOffset());
                             }
                         }
                         break;
                     case CHANNEL_TYPE_BALANCE:
                         if (command instanceof DecimalType) {
-                            int value = ((DecimalType) command).intValue();
-                            if (value >= MIN_BALANCE && value <= MAX_BALANCE) {
-                                logger.debug("Got balance command {} zone {}", value, zone);
-                                connector.sendCommand(zone, MonopriceAudioCommand.BALANCE, value + BALANCE_OFFSET);
-                                zoneDataMap.get(zone.getZoneId()).setBalance(value + BALANCE_OFFSET);
+                            final int value = ((DecimalType) command).intValue();
+                            if (value >= amp.getMinBal() && value <= amp.getMaxBal()) {
+                                logger.debug("Got balance command {} zone {}", value, zoneId);
+                                connector.sendCommand(zoneId, amp.getBalanceCmd(), value + amp.getBalOffset());
+                                zoneDataMap.get(zoneId).setBalance(value + amp.getBalOffset());
                             }
                         }
                         break;
                     case CHANNEL_TYPE_DND:
                         if (command instanceof OnOffType) {
-                            connector.sendCommand(zone, MonopriceAudioCommand.DND, command == OnOffType.ON ? 1 : 0);
-                            zoneDataMap.get(zone.getZoneId()).setDnd(command == OnOffType.ON ? ON_STR : OFF_STR);
+                            connector.sendCommand(zoneId, amp.getDndCmd(), command == OnOffType.ON ? ONE : ZERO);
+                            zoneDataMap.get(zoneId).setDnd(command == OnOffType.ON ? amp.getOnStr() : amp.getOffStr());
                         }
                         break;
                     case CHANNEL_TYPE_ALLPOWER:
                         if (command instanceof OnOffType) {
-                            zoneStream.forEach((zoneName) -> {
-                                if (command == OnOffType.OFF || !ignoreZones.contains(zoneName)) {
+                            final int cmd = command == OnOffType.ON ? ONE : ZERO;
+                            zoneStream.forEach((streamZoneId) -> {
+                                if (command == OnOffType.OFF || !ignoreZones.contains(amp.getZoneName(streamZoneId))) {
                                     try {
-                                        connector.sendCommand(MonopriceAudioZone.valueOf(zoneName),
-                                                MonopriceAudioCommand.POWER, command == OnOffType.ON ? 1 : 0);
+                                        connector.sendCommand(streamZoneId, amp.getPowerCmd(), cmd);
+                                        zoneDataMap.get(streamZoneId).setPower(amp.getFormattedValue(cmd));
+                                        updateChannelState(streamZoneId, CHANNEL_TYPE_POWER);
+
                                         if (command == OnOffType.ON) {
                                             // reset the volume of each zone to allVolume
-                                            connector.sendCommand(MonopriceAudioZone.valueOf(zoneName),
-                                                    MonopriceAudioCommand.VOLUME, allVolume);
+                                            connector.sendCommand(streamZoneId, amp.getVolumeCmd(), allVolume);
+                                            zoneDataMap.get(streamZoneId).setVolume(allVolume);
+                                            updateChannelState(streamZoneId, CHANNEL_TYPE_VOLUME);
                                         }
                                     } catch (MonopriceAudioException e) {
-                                        logger.warn("Error Turning All Zones On: {}", e.getMessage());
+                                        logger.debug("Error Turning All Zones On: {}", e.getMessage());
                                     }
                                 }
 
@@ -357,15 +335,19 @@ public class MonopriceAudioHandler extends BaseThingHandler implements Monoprice
                         break;
                     case CHANNEL_TYPE_ALLSOURCE:
                         if (command instanceof DecimalType) {
-                            int value = ((DecimalType) command).intValue();
-                            if (value >= ONE && value <= MAX_SRC) {
-                                zoneStream.forEach((zoneName) -> {
-                                    if (!ignoreZones.contains(zoneName)) {
+                            final int value = ((DecimalType) command).intValue();
+                            if (value >= ONE && value <= amp.getNumSources()) {
+                                zoneStream.forEach((streamZoneId) -> {
+                                    if (!ignoreZones.contains(amp.getZoneName(streamZoneId))) {
                                         try {
-                                            connector.sendCommand(MonopriceAudioZone.valueOf(zoneName),
-                                                    MonopriceAudioCommand.SOURCE, value);
+                                            connector.sendCommand(streamZoneId, amp.getSourceCmd(), value);
+                                            if (zoneDataMap.get(streamZoneId).isPowerOn()
+                                                    && !zoneDataMap.get(streamZoneId).isMuted()) {
+                                                zoneDataMap.get(streamZoneId).setSource(amp.getFormattedValue(value));
+                                                updateChannelState(streamZoneId, CHANNEL_TYPE_SOURCE);
+                                            }
                                         } catch (MonopriceAudioException e) {
-                                            logger.warn("Error Setting Source for  All Zones: {}", e.getMessage());
+                                            logger.debug("Error Setting Source for All Zones: {}", e.getMessage());
                                         }
                                     }
                                 });
@@ -374,17 +356,20 @@ public class MonopriceAudioHandler extends BaseThingHandler implements Monoprice
                         break;
                     case CHANNEL_TYPE_ALLVOLUME:
                         if (command instanceof PercentType) {
-                            int value = (int) Math
-                                    .round(((PercentType) command).doubleValue() / 100.0 * (MAX_VOLUME - MIN_VOLUME))
+                            allVolume = (int) Math.round(
+                                    ((PercentType) command).doubleValue() / 100.0 * (amp.getMaxVol() - MIN_VOLUME))
                                     + MIN_VOLUME;
-                            allVolume = value;
-                            zoneStream.forEach((zoneName) -> {
-                                if (!ignoreZones.contains(zoneName)) {
+                            zoneStream.forEach((streamZoneId) -> {
+                                if (!ignoreZones.contains(amp.getZoneName(streamZoneId))) {
                                     try {
-                                        connector.sendCommand(MonopriceAudioZone.valueOf(zoneName),
-                                                MonopriceAudioCommand.VOLUME, value);
+                                        connector.sendCommand(streamZoneId, amp.getVolumeCmd(), allVolume);
+                                        if (zoneDataMap.get(streamZoneId).isPowerOn()
+                                                && !zoneDataMap.get(streamZoneId).isMuted()) {
+                                            zoneDataMap.get(streamZoneId).setVolume(allVolume);
+                                            updateChannelState(streamZoneId, CHANNEL_TYPE_VOLUME);
+                                        }
                                     } catch (MonopriceAudioException e) {
-                                        logger.warn("Error Setting Volume for All Zones: {}", e.getMessage());
+                                        logger.debug("Error Setting Volume for All Zones: {}", e.getMessage());
                                     }
                                 }
                             });
@@ -392,14 +377,17 @@ public class MonopriceAudioHandler extends BaseThingHandler implements Monoprice
                         break;
                     case CHANNEL_TYPE_ALLMUTE:
                         if (command instanceof OnOffType) {
-                            int cmd = command == OnOffType.ON ? 1 : 0;
-                            zoneStream.forEach((zoneName) -> {
-                                if (!ignoreZones.contains(zoneName)) {
+                            final int cmd = command == OnOffType.ON ? ONE : ZERO;
+                            zoneStream.forEach((streamZoneId) -> {
+                                if (!ignoreZones.contains(amp.getZoneName(streamZoneId))) {
                                     try {
-                                        connector.sendCommand(MonopriceAudioZone.valueOf(zoneName),
-                                                MonopriceAudioCommand.MUTE, cmd);
+                                        connector.sendCommand(streamZoneId, amp.getMuteCmd(), cmd);
+                                        if (zoneDataMap.get(streamZoneId).isPowerOn()) {
+                                            zoneDataMap.get(streamZoneId).setMute(amp.getFormattedValue(cmd));
+                                            updateChannelState(streamZoneId, CHANNEL_TYPE_MUTE);
+                                        }
                                     } catch (MonopriceAudioException e) {
-                                        logger.warn("Error Setting Mute for All Zones: {}", e.getMessage());
+                                        logger.debug("Error Setting Mute for All Zones: {}", e.getMessage());
                                     }
                                 }
                             });
@@ -415,8 +403,9 @@ public class MonopriceAudioHandler extends BaseThingHandler implements Monoprice
                     logger.trace("Command {} from channel {} succeeded", command, channel);
                 }
             } catch (MonopriceAudioException e) {
-                logger.warn("Command {} from channel {} failed: {}", command, channel, e.getMessage());
-                updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Sending command failed");
+                logger.debug("Command {} from channel {} failed: {}", command, channel, e.getMessage());
+                updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
+                        "@text/offline.communication-error-failed");
                 closeConnection();
                 scheduleReconnectJob();
             }
@@ -424,7 +413,7 @@ public class MonopriceAudioHandler extends BaseThingHandler implements Monoprice
     }
 
     /**
-     * Open the connection with the MonopriceAudio device
+     * Open the connection to the amplifier
      *
      * @return true if the connection is opened successfully or false if not
      */
@@ -440,7 +429,7 @@ public class MonopriceAudioHandler extends BaseThingHandler implements Monoprice
     }
 
     /**
-     * Close the connection with the MonopriceAudio device
+     * Close the connection to the amplifier
      */
     private synchronized void closeConnection() {
         if (connector.isConnected()) {
@@ -453,36 +442,29 @@ public class MonopriceAudioHandler extends BaseThingHandler implements Monoprice
     @Override
     public void onNewMessageEvent(MonopriceAudioMessageEvent evt) {
         String key = evt.getKey();
-        String updateData = evt.getValue().trim();
-        if (!MonopriceAudioConnector.KEY_ERROR.equals(key)) {
-            updateStatus(ThingStatus.ONLINE);
-        }
-        try {
-            switch (key) {
-                case MonopriceAudioConnector.KEY_ERROR:
-                    logger.debug("Reading feedback message failed");
-                    updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Reading thread ended");
-                    closeConnection();
-                    break;
 
-                case MonopriceAudioConnector.KEY_ZONE_UPDATE:
-                    String zoneId = updateData.substring(0, 2);
-                    MonopriceAudioZoneDTO zoneDTO = zoneDataMap.get(zoneId);
-                    if (MonopriceAudioZone.VALID_ZONE_IDS.contains(zoneId) && zoneDTO != null) {
-                        MonopriceAudioZone targetZone = MonopriceAudioZone.fromZoneId(zoneId);
-                        processZoneUpdate(targetZone, zoneDTO, updateData);
+        switch (key) {
+            case MonopriceAudioConnector.KEY_ZONE_UPDATE:
+                MonopriceAudioZoneDTO newZoneData = amp.getZoneData(evt.getValue());
+                MonopriceAudioZoneDTO zoneData = zoneDataMap.get(newZoneData.getZone());
+                if (amp.getZoneIds().contains(newZoneData.getZone()) && zoneData != null) {
+                    if (amp == AmplifierModel.MONOPRICE70) {
+                        processMonoprice70Update(zoneData, newZoneData);
                     } else {
-                        logger.warn("invalid event: {} for key: {} or zone data null", evt.getValue(), key);
+                        processZoneUpdate(zoneData, newZoneData);
                     }
-                    break;
-                default:
-                    logger.debug("onNewMessageEvent: unhandled key {}", key);
-                    break;
-            }
-        } catch (NumberFormatException e) {
-            logger.warn("Invalid value {} for key {}", updateData, key);
-        } catch (MonopriceAudioException e) {
-            logger.warn("Error processing zone update: {}", e.getMessage());
+                } else {
+                    logger.debug("invalid event: {} for key: {} or zone data null", evt.getValue(), key);
+                }
+                break;
+
+            case MonopriceAudioConnector.KEY_PING:
+                lastPollingUpdate = System.currentTimeMillis();
+                break;
+
+            default:
+                logger.debug("onNewMessageEvent: unhandled key {}", key);
+                break;
         }
     }
 
@@ -500,24 +482,39 @@ public class MonopriceAudioHandler extends BaseThingHandler implements Monoprice
                     String error = null;
 
                     if (openConnection()) {
-                        try {
-                            long prevUpdateTime = lastPollingUpdate;
-                            connector.queryZone(MonopriceAudioZone.ZONE1);
-
-                            // prevUpdateTime should have changed if a zone update was received
-                            if (lastPollingUpdate == prevUpdateTime) {
-                                error = "Controller not responding to status requests";
+                        long prevUpdateTime = lastPollingUpdate;
+                        // poll all zones on the amplifier to get current state
+                        amp.getZoneIds().stream().limit(numZones).forEach((streamZoneId) -> {
+                            try {
+                                connector.queryZone(streamZoneId);
+
+                                if (amp == AmplifierModel.MONOPRICE70) {
+                                    connector.queryTrebBassBalance(streamZoneId);
+                                }
+                            } catch (MonopriceAudioException e) {
+                                logger.debug("Polling error: {}", e.getMessage());
+                            }
+                        });
+
+                        if (amp == AmplifierModel.XANTECH) {
+                            try {
+                                // for xantech send the commands to enable unsolicited updates
+                                connector.sendCommand("!ZA1");
+                                connector.sendCommand("!ZP10"); // Zone Periodic Auto Update set to 10 secs
+                            } catch (MonopriceAudioException e) {
+                                logger.debug("Error sending Xantech periodic update commands: {}", e.getMessage());
                             }
+                        }
 
-                        } catch (MonopriceAudioException e) {
-                            error = "First command after connection failed";
-                            logger.warn("{}: {}", error, e.getMessage());
-                            closeConnection();
+                        // prevUpdateTime should have changed if a zone update was received
+                        if (lastPollingUpdate == prevUpdateTime) {
+                            error = "@text/offline.communication-error-polling";
                         }
                     } else {
-                        error = "Reconnection failed";
+                        error = "@text/offline.communication-error-reconnection";
                     }
                     if (error != null) {
+                        closeConnection();
                         updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, error);
                     } else {
                         updateStatus(ThingStatus.ONLINE);
@@ -549,23 +546,32 @@ public class MonopriceAudioHandler extends BaseThingHandler implements Monoprice
         pollingJob = scheduler.scheduleWithFixedDelay(() -> {
             synchronized (sequenceLock) {
                 if (connector.isConnected()) {
-                    logger.debug("Polling the controller for updated status...");
-
-                    // poll each zone up to the number of zones specified in the configuration
-                    MonopriceAudioZone.VALID_ZONES.stream().limit(numZones).forEach((zoneName) -> {
+                    logger.debug("Polling the amplifier for updated status...");
+
+                    if (!disableKeypadPolling) {
+                        // poll each zone up to the number of zones specified in the configuration
+                        amp.getZoneIds().stream().limit(numZones).forEach((streamZoneId) -> {
+                            try {
+                                connector.queryZone(streamZoneId);
+                            } catch (MonopriceAudioException e) {
+                                logger.debug("Polling error for zone id {}: {}", streamZoneId, e.getMessage());
+                            }
+                        });
+                    } else {
                         try {
-                            connector.queryZone(MonopriceAudioZone.valueOf(zoneName));
+                            // ping only (no zone updates) to verify the connection is still alive
+                            connector.sendPing();
                         } catch (MonopriceAudioException e) {
-                            logger.warn("Polling error: {}", e.getMessage());
+                            logger.debug("Ping error: {}", e.getMessage());
                         }
-                    });
+                    }
 
-                    // if the last successful polling update was more than 2.25 intervals ago, the controller
+                    // if the last successful polling update was more than 2.25 intervals ago, the amplifier
                     // is either switched off or not responding even though the connection is still good
                     if ((System.currentTimeMillis() - lastPollingUpdate) > (pollingInterval * 2.25 * 1000)) {
-                        logger.warn("Controller not responding to status requests");
+                        logger.debug("Amplifier not responding to status requests");
                         updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
-                                "Controller not responding to status requests");
+                                "@text/offline.communication-error-polling");
                         closeConnection();
                         scheduleReconnectJob();
                     }
@@ -585,125 +591,143 @@ public class MonopriceAudioHandler extends BaseThingHandler implements Monoprice
         }
     }
 
-    /**
-     * Update the state of a channel
-     *
-     * @param channel the channel
-     */
-    private void updateChannelState(MonopriceAudioZone zone, String channelType, MonopriceAudioZoneDTO zoneData) {
-        String channel = zone.name().toLowerCase() + CHANNEL_DELIMIT + channelType;
+    private void processZoneUpdate(MonopriceAudioZoneDTO zoneData, MonopriceAudioZoneDTO newZoneData) {
+        // only process the update if something actually changed in this zone since the last polling update
+        if (!newZoneData.toString().equals(zoneData.toString())) {
+            if (!newZoneData.getPage().equals(zoneData.getPage())) {
+                zoneData.setPage(newZoneData.getPage());
+                updateChannelState(zoneData.getZone(), CHANNEL_TYPE_PAGE);
+            }
 
-        if (!isLinked(channel)) {
-            return;
-        }
+            if (!newZoneData.getPower().equals(zoneData.getPower())) {
+                zoneData.setPower(newZoneData.getPower());
+                updateChannelState(zoneData.getZone(), CHANNEL_TYPE_POWER);
+            }
 
-        State state = UnDefType.UNDEF;
-        switch (channelType) {
-            case CHANNEL_TYPE_POWER:
-                state = zoneData.isPowerOn() ? OnOffType.ON : OnOffType.OFF;
-                break;
-            case CHANNEL_TYPE_SOURCE:
-                state = new DecimalType(zoneData.getSource());
-                break;
-            case CHANNEL_TYPE_VOLUME:
-                long volumePct = Math.round(
-                        (double) (zoneData.getVolume() - MIN_VOLUME) / (double) (MAX_VOLUME - MIN_VOLUME) * 100.0);
-                state = new PercentType(BigDecimal.valueOf(volumePct));
-                break;
-            case CHANNEL_TYPE_MUTE:
-                state = zoneData.isMuted() ? OnOffType.ON : OnOffType.OFF;
-                break;
-            case CHANNEL_TYPE_TREBLE:
-                state = new DecimalType(BigDecimal.valueOf(zoneData.getTreble() - TONE_OFFSET));
-                break;
-            case CHANNEL_TYPE_BASS:
-                state = new DecimalType(BigDecimal.valueOf(zoneData.getBass() - TONE_OFFSET));
-                break;
-            case CHANNEL_TYPE_BALANCE:
-                state = new DecimalType(BigDecimal.valueOf(zoneData.getBalance() - BALANCE_OFFSET));
-                break;
-            case CHANNEL_TYPE_DND:
-                state = zoneData.isDndOn() ? OnOffType.ON : OnOffType.OFF;
-                break;
-            case CHANNEL_TYPE_PAGE:
-                state = zoneData.isPageActive() ? OpenClosedType.OPEN : OpenClosedType.CLOSED;
-                break;
-            case CHANNEL_TYPE_KEYPAD:
-                state = zoneData.isKeypadActive() ? OpenClosedType.OPEN : OpenClosedType.CLOSED;
-                break;
-            default:
-                break;
-        }
-        updateState(channel, state);
-    }
+            if (!newZoneData.getMute().equals(zoneData.getMute())) {
+                zoneData.setMute(newZoneData.getMute());
+                updateChannelState(zoneData.getZone(), CHANNEL_TYPE_MUTE);
+            }
 
-    private void processZoneUpdate(MonopriceAudioZone zone, MonopriceAudioZoneDTO zoneData, String newZoneData) {
-        // only process the update if something actually changed in this zone since the last time through
-        if (!newZoneData.equals(zoneData.toString())) {
-            // example status string: 1200010000130809100601, matcher pattern from above:
-            // "^(\\d{2})(\\d{2})(\\d{2})(\\d{2})(\\d{2})(\\d{2})(\\d{2})(\\d{2})(\\d{2})(\\d{2})(\\d{2})"
-            Matcher matcher = PATTERN.matcher(newZoneData);
-            if (matcher.find()) {
-                zoneData.setZone(matcher.group(1));
-
-                if (!matcher.group(2).equals(zoneData.getPage())) {
-                    zoneData.setPage(matcher.group(2));
-                    updateChannelState(zone, CHANNEL_TYPE_PAGE, zoneData);
-                }
+            if (!newZoneData.getDnd().equals(zoneData.getDnd())) {
+                zoneData.setDnd(newZoneData.getDnd());
+                updateChannelState(zoneData.getZone(), CHANNEL_TYPE_DND);
+            }
 
-                if (!matcher.group(3).equals(zoneData.getPower())) {
-                    zoneData.setPower(matcher.group(3));
-                    updateChannelState(zone, CHANNEL_TYPE_POWER, zoneData);
-                }
+            if (newZoneData.getVolume() != zoneData.getVolume()) {
+                zoneData.setVolume(newZoneData.getVolume());
+                updateChannelState(zoneData.getZone(), CHANNEL_TYPE_VOLUME);
+            }
 
-                if (!matcher.group(4).equals(zoneData.getMute())) {
-                    zoneData.setMute(matcher.group(4));
-                    updateChannelState(zone, CHANNEL_TYPE_MUTE, zoneData);
-                }
+            if (newZoneData.getTreble() != zoneData.getTreble()) {
+                zoneData.setTreble(newZoneData.getTreble());
+                updateChannelState(zoneData.getZone(), CHANNEL_TYPE_TREBLE);
+            }
 
-                if (!matcher.group(5).equals(zoneData.getDnd())) {
-                    zoneData.setDnd(matcher.group(5));
-                    updateChannelState(zone, CHANNEL_TYPE_DND, zoneData);
-                }
+            if (newZoneData.getBass() != zoneData.getBass()) {
+                zoneData.setBass(newZoneData.getBass());
+                updateChannelState(zoneData.getZone(), CHANNEL_TYPE_BASS);
+            }
 
-                int volume = Integer.parseInt(matcher.group(6));
-                if (volume != zoneData.getVolume()) {
-                    zoneData.setVolume(volume);
-                    updateChannelState(zone, CHANNEL_TYPE_VOLUME, zoneData);
-                }
+            if (newZoneData.getBalance() != zoneData.getBalance()) {
+                zoneData.setBalance(newZoneData.getBalance());
+                updateChannelState(zoneData.getZone(), CHANNEL_TYPE_BALANCE);
+            }
 
-                int treble = Integer.parseInt(matcher.group(7));
-                if (treble != zoneData.getTreble()) {
-                    zoneData.setTreble(treble);
-                    updateChannelState(zone, CHANNEL_TYPE_TREBLE, zoneData);
-                }
+            if (!newZoneData.getSource().equals(zoneData.getSource())) {
+                zoneData.setSource(newZoneData.getSource());
+                updateChannelState(zoneData.getZone(), CHANNEL_TYPE_SOURCE);
+            }
 
-                int bass = Integer.parseInt(matcher.group(8));
-                if (bass != zoneData.getBass()) {
-                    zoneData.setBass(bass);
-                    updateChannelState(zone, CHANNEL_TYPE_BASS, zoneData);
-                }
+            if (!newZoneData.getKeypad().equals(zoneData.getKeypad())) {
+                zoneData.setKeypad(newZoneData.getKeypad());
+                updateChannelState(zoneData.getZone(), CHANNEL_TYPE_KEYPAD);
+            }
 
-                int balance = Integer.parseInt(matcher.group(9));
-                if (balance != zoneData.getBalance()) {
-                    zoneData.setBalance(balance);
-                    updateChannelState(zone, CHANNEL_TYPE_BALANCE, zoneData);
-                }
+        }
+        lastPollingUpdate = System.currentTimeMillis();
+    }
 
-                if (!matcher.group(10).equals(zoneData.getSource())) {
-                    zoneData.setSource(matcher.group(10));
-                    updateChannelState(zone, CHANNEL_TYPE_SOURCE, zoneData);
-                }
+    private void processMonoprice70Update(MonopriceAudioZoneDTO zoneData, MonopriceAudioZoneDTO newZoneData) {
+        if (newZoneData.getTreble() != NIL) {
+            zoneData.setTreble(newZoneData.getTreble());
+            updateChannelState(zoneData.getZone(), CHANNEL_TYPE_TREBLE);
+        } else if (newZoneData.getBass() != NIL) {
+            zoneData.setBass(newZoneData.getBass());
+            updateChannelState(zoneData.getZone(), CHANNEL_TYPE_BASS);
+        } else if (newZoneData.getBalance() != NIL) {
+            zoneData.setBalance(newZoneData.getBalance());
+            updateChannelState(zoneData.getZone(), CHANNEL_TYPE_BALANCE);
+        } else {
+            zoneData.setPower(newZoneData.getPower());
+            zoneData.setMute(newZoneData.getMute());
+            zoneData.setVolume(newZoneData.getVolume());
+            zoneData.setSource(newZoneData.getSource());
+            updateChannelState(zoneData.getZone(), CHANNEL_TYPE_POWER);
+            updateChannelState(zoneData.getZone(), CHANNEL_TYPE_MUTE);
+            updateChannelState(zoneData.getZone(), CHANNEL_TYPE_VOLUME);
+            updateChannelState(zoneData.getZone(), CHANNEL_TYPE_SOURCE);
 
-                if (!matcher.group(11).equals(zoneData.getKeypad())) {
-                    zoneData.setKeypad(matcher.group(11));
-                    updateChannelState(zone, CHANNEL_TYPE_KEYPAD, zoneData);
-                }
-            } else {
-                logger.debug("Invalid zone update message: {}", newZoneData);
+        }
+        lastPollingUpdate = System.currentTimeMillis();
+    }
+
+    /**
+     * Update the state of a channel
+     *
+     * @param zoneId the zone id used to lookup the channel to be updated
+     * @param channelType the channel type to be updated
+     */
+    private void updateChannelState(String zoneId, String channelType) {
+        MonopriceAudioZoneDTO zoneData = zoneDataMap.get(zoneId);
+
+        if (zoneData != null) {
+            String channel = amp.getZoneName(zoneId) + CHANNEL_DELIMIT + channelType;
+
+            if (!isLinked(channel)) {
+                return;
             }
 
+            logger.debug("updating channel state for zone: {}, channel type: {}", zoneId, channelType);
+
+            State state = UnDefType.UNDEF;
+            switch (channelType) {
+                case CHANNEL_TYPE_POWER:
+                    state = OnOffType.from(zoneData.isPowerOn());
+                    break;
+                case CHANNEL_TYPE_SOURCE:
+                    state = new DecimalType(zoneData.getSource());
+                    break;
+                case CHANNEL_TYPE_VOLUME:
+                    long volumePct = Math.round(
+                            (zoneData.getVolume() - MIN_VOLUME) / (double) (amp.getMaxVol() - MIN_VOLUME) * 100.0);
+                    state = new PercentType(BigDecimal.valueOf(volumePct));
+                    break;
+                case CHANNEL_TYPE_MUTE:
+                    state = OnOffType.from(zoneData.isMuted());
+                    break;
+                case CHANNEL_TYPE_TREBLE:
+                    state = new DecimalType(BigDecimal.valueOf(zoneData.getTreble() - amp.getToneOffset()));
+                    break;
+                case CHANNEL_TYPE_BASS:
+                    state = new DecimalType(BigDecimal.valueOf(zoneData.getBass() - amp.getToneOffset()));
+                    break;
+                case CHANNEL_TYPE_BALANCE:
+                    state = new DecimalType(BigDecimal.valueOf(zoneData.getBalance() - amp.getBalOffset()));
+                    break;
+                case CHANNEL_TYPE_DND:
+                    state = OnOffType.from(zoneData.isDndOn());
+                    break;
+                case CHANNEL_TYPE_PAGE:
+                    state = zoneData.isPageActive() ? OpenClosedType.OPEN : OpenClosedType.CLOSED;
+                    break;
+                case CHANNEL_TYPE_KEYPAD:
+                    state = zoneData.isKeypadActive() ? OpenClosedType.OPEN : OpenClosedType.CLOSED;
+                    break;
+                default:
+                    break;
+            }
+            updateState(channel, state);
         }
-        lastPollingUpdate = System.currentTimeMillis();
     }
 }
index 7590ce56fa3ab11687a58c92a0a6be703820cb64..57217533397fb9afbb0c16486894505456b63457 100644 (file)
@@ -5,7 +5,8 @@
 
        <type>binding</type>
        <name>Monoprice Whole House Audio Binding</name>
-       <description>Controls Monoprice and Dayton Audio Whole House Amplifiers.</description>
+       <description>Controls Monoprice, Dayton Audio and Xantech Whole House Amplifiers.</description>
+
        <connection>local</connection>
 
 </addon:addon>
index dd1c897282bf3bd3e9304ea09c662f100341a7a1..5920f7336776d2d04aa53b872b0881379b216692 100644 (file)
@@ -1,7 +1,7 @@
 # add-on
 
 addon.monopriceaudio.name = Monoprice Whole House Audio Binding
-addon.monopriceaudio.description = Controls Monoprice and Dayton Audio Whole House Amplifiers.
+addon.monopriceaudio.description = Controls Monoprice, Dayton Audio and Xantech Whole House Amplifiers.
 
 # thing types
 
@@ -45,11 +45,85 @@ thing-type.monopriceaudio.amplifier.group.zone17.label = Zone 17
 thing-type.monopriceaudio.amplifier.group.zone17.description = The Controls for Zone 17
 thing-type.monopriceaudio.amplifier.group.zone18.label = Zone 18
 thing-type.monopriceaudio.amplifier.group.zone18.description = The Controls for Zone 18
+thing-type.monopriceaudio.dax88.label = Dayton DAX88 Amplifier
+thing-type.monopriceaudio.dax88.description = A Multi-zone Whole House Amplifier System
+thing-type.monopriceaudio.dax88.group.all.label = All Zones
+thing-type.monopriceaudio.dax88.group.all.description = Control All Zones Simultaneously
+thing-type.monopriceaudio.dax88.group.zone1.label = Zone 1
+thing-type.monopriceaudio.dax88.group.zone1.description = The Controls for Zone 1
+thing-type.monopriceaudio.dax88.group.zone2.label = Zone 2
+thing-type.monopriceaudio.dax88.group.zone2.description = The Controls for Zone 2
+thing-type.monopriceaudio.dax88.group.zone3.label = Zone 3
+thing-type.monopriceaudio.dax88.group.zone3.description = The Controls for Zone 3
+thing-type.monopriceaudio.dax88.group.zone4.label = Zone 4
+thing-type.monopriceaudio.dax88.group.zone4.description = The Controls for Zone 4
+thing-type.monopriceaudio.dax88.group.zone5.label = Zone 5
+thing-type.monopriceaudio.dax88.group.zone5.description = The Controls for Zone 5
+thing-type.monopriceaudio.dax88.group.zone6.label = Zone 6
+thing-type.monopriceaudio.dax88.group.zone6.description = The Controls for Zone 6
+thing-type.monopriceaudio.dax88.group.zone7.label = Zone 7
+thing-type.monopriceaudio.dax88.group.zone7.description = The Controls for Zone 7
+thing-type.monopriceaudio.dax88.group.zone8.label = Zone 8
+thing-type.monopriceaudio.dax88.group.zone8.description = The Controls for Zone 8
+thing-type.monopriceaudio.monoprice70.label = Monoprice 31028 70V Amplifier
+thing-type.monopriceaudio.monoprice70.description = A Multi-zone Whole House Amplifier System
+thing-type.monopriceaudio.monoprice70.group.all.label = All Zones
+thing-type.monopriceaudio.monoprice70.group.all.description = Control All Zones Simultaneously
+thing-type.monopriceaudio.monoprice70.group.zone1.label = Zone 1
+thing-type.monopriceaudio.monoprice70.group.zone1.description = The Controls for Zone 1
+thing-type.monopriceaudio.monoprice70.group.zone2.label = Zone 2
+thing-type.monopriceaudio.monoprice70.group.zone2.description = The Controls for Zone 2
+thing-type.monopriceaudio.monoprice70.group.zone3.label = Zone 3
+thing-type.monopriceaudio.monoprice70.group.zone3.description = The Controls for Zone 3
+thing-type.monopriceaudio.monoprice70.group.zone4.label = Zone 4
+thing-type.monopriceaudio.monoprice70.group.zone4.description = The Controls for Zone 4
+thing-type.monopriceaudio.monoprice70.group.zone5.label = Zone 5
+thing-type.monopriceaudio.monoprice70.group.zone5.description = The Controls for Zone 5
+thing-type.monopriceaudio.monoprice70.group.zone6.label = Zone 6
+thing-type.monopriceaudio.monoprice70.group.zone6.description = The Controls for Zone 6
+thing-type.monopriceaudio.xantech.label = Xantech 8x8 Amplifier
+thing-type.monopriceaudio.xantech.description = A Multi-zone Whole House Amplifier System
+thing-type.monopriceaudio.xantech.group.all.label = All Zones
+thing-type.monopriceaudio.xantech.group.all.description = Control All Zones Simultaneously
+thing-type.monopriceaudio.xantech.group.zone1.label = Zone 1
+thing-type.monopriceaudio.xantech.group.zone1.description = The Controls for Zone 1
+thing-type.monopriceaudio.xantech.group.zone2.label = Zone 2
+thing-type.monopriceaudio.xantech.group.zone2.description = The Controls for Zone 2
+thing-type.monopriceaudio.xantech.group.zone3.label = Zone 3
+thing-type.monopriceaudio.xantech.group.zone3.description = The Controls for Zone 3
+thing-type.monopriceaudio.xantech.group.zone4.label = Zone 4
+thing-type.monopriceaudio.xantech.group.zone4.description = The Controls for Zone 4
+thing-type.monopriceaudio.xantech.group.zone5.label = Zone 5
+thing-type.monopriceaudio.xantech.group.zone5.description = The Controls for Zone 5
+thing-type.monopriceaudio.xantech.group.zone6.label = Zone 6
+thing-type.monopriceaudio.xantech.group.zone6.description = The Controls for Zone 6
+thing-type.monopriceaudio.xantech.group.zone7.label = Zone 7
+thing-type.monopriceaudio.xantech.group.zone7.description = The Controls for Zone 7
+thing-type.monopriceaudio.xantech.group.zone8.label = Zone 8
+thing-type.monopriceaudio.xantech.group.zone8.description = The Controls for Zone 8
+thing-type.monopriceaudio.xantech.group.zone9.label = Zone 9
+thing-type.monopriceaudio.xantech.group.zone9.description = The Controls for Zone 9
+thing-type.monopriceaudio.xantech.group.zone10.label = Zone 10
+thing-type.monopriceaudio.xantech.group.zone10.description = The Controls for Zone 10
+thing-type.monopriceaudio.xantech.group.zone11.label = Zone 11
+thing-type.monopriceaudio.xantech.group.zone11.description = The Controls for Zone 11
+thing-type.monopriceaudio.xantech.group.zone12.label = Zone 12
+thing-type.monopriceaudio.xantech.group.zone12.description = The Controls for Zone 12
+thing-type.monopriceaudio.xantech.group.zone13.label = Zone 13
+thing-type.monopriceaudio.xantech.group.zone13.description = The Controls for Zone 13
+thing-type.monopriceaudio.xantech.group.zone14.label = Zone 14
+thing-type.monopriceaudio.xantech.group.zone14.description = The Controls for Zone 14
+thing-type.monopriceaudio.xantech.group.zone15.label = Zone 15
+thing-type.monopriceaudio.xantech.group.zone15.description = The Controls for Zone 15
+thing-type.monopriceaudio.xantech.group.zone16.label = Zone 16
+thing-type.monopriceaudio.xantech.group.zone16.description = The Controls for Zone 16
 
 # thing types config
 
+thing-type.config.monopriceaudio.amplifier.disableKeypadPolling.label = Disable Keypad Polling
+thing-type.config.monopriceaudio.amplifier.disableKeypadPolling.description = If physical keypads are not used, this option will disable polling the amplifier for zone updates
 thing-type.config.monopriceaudio.amplifier.host.label = Address
-thing-type.config.monopriceaudio.amplifier.host.description = Host Name or IP Address of the Amplifier or Serial over IP device
+thing-type.config.monopriceaudio.amplifier.host.description = Host Name or IP Address of the Monoprice Amplifier or Serial over IP device
 thing-type.config.monopriceaudio.amplifier.ignoreZones.label = Ignore Zones
 thing-type.config.monopriceaudio.amplifier.ignoreZones.description = (Optional) A Comma Seperated List of Zone Numbers That Will Ignore the 'All Zone' (Except All Off) Commands (ie: 1,6,10)
 thing-type.config.monopriceaudio.amplifier.initialAllVolume.label = Initial All Volume
@@ -69,16 +143,110 @@ thing-type.config.monopriceaudio.amplifier.inputLabel6.description = Friendly Na
 thing-type.config.monopriceaudio.amplifier.numZones.label = Number of Zones
 thing-type.config.monopriceaudio.amplifier.numZones.description = Number of Zones on the Amplifier to Utilize in the Binding (Up to 18 Zones With 3 Amplifiers Connected Together)
 thing-type.config.monopriceaudio.amplifier.pollingInterval.label = Polling Interval
-thing-type.config.monopriceaudio.amplifier.pollingInterval.description = Configures How Often to Poll the Controller to Check for Zone Updates (5-60; Default 15)
+thing-type.config.monopriceaudio.amplifier.pollingInterval.description = Configures How Often to Poll the Amplifier to Check for Zone Updates (5-60; Default 15)
 thing-type.config.monopriceaudio.amplifier.port.label = Port
-thing-type.config.monopriceaudio.amplifier.port.description = Communication Port for Serial over IP connection to the Amplifier (Default 8080 for amps with built-in Serial over IP)
+thing-type.config.monopriceaudio.amplifier.port.description = Communication Port for Serial over IP connection to the Monoprice Amplifier (8080 for amps with built-in Serial over IP)
 thing-type.config.monopriceaudio.amplifier.serialPort.label = Serial Port
 thing-type.config.monopriceaudio.amplifier.serialPort.description = Serial Port to Use for Connecting to the Monoprice Amplifier
+thing-type.config.monopriceaudio.dax88.disableKeypadPolling.label = Disable Keypad Polling
+thing-type.config.monopriceaudio.dax88.disableKeypadPolling.description = If physical keypads are not used, this option will disable polling the amplifier for zone updates
+thing-type.config.monopriceaudio.dax88.host.label = Address
+thing-type.config.monopriceaudio.dax88.host.description = Host Name or IP Address of the Machine Connected to the Dayton Amplifier (Serial over IP)
+thing-type.config.monopriceaudio.dax88.ignoreZones.label = Ignore Zones
+thing-type.config.monopriceaudio.dax88.ignoreZones.description = (Optional) A Comma Seperated List of Zone Numbers That Will Ignore the 'All Zone' (Except All Off) Commands (ie: 1,6,10)
+thing-type.config.monopriceaudio.dax88.initialAllVolume.label = Initial All Volume
+thing-type.config.monopriceaudio.dax88.initialAllVolume.description = When 'All' Zones Are Activated, the Volume Will Reset to This Value (1-30; default 10) to Prevent Excessive Blaring of Sound ;)
+thing-type.config.monopriceaudio.dax88.inputLabel1.label = Source 1 Input Label
+thing-type.config.monopriceaudio.dax88.inputLabel1.description = Friendly Name for the Input Source to Be Displayed in the UI (ie: Chromecast, Radio, CD, etc.)
+thing-type.config.monopriceaudio.dax88.inputLabel2.label = Source 2 Input Label
+thing-type.config.monopriceaudio.dax88.inputLabel2.description = Friendly Name for the Input Source to Be Displayed in the UI (ie: Chromecast, Radio, CD, etc.)
+thing-type.config.monopriceaudio.dax88.inputLabel3.label = Source 3 Input Label
+thing-type.config.monopriceaudio.dax88.inputLabel3.description = Friendly Name for the Input Source to Be Displayed in the UI (ie: Chromecast, Radio, CD, etc.)
+thing-type.config.monopriceaudio.dax88.inputLabel4.label = Source 4 Input Label
+thing-type.config.monopriceaudio.dax88.inputLabel4.description = Friendly Name for the Input Source to Be Displayed in the UI (ie: Chromecast, Radio, CD, etc.)
+thing-type.config.monopriceaudio.dax88.inputLabel5.label = Source 5 Input Label
+thing-type.config.monopriceaudio.dax88.inputLabel5.description = Friendly Name for the Input Source to Be Displayed in the UI (ie: Chromecast, Radio, CD, etc.)
+thing-type.config.monopriceaudio.dax88.inputLabel6.label = Source 6 Input Label
+thing-type.config.monopriceaudio.dax88.inputLabel6.description = Friendly Name for the Input Source to Be Displayed in the UI (ie: Chromecast, Radio, CD, etc.)
+thing-type.config.monopriceaudio.dax88.inputLabel7.label = Source 7 Input Label
+thing-type.config.monopriceaudio.dax88.inputLabel7.description = Friendly Name for the Input Source to Be Displayed in the UI (ie: Chromecast, Radio, CD, etc.)
+thing-type.config.monopriceaudio.dax88.inputLabel8.label = Source 8 Input Label
+thing-type.config.monopriceaudio.dax88.inputLabel8.description = Friendly Name for the Input Source to Be Displayed in the UI (ie: Chromecast, Radio, CD, etc.)
+thing-type.config.monopriceaudio.dax88.numZones.label = Number of Zones
+thing-type.config.monopriceaudio.dax88.numZones.description = Number of Zones on the Amplifier to Utilize in the Binding
+thing-type.config.monopriceaudio.dax88.pollingInterval.label = Polling Interval
+thing-type.config.monopriceaudio.dax88.pollingInterval.description = Configures How Often to Poll the Amplifier to Check for Zone Updates (5-60; Default 15)
+thing-type.config.monopriceaudio.dax88.port.label = Port
+thing-type.config.monopriceaudio.dax88.port.description = Communication Port for Serial over IP connection to the Dayton Amplifier
+thing-type.config.monopriceaudio.dax88.serialPort.label = Serial Port
+thing-type.config.monopriceaudio.dax88.serialPort.description = Serial Port to Use for Connecting to the Dayton Amplifier
+thing-type.config.monopriceaudio.monoprice70.host.label = Address
+thing-type.config.monopriceaudio.monoprice70.host.description = Host Name or IP Address of the Machine Connected to the Monoprice Amplifier (Serial over IP)
+thing-type.config.monopriceaudio.monoprice70.ignoreZones.label = Ignore Zones
+thing-type.config.monopriceaudio.monoprice70.ignoreZones.description = (Optional) A Comma Seperated List of Zone Numbers That Will Ignore the 'All Zone' (Except All Off) Commands (ie: 1,5,6)
+thing-type.config.monopriceaudio.monoprice70.initialAllVolume.label = Initial All Volume
+thing-type.config.monopriceaudio.monoprice70.initialAllVolume.description = When 'All' Zones Are Activated, the Volume Will Reset to This Value (1-30; default 10) to Prevent Excessive Blaring of Sound ;)
+thing-type.config.monopriceaudio.monoprice70.inputLabel1.label = Source 0 Input Label
+thing-type.config.monopriceaudio.monoprice70.inputLabel1.description = Friendly Name for the Input Source to Be Displayed in the UI (ie: Chromecast, Radio, CD, etc.)
+thing-type.config.monopriceaudio.monoprice70.inputLabel2.label = Source 1 Input Label
+thing-type.config.monopriceaudio.monoprice70.inputLabel2.description = Friendly Name for the Input Source to Be Displayed in the UI (ie: Chromecast, Radio, CD, etc.)
+thing-type.config.monopriceaudio.monoprice70.numZones.label = Number of Zones
+thing-type.config.monopriceaudio.monoprice70.numZones.description = Number of Zones on the Amplifier to Utilize in the Binding
+thing-type.config.monopriceaudio.monoprice70.pollingInterval.label = Polling Interval
+thing-type.config.monopriceaudio.monoprice70.pollingInterval.description = Configures How Often to Poll the Amplifier to Check for Zone Updates (5-60; Default 30)
+thing-type.config.monopriceaudio.monoprice70.port.label = Port
+thing-type.config.monopriceaudio.monoprice70.port.description = Communication Port for Serial over IP connection to the Monoprice Amplifier
+thing-type.config.monopriceaudio.monoprice70.serialPort.label = Serial Port
+thing-type.config.monopriceaudio.monoprice70.serialPort.description = Serial Port to Use for Connecting to the Monoprice Amplifier
+thing-type.config.monopriceaudio.xantech.disableKeypadPolling.label = Disable Keypad Polling
+thing-type.config.monopriceaudio.xantech.disableKeypadPolling.description = If physical keypads are not used, this option will disable polling the amplifier for zone updates
+thing-type.config.monopriceaudio.xantech.host.label = Address
+thing-type.config.monopriceaudio.xantech.host.description = Host Name or IP Address of the Machine Connected to the Xantech Amplifier (Serial over IP)
+thing-type.config.monopriceaudio.xantech.ignoreZones.label = Ignore Zones
+thing-type.config.monopriceaudio.xantech.ignoreZones.description = (Optional) A Comma Seperated List of Zone Numbers That Will Ignore the 'All Zone' (Except All Off) Commands (ie: 1,6,10)
+thing-type.config.monopriceaudio.xantech.initialAllVolume.label = Initial All Volume
+thing-type.config.monopriceaudio.xantech.initialAllVolume.description = When 'All' Zones Are Activated, the Volume Will Reset to This Value (1-30; default 10) to Prevent Excessive Blaring of Sound ;)
+thing-type.config.monopriceaudio.xantech.inputLabel1.label = Source 1 Input Label
+thing-type.config.monopriceaudio.xantech.inputLabel1.description = Friendly Name for the Input Source to Be Displayed in the UI (ie: Chromecast, Radio, CD, etc.)
+thing-type.config.monopriceaudio.xantech.inputLabel2.label = Source 2 Input Label
+thing-type.config.monopriceaudio.xantech.inputLabel2.description = Friendly Name for the Input Source to Be Displayed in the UI (ie: Chromecast, Radio, CD, etc.)
+thing-type.config.monopriceaudio.xantech.inputLabel3.label = Source 3 Input Label
+thing-type.config.monopriceaudio.xantech.inputLabel3.description = Friendly Name for the Input Source to Be Displayed in the UI (ie: Chromecast, Radio, CD, etc.)
+thing-type.config.monopriceaudio.xantech.inputLabel4.label = Source 4 Input Label
+thing-type.config.monopriceaudio.xantech.inputLabel4.description = Friendly Name for the Input Source to Be Displayed in the UI (ie: Chromecast, Radio, CD, etc.)
+thing-type.config.monopriceaudio.xantech.inputLabel5.label = Source 5 Input Label
+thing-type.config.monopriceaudio.xantech.inputLabel5.description = Friendly Name for the Input Source to Be Displayed in the UI (ie: Chromecast, Radio, CD, etc.)
+thing-type.config.monopriceaudio.xantech.inputLabel6.label = Source 6 Input Label
+thing-type.config.monopriceaudio.xantech.inputLabel6.description = Friendly Name for the Input Source to Be Displayed in the UI (ie: Chromecast, Radio, CD, etc.)
+thing-type.config.monopriceaudio.xantech.inputLabel7.label = Source 7 Input Label
+thing-type.config.monopriceaudio.xantech.inputLabel7.description = Friendly Name for the Input Source to Be Displayed in the UI (ie: Chromecast, Radio, CD, etc.)
+thing-type.config.monopriceaudio.xantech.inputLabel8.label = Source 8 Input Label
+thing-type.config.monopriceaudio.xantech.inputLabel8.description = Friendly Name for the Input Source to Be Displayed in the UI (ie: Chromecast, Radio, CD, etc.)
+thing-type.config.monopriceaudio.xantech.numZones.label = Number of Zones
+thing-type.config.monopriceaudio.xantech.numZones.description = Number of Zones on the Amplifier to Utilize in the Binding (Up to 16 Zones With 2 Amplifiers Connected Together)
+thing-type.config.monopriceaudio.xantech.pollingInterval.label = Polling Interval
+thing-type.config.monopriceaudio.xantech.pollingInterval.description = Configures How Often to Poll the Amplifier to Check for Zone Updates (5-60; Default 30)
+thing-type.config.monopriceaudio.xantech.port.label = Port
+thing-type.config.monopriceaudio.xantech.port.description = Communication Port for Serial over IP connection to the Xantech Amplifier
+thing-type.config.monopriceaudio.xantech.serialPort.label = Serial Port
+thing-type.config.monopriceaudio.xantech.serialPort.description = Serial Port to Use for Connecting to the Xantech Amplifier
 
 # channel group types
 
 channel-group-type.monopriceaudio.all.label = All Zones
 channel-group-type.monopriceaudio.all.description = Control All Zones Simultaneously
+channel-group-type.monopriceaudio.dax88-all.label = All Zones
+channel-group-type.monopriceaudio.dax88-all.description = Control All Zones Simultaneously
+channel-group-type.monopriceaudio.dax88-zone.label = Zone Controls
+channel-group-type.monopriceaudio.dax88-zone.description = The Controls for the Zone
+channel-group-type.monopriceaudio.monoprice70-all.label = All Zones
+channel-group-type.monopriceaudio.monoprice70-all.description = Control All Zones Simultaneously
+channel-group-type.monopriceaudio.monoprice70-zone.label = Zone Controls
+channel-group-type.monopriceaudio.monoprice70-zone.description = The Controls for the Zone
+channel-group-type.monopriceaudio.xantech-all.label = All Zones
+channel-group-type.monopriceaudio.xantech-all.description = Control All Zones Simultaneously
+channel-group-type.monopriceaudio.xantech-zone.label = Zone Controls
+channel-group-type.monopriceaudio.xantech-zone.description = The Controls for the Zone
 channel-group-type.monopriceaudio.zone.label = Zone Controls
 channel-group-type.monopriceaudio.zone.description = The Controls for the Zone
 
@@ -90,12 +258,42 @@ channel-type.monopriceaudio.balance.label = Balance Adjustment
 channel-type.monopriceaudio.balance.description = Adjust the Balance
 channel-type.monopriceaudio.bass.label = Bass Adjustment
 channel-type.monopriceaudio.bass.description = Adjust the Bass
+channel-type.monopriceaudio.dax88-allpower.label = All On
+channel-type.monopriceaudio.dax88-allpower.description = Turn All Zones On or Off
+channel-type.monopriceaudio.dax88-balance.label = Balance Adjustment
+channel-type.monopriceaudio.dax88-balance.description = Adjust the Balance
+channel-type.monopriceaudio.dax88-bass.label = Bass Adjustment
+channel-type.monopriceaudio.dax88-bass.description = Adjust the Bass
+channel-type.monopriceaudio.dax88-dnd.label = Do Not Disturb
+channel-type.monopriceaudio.dax88-dnd.description = Controls if the Zone Should Ignore an Incoming Audio Page
+channel-type.monopriceaudio.dax88-keypad.label = Keypad Connected
+channel-type.monopriceaudio.dax88-keypad.description = Indicates if a Physical Keypad is Attached to This Zone
+channel-type.monopriceaudio.dax88-keypad.state.option.CLOSED = Disconnected
+channel-type.monopriceaudio.dax88-keypad.state.option.OPEN = Connected
+channel-type.monopriceaudio.dax88-page.label = Page Active
+channel-type.monopriceaudio.dax88-page.description = Indicates if the Page Mode is Active for This Zone
+channel-type.monopriceaudio.dax88-page.state.option.CLOSED = Inactive
+channel-type.monopriceaudio.dax88-page.state.option.OPEN = Active
+channel-type.monopriceaudio.dax88-source.label = Source Input
+channel-type.monopriceaudio.dax88-source.description = Select the Source Input
+channel-type.monopriceaudio.dax88-treble.label = Treble Adjustment
+channel-type.monopriceaudio.dax88-treble.description = Adjust the Treble
 channel-type.monopriceaudio.dnd.label = Do Not Disturb
 channel-type.monopriceaudio.dnd.description = Controls if the Zone Should Ignore an Incoming Audio Page
 channel-type.monopriceaudio.keypad.label = Keypad Connected
 channel-type.monopriceaudio.keypad.description = Indicates if a Physical Keypad is Attached to This Zone
 channel-type.monopriceaudio.keypad.state.option.CLOSED = Disconnected
 channel-type.monopriceaudio.keypad.state.option.OPEN = Connected
+channel-type.monopriceaudio.monoprice70-allpower.label = All On
+channel-type.monopriceaudio.monoprice70-allpower.description = Turn All Zones On or Off
+channel-type.monopriceaudio.monoprice70-balance.label = Balance Adjustment
+channel-type.monopriceaudio.monoprice70-balance.description = Adjust the Balance
+channel-type.monopriceaudio.monoprice70-bass.label = Bass Adjustment
+channel-type.monopriceaudio.monoprice70-bass.description = Adjust the Bass
+channel-type.monopriceaudio.monoprice70-source.label = Source Input
+channel-type.monopriceaudio.monoprice70-source.description = Select the Source Input
+channel-type.monopriceaudio.monoprice70-treble.label = Treble Adjustment
+channel-type.monopriceaudio.monoprice70-treble.description = Adjust the Treble
 channel-type.monopriceaudio.page.label = Page Active
 channel-type.monopriceaudio.page.description = Indicates if the Page Mode is Active for This Zone
 channel-type.monopriceaudio.page.state.option.CLOSED = Inactive
@@ -104,3 +302,30 @@ channel-type.monopriceaudio.source.label = Source Input
 channel-type.monopriceaudio.source.description = Select the Source Input
 channel-type.monopriceaudio.treble.label = Treble Adjustment
 channel-type.monopriceaudio.treble.description = Adjust the Treble
+channel-type.monopriceaudio.xantech-allpower.label = All On
+channel-type.monopriceaudio.xantech-allpower.description = Turn All Zones On or Off
+channel-type.monopriceaudio.xantech-balance.label = Balance Adjustment
+channel-type.monopriceaudio.xantech-balance.description = Adjust the Balance
+channel-type.monopriceaudio.xantech-bass.label = Bass Adjustment
+channel-type.monopriceaudio.xantech-bass.description = Adjust the Bass
+channel-type.monopriceaudio.xantech-keypad.label = Keypad Connected
+channel-type.monopriceaudio.xantech-keypad.description = Indicates if a Physical Keypad is Attached to This Zone
+channel-type.monopriceaudio.xantech-keypad.state.option.CLOSED = Disconnected
+channel-type.monopriceaudio.xantech-keypad.state.option.OPEN = Connected
+channel-type.monopriceaudio.xantech-page.label = Page Active
+channel-type.monopriceaudio.xantech-page.description = Indicates if the Page Mode is Active for This Zone
+channel-type.monopriceaudio.xantech-page.state.option.CLOSED = Inactive
+channel-type.monopriceaudio.xantech-page.state.option.OPEN = Active
+channel-type.monopriceaudio.xantech-source.label = Source Input
+channel-type.monopriceaudio.xantech-source.description = Select the Source Input
+channel-type.monopriceaudio.xantech-treble.label = Treble Adjustment
+channel-type.monopriceaudio.xantech-treble.description = Adjust the Treble
+
+# message strings
+
+offline.configuration-error-rfc2217 = Use Host and Port configuration settings for a serial over IP connection
+offline.configuration-error-conflict = Serial port cannot be used at the same time that Host & Port is used
+offline.configuration-error-missing = Either Serial port or Host & Port must be specifed
+offline.communication-error-failed = Sending command failed
+offline.communication-error-polling = Amplifier not responding to status requests
+offline.communication-error-reconnection = Reconnection failed
diff --git a/bundles/org.openhab.binding.monopriceaudio/src/main/resources/OH-INF/thing/channels.xml b/bundles/org.openhab.binding.monopriceaudio/src/main/resources/OH-INF/thing/channels.xml
deleted file mode 100644 (file)
index 105a14f..0000000
+++ /dev/null
@@ -1,258 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<thing:thing-descriptions bindingId="monopriceaudio"
-       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-       xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
-       xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
-
-       <!-- Monoprice 10761/39261/DAX66 Amplifier Thing -->
-       <thing-type id="amplifier">
-               <label>Monoprice 10761 Amplifier, 39261 Matrix or Dayton DAX66 Amplifier</label>
-               <description>
-                       A Multi-zone Whole House Amplifier System
-               </description>
-
-               <channel-groups>
-                       <channel-group id="all" typeId="all">
-                               <label>All Zones</label>
-                               <description>Control All Zones Simultaneously</description>
-                       </channel-group>
-                       <channel-group id="zone1" typeId="zone">
-                               <label>Zone 1</label>
-                               <description>The Controls for Zone 1</description>
-                       </channel-group>
-                       <channel-group id="zone2" typeId="zone">
-                               <label>Zone 2</label>
-                               <description>The Controls for Zone 2</description>
-                       </channel-group>
-                       <channel-group id="zone3" typeId="zone">
-                               <label>Zone 3</label>
-                               <description>The Controls for Zone 3</description>
-                       </channel-group>
-                       <channel-group id="zone4" typeId="zone">
-                               <label>Zone 4</label>
-                               <description>The Controls for Zone 4</description>
-                       </channel-group>
-                       <channel-group id="zone5" typeId="zone">
-                               <label>Zone 5</label>
-                               <description>The Controls for Zone 5</description>
-                       </channel-group>
-                       <channel-group id="zone6" typeId="zone">
-                               <label>Zone 6</label>
-                               <description>The Controls for Zone 6</description>
-                       </channel-group>
-                       <channel-group id="zone7" typeId="zone">
-                               <label>Zone 7</label>
-                               <description>The Controls for Zone 7</description>
-                       </channel-group>
-                       <channel-group id="zone8" typeId="zone">
-                               <label>Zone 8</label>
-                               <description>The Controls for Zone 8</description>
-                       </channel-group>
-                       <channel-group id="zone9" typeId="zone">
-                               <label>Zone 9</label>
-                               <description>The Controls for Zone 9</description>
-                       </channel-group>
-                       <channel-group id="zone10" typeId="zone">
-                               <label>Zone 10</label>
-                               <description>The Controls for Zone 10</description>
-                       </channel-group>
-                       <channel-group id="zone11" typeId="zone">
-                               <label>Zone 11</label>
-                               <description>The Controls for Zone 11</description>
-                       </channel-group>
-                       <channel-group id="zone12" typeId="zone">
-                               <label>Zone 12</label>
-                               <description>The Controls for Zone 12</description>
-                       </channel-group>
-                       <channel-group id="zone13" typeId="zone">
-                               <label>Zone 13</label>
-                               <description>The Controls for Zone 13</description>
-                       </channel-group>
-                       <channel-group id="zone14" typeId="zone">
-                               <label>Zone 14</label>
-                               <description>The Controls for Zone 14</description>
-                       </channel-group>
-                       <channel-group id="zone15" typeId="zone">
-                               <label>Zone 15</label>
-                               <description>The Controls for Zone 15</description>
-                       </channel-group>
-                       <channel-group id="zone16" typeId="zone">
-                               <label>Zone 16</label>
-                               <description>The Controls for Zone 16</description>
-                       </channel-group>
-                       <channel-group id="zone17" typeId="zone">
-                               <label>Zone 17</label>
-                               <description>The Controls for Zone 17</description>
-                       </channel-group>
-                       <channel-group id="zone18" typeId="zone">
-                               <label>Zone 18</label>
-                               <description>The Controls for Zone 18</description>
-                       </channel-group>
-               </channel-groups>
-
-               <config-description>
-                       <parameter name="serialPort" type="text" required="false">
-                               <context>serial-port</context>
-                               <limitToOptions>false</limitToOptions>
-                               <label>Serial Port</label>
-                               <description>Serial Port to Use for Connecting to the Monoprice Amplifier</description>
-                       </parameter>
-                       <parameter name="host" type="text" required="false">
-                               <context>network-address</context>
-                               <label>Address</label>
-                               <description>Host Name or IP Address of the Amplifier or Serial over IP device</description>
-                       </parameter>
-                       <parameter name="port" type="integer" min="1" max="65535" required="false">
-                               <label>Port</label>
-                               <description>Communication Port for Serial over IP connection to the Amplifier (Default 8080 for amps with built-in
-                                       Serial over IP)</description>
-                               <default>8080</default>
-                       </parameter>
-                       <parameter name="numZones" type="integer" min="1" max="18" required="true">
-                               <label>Number of Zones</label>
-                               <description>Number of Zones on the Amplifier to Utilize in the Binding (Up to 18 Zones With 3 Amplifiers Connected
-                                       Together)</description>
-                               <default>6</default>
-                       </parameter>
-                       <parameter name="pollingInterval" type="integer" min="5" max="60" unit="s" required="false">
-                               <label>Polling Interval</label>
-                               <description>Configures How Often to Poll the Controller to Check for Zone Updates (5-60; Default 15)</description>
-                               <default>15</default>
-                       </parameter>
-                       <parameter name="ignoreZones" type="text" required="false">
-                               <label>Ignore Zones</label>
-                               <description>(Optional) A Comma Seperated List of Zone Numbers That Will Ignore the 'All Zone' (Except All Off)
-                                       Commands (ie: 1,6,10)</description>
-                       </parameter>
-                       <parameter name="initialAllVolume" type="integer" min="1" max="30" required="false">
-                               <label>Initial All Volume</label>
-                               <description>When 'All' Zones Are Activated, the Volume Will Reset to This Value (1-30; default 10) to Prevent
-                                       Excessive Blaring of Sound ;)</description>
-                               <default>10</default>
-                       </parameter>
-                       <parameter name="inputLabel1" type="text" required="false">
-                               <label>Source 1 Input Label</label>
-                               <description>Friendly Name for the Input Source to Be Displayed in the UI (ie: Chromecast, Radio, CD, etc.)</description>
-                               <default>Source 1</default>
-                       </parameter>
-                       <parameter name="inputLabel2" type="text" required="false">
-                               <label>Source 2 Input Label</label>
-                               <description>Friendly Name for the Input Source to Be Displayed in the UI (ie: Chromecast, Radio, CD, etc.)</description>
-                               <default>Source 2</default>
-                       </parameter>
-                       <parameter name="inputLabel3" type="text" required="false">
-                               <label>Source 3 Input Label</label>
-                               <description>Friendly Name for the Input Source to Be Displayed in the UI (ie: Chromecast, Radio, CD, etc.)</description>
-                               <default>Source 3</default>
-                       </parameter>
-                       <parameter name="inputLabel4" type="text" required="false">
-                               <label>Source 4 Input Label</label>
-                               <description>Friendly Name for the Input Source to Be Displayed in the UI (ie: Chromecast, Radio, CD, etc.)</description>
-                               <default>Source 4</default>
-                       </parameter>
-                       <parameter name="inputLabel5" type="text" required="false">
-                               <label>Source 5 Input Label</label>
-                               <description>Friendly Name for the Input Source to Be Displayed in the UI (ie: Chromecast, Radio, CD, etc.)</description>
-                               <default>Source 5</default>
-                       </parameter>
-                       <parameter name="inputLabel6" type="text" required="false">
-                               <label>Source 6 Input Label</label>
-                               <description>Friendly Name for the Input Source to Be Displayed in the UI (ie: Chromecast, Radio, CD, etc.)</description>
-                               <default>Source 6</default>
-                       </parameter>
-               </config-description>
-       </thing-type>
-
-       <channel-group-type id="all">
-               <label>All Zones</label>
-               <description>Control All Zones Simultaneously</description>
-               <channels>
-                       <channel id="allpower" typeId="allpower"/>
-                       <channel id="allsource" typeId="source"/>
-                       <channel id="allvolume" typeId="system.volume"/>
-                       <channel id="allmute" typeId="system.mute"/>
-               </channels>
-       </channel-group-type>
-
-       <channel-group-type id="zone">
-               <label>Zone Controls</label>
-               <description>The Controls for the Zone</description>
-               <channels>
-                       <channel id="power" typeId="system.power"/>
-                       <channel id="source" typeId="source"/>
-                       <channel id="volume" typeId="system.volume"/>
-                       <channel id="mute" typeId="system.mute"/>
-                       <channel id="treble" typeId="treble"/>
-                       <channel id="bass" typeId="bass"/>
-                       <channel id="balance" typeId="balance"/>
-                       <channel id="dnd" typeId="dnd"/>
-                       <channel id="page" typeId="page"/>
-                       <channel id="keypad" typeId="keypad"/>
-               </channels>
-       </channel-group-type>
-
-       <channel-type id="allpower">
-               <item-type>Switch</item-type>
-               <label>All On</label>
-               <description>Turn All Zones On or Off</description>
-       </channel-type>
-
-       <channel-type id="source">
-               <item-type>Number</item-type>
-               <label>Source Input</label>
-               <description>Select the Source Input</description>
-               <state min="1" max="6"/>
-       </channel-type>
-
-       <channel-type id="treble">
-               <item-type>Number</item-type>
-               <label>Treble Adjustment</label>
-               <description>Adjust the Treble</description>
-               <state min="-7" max="7" step="1" pattern="%d"/>
-       </channel-type>
-
-       <channel-type id="bass">
-               <item-type>Number</item-type>
-               <label>Bass Adjustment</label>
-               <description>Adjust the Bass</description>
-               <state min="-7" max="7" step="1" pattern="%d"/>
-       </channel-type>
-
-       <channel-type id="balance">
-               <item-type>Number</item-type>
-               <label>Balance Adjustment</label>
-               <description>Adjust the Balance</description>
-               <state min="-10" max="10" step="1" pattern="%d"/>
-       </channel-type>
-
-       <channel-type id="dnd">
-               <item-type>Switch</item-type>
-               <label>Do Not Disturb</label>
-               <description>Controls if the Zone Should Ignore an Incoming Audio Page</description>
-       </channel-type>
-
-       <channel-type id="page">
-               <item-type>Contact</item-type>
-               <label>Page Active</label>
-               <description>Indicates if the Page Mode is Active for This Zone</description>
-               <state readOnly="true">
-                       <options>
-                               <option value="CLOSED">Inactive</option>
-                               <option value="OPEN">Active</option>
-                       </options>
-               </state>
-       </channel-type>
-
-       <channel-type id="keypad">
-               <item-type>Contact</item-type>
-               <label>Keypad Connected</label>
-               <description>Indicates if a Physical Keypad is Attached to This Zone</description>
-               <state readOnly="true">
-                       <options>
-                               <option value="CLOSED">Disconnected</option>
-                               <option value="OPEN">Connected</option>
-                       </options>
-               </state>
-       </channel-type>
-
-</thing:thing-descriptions>
diff --git a/bundles/org.openhab.binding.monopriceaudio/src/main/resources/OH-INF/thing/dax88.xml b/bundles/org.openhab.binding.monopriceaudio/src/main/resources/OH-INF/thing/dax88.xml
new file mode 100644 (file)
index 0000000..8683f7d
--- /dev/null
@@ -0,0 +1,230 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<thing:thing-descriptions bindingId="monopriceaudio"
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
+       xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
+
+       <!-- Dayton DAX88 Amplifier Thing -->
+       <thing-type id="dax88">
+               <label>Dayton DAX88 Amplifier</label>
+               <description>
+                       A Multi-zone Whole House Amplifier System
+               </description>
+
+               <channel-groups>
+                       <channel-group id="all" typeId="dax88-all">
+                               <label>All Zones</label>
+                               <description>Control All Zones Simultaneously</description>
+                       </channel-group>
+                       <channel-group id="zone1" typeId="dax88-zone">
+                               <label>Zone 1</label>
+                               <description>The Controls for Zone 1</description>
+                       </channel-group>
+                       <channel-group id="zone2" typeId="dax88-zone">
+                               <label>Zone 2</label>
+                               <description>The Controls for Zone 2</description>
+                       </channel-group>
+                       <channel-group id="zone3" typeId="dax88-zone">
+                               <label>Zone 3</label>
+                               <description>The Controls for Zone 3</description>
+                       </channel-group>
+                       <channel-group id="zone4" typeId="dax88-zone">
+                               <label>Zone 4</label>
+                               <description>The Controls for Zone 4</description>
+                       </channel-group>
+                       <channel-group id="zone5" typeId="dax88-zone">
+                               <label>Zone 5</label>
+                               <description>The Controls for Zone 5</description>
+                       </channel-group>
+                       <channel-group id="zone6" typeId="dax88-zone">
+                               <label>Zone 6</label>
+                               <description>The Controls for Zone 6</description>
+                       </channel-group>
+                       <channel-group id="zone7" typeId="dax88-zone">
+                               <label>Zone 7</label>
+                               <description>The Controls for Zone 7</description>
+                       </channel-group>
+                       <channel-group id="zone8" typeId="dax88-zone">
+                               <label>Zone 8</label>
+                               <description>The Controls for Zone 8</description>
+                       </channel-group>
+               </channel-groups>
+
+               <config-description>
+                       <parameter name="serialPort" type="text" required="false">
+                               <context>serial-port</context>
+                               <limitToOptions>false</limitToOptions>
+                               <label>Serial Port</label>
+                               <description>Serial Port to Use for Connecting to the Dayton Amplifier</description>
+                       </parameter>
+                       <parameter name="host" type="text" required="false">
+                               <context>network-address</context>
+                               <label>Address</label>
+                               <description>Host Name or IP Address of the Machine Connected to the Dayton Amplifier (Serial over IP)</description>
+                       </parameter>
+                       <parameter name="port" type="integer" min="1" max="65535" required="false">
+                               <label>Port</label>
+                               <description>Communication Port for Serial over IP connection to the Dayton Amplifier</description>
+                       </parameter>
+                       <parameter name="numZones" type="integer" min="1" max="8" required="true">
+                               <label>Number of Zones</label>
+                               <description>Number of Zones on the Amplifier to Utilize in the Binding</description>
+                               <default>8</default>
+                       </parameter>
+                       <parameter name="pollingInterval" type="integer" min="5" max="60" unit="s" required="false">
+                               <label>Polling Interval</label>
+                               <description>Configures How Often to Poll the Amplifier to Check for Zone Updates (5-60; Default 15)</description>
+                               <default>15</default>
+                       </parameter>
+                       <parameter name="ignoreZones" type="text" required="false">
+                               <label>Ignore Zones</label>
+                               <description>(Optional) A Comma Seperated List of Zone Numbers That Will Ignore the 'All Zone' (Except All Off)
+                                       Commands (ie: 1,6,10)</description>
+                       </parameter>
+                       <parameter name="initialAllVolume" type="integer" min="1" max="30" required="false">
+                               <label>Initial All Volume</label>
+                               <description>When 'All' Zones Are Activated, the Volume Will Reset to This Value (1-30; default 10) to Prevent
+                                       Excessive Blaring of Sound ;)</description>
+                               <default>10</default>
+                       </parameter>
+                       <parameter name="inputLabel1" type="text" required="false">
+                               <label>Source 1 Input Label</label>
+                               <description>Friendly Name for the Input Source to Be Displayed in the UI (ie: Chromecast, Radio, CD, etc.)</description>
+                               <default>Source 1</default>
+                       </parameter>
+                       <parameter name="inputLabel2" type="text" required="false">
+                               <label>Source 2 Input Label</label>
+                               <description>Friendly Name for the Input Source to Be Displayed in the UI (ie: Chromecast, Radio, CD, etc.)</description>
+                               <default>Source 2</default>
+                       </parameter>
+                       <parameter name="inputLabel3" type="text" required="false">
+                               <label>Source 3 Input Label</label>
+                               <description>Friendly Name for the Input Source to Be Displayed in the UI (ie: Chromecast, Radio, CD, etc.)</description>
+                               <default>Source 3</default>
+                       </parameter>
+                       <parameter name="inputLabel4" type="text" required="false">
+                               <label>Source 4 Input Label</label>
+                               <description>Friendly Name for the Input Source to Be Displayed in the UI (ie: Chromecast, Radio, CD, etc.)</description>
+                               <default>Source 4</default>
+                       </parameter>
+                       <parameter name="inputLabel5" type="text" required="false">
+                               <label>Source 5 Input Label</label>
+                               <description>Friendly Name for the Input Source to Be Displayed in the UI (ie: Chromecast, Radio, CD, etc.)</description>
+                               <default>Source 5</default>
+                       </parameter>
+                       <parameter name="inputLabel6" type="text" required="false">
+                               <label>Source 6 Input Label</label>
+                               <description>Friendly Name for the Input Source to Be Displayed in the UI (ie: Chromecast, Radio, CD, etc.)</description>
+                               <default>Source 6</default>
+                       </parameter>
+                       <parameter name="inputLabel7" type="text" required="false">
+                               <label>Source 7 Input Label</label>
+                               <description>Friendly Name for the Input Source to Be Displayed in the UI (ie: Chromecast, Radio, CD, etc.)</description>
+                               <default>Source 7</default>
+                       </parameter>
+                       <parameter name="inputLabel8" type="text" required="false">
+                               <label>Source 8 Input Label</label>
+                               <description>Friendly Name for the Input Source to Be Displayed in the UI (ie: Chromecast, Radio, CD, etc.)</description>
+                               <default>Wi-Fi streaming</default>
+                       </parameter>
+                       <parameter name="disableKeypadPolling" type="boolean" required="false">
+                               <label>Disable Keypad Polling</label>
+                               <description>If physical keypads are not used, this option will disable polling the amplifier for zone updates</description>
+                               <default>false</default>
+                       </parameter>
+               </config-description>
+       </thing-type>
+
+       <channel-group-type id="dax88-all">
+               <label>All Zones</label>
+               <description>Control All Zones Simultaneously</description>
+               <channels>
+                       <channel id="allpower" typeId="dax88-allpower"/>
+                       <channel id="allsource" typeId="dax88-source"/>
+                       <channel id="allvolume" typeId="system.volume"/>
+                       <channel id="allmute" typeId="system.mute"/>
+               </channels>
+       </channel-group-type>
+
+       <channel-group-type id="dax88-zone">
+               <label>Zone Controls</label>
+               <description>The Controls for the Zone</description>
+               <channels>
+                       <channel id="power" typeId="system.power"/>
+                       <channel id="source" typeId="dax88-source"/>
+                       <channel id="volume" typeId="system.volume"/>
+                       <channel id="mute" typeId="system.mute"/>
+                       <channel id="treble" typeId="dax88-treble"/>
+                       <channel id="bass" typeId="dax88-bass"/>
+                       <channel id="balance" typeId="dax88-balance"/>
+                       <channel id="dnd" typeId="dax88-dnd"/>
+                       <channel id="page" typeId="dax88-page"/>
+                       <channel id="keypad" typeId="dax88-keypad"/>
+               </channels>
+       </channel-group-type>
+
+       <channel-type id="dax88-allpower">
+               <item-type>Switch</item-type>
+               <label>All On</label>
+               <description>Turn All Zones On or Off</description>
+       </channel-type>
+
+       <channel-type id="dax88-source">
+               <item-type>Number</item-type>
+               <label>Source Input</label>
+               <description>Select the Source Input</description>
+               <state min="1" max="8"/>
+       </channel-type>
+
+       <channel-type id="dax88-treble">
+               <item-type>Number</item-type>
+               <label>Treble Adjustment</label>
+               <description>Adjust the Treble</description>
+               <state min="-12" max="12" step="1" pattern="%d"/>
+       </channel-type>
+
+       <channel-type id="dax88-bass">
+               <item-type>Number</item-type>
+               <label>Bass Adjustment</label>
+               <description>Adjust the Bass</description>
+               <state min="-12" max="12" step="1" pattern="%d"/>
+       </channel-type>
+
+       <channel-type id="dax88-balance">
+               <item-type>Number</item-type>
+               <label>Balance Adjustment</label>
+               <description>Adjust the Balance</description>
+               <state min="-10" max="10" step="1" pattern="%d"/>
+       </channel-type>
+
+       <channel-type id="dax88-dnd">
+               <item-type>Switch</item-type>
+               <label>Do Not Disturb</label>
+               <description>Controls if the Zone Should Ignore an Incoming Audio Page</description>
+       </channel-type>
+
+       <channel-type id="dax88-page">
+               <item-type>Contact</item-type>
+               <label>Page Active</label>
+               <description>Indicates if the Page Mode is Active for This Zone</description>
+               <state readOnly="true">
+                       <options>
+                               <option value="CLOSED">Inactive</option>
+                               <option value="OPEN">Active</option>
+                       </options>
+               </state>
+       </channel-type>
+
+       <channel-type id="dax88-keypad">
+               <item-type>Contact</item-type>
+               <label>Keypad Connected</label>
+               <description>Indicates if a Physical Keypad is Attached to This Zone</description>
+               <state readOnly="true">
+                       <options>
+                               <option value="CLOSED">Disconnected</option>
+                               <option value="OPEN">Connected</option>
+                       </options>
+               </state>
+       </channel-type>
+
+</thing:thing-descriptions>
diff --git a/bundles/org.openhab.binding.monopriceaudio/src/main/resources/OH-INF/thing/monoprice.xml b/bundles/org.openhab.binding.monopriceaudio/src/main/resources/OH-INF/thing/monoprice.xml
new file mode 100644 (file)
index 0000000..6f94f01
--- /dev/null
@@ -0,0 +1,262 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<thing:thing-descriptions bindingId="monopriceaudio"
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
+       xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
+
+       <!-- Monoprice 10761/39261/DAX66 Amplifier Thing -->
+       <thing-type id="amplifier">
+               <label>Monoprice 10761 Amplifier, 39261 Matrix or Dayton DAX66 Amplifier</label>
+               <description>
+                       A Multi-zone Whole House Amplifier System
+               </description>
+
+               <channel-groups>
+                       <channel-group id="all" typeId="all">
+                               <label>All Zones</label>
+                               <description>Control All Zones Simultaneously</description>
+                       </channel-group>
+                       <channel-group id="zone1" typeId="zone">
+                               <label>Zone 1</label>
+                               <description>The Controls for Zone 1</description>
+                       </channel-group>
+                       <channel-group id="zone2" typeId="zone">
+                               <label>Zone 2</label>
+                               <description>The Controls for Zone 2</description>
+                       </channel-group>
+                       <channel-group id="zone3" typeId="zone">
+                               <label>Zone 3</label>
+                               <description>The Controls for Zone 3</description>
+                       </channel-group>
+                       <channel-group id="zone4" typeId="zone">
+                               <label>Zone 4</label>
+                               <description>The Controls for Zone 4</description>
+                       </channel-group>
+                       <channel-group id="zone5" typeId="zone">
+                               <label>Zone 5</label>
+                               <description>The Controls for Zone 5</description>
+                       </channel-group>
+                       <channel-group id="zone6" typeId="zone">
+                               <label>Zone 6</label>
+                               <description>The Controls for Zone 6</description>
+                       </channel-group>
+                       <channel-group id="zone7" typeId="zone">
+                               <label>Zone 7</label>
+                               <description>The Controls for Zone 7</description>
+                       </channel-group>
+                       <channel-group id="zone8" typeId="zone">
+                               <label>Zone 8</label>
+                               <description>The Controls for Zone 8</description>
+                       </channel-group>
+                       <channel-group id="zone9" typeId="zone">
+                               <label>Zone 9</label>
+                               <description>The Controls for Zone 9</description>
+                       </channel-group>
+                       <channel-group id="zone10" typeId="zone">
+                               <label>Zone 10</label>
+                               <description>The Controls for Zone 10</description>
+                       </channel-group>
+                       <channel-group id="zone11" typeId="zone">
+                               <label>Zone 11</label>
+                               <description>The Controls for Zone 11</description>
+                       </channel-group>
+                       <channel-group id="zone12" typeId="zone">
+                               <label>Zone 12</label>
+                               <description>The Controls for Zone 12</description>
+                       </channel-group>
+                       <channel-group id="zone13" typeId="zone">
+                               <label>Zone 13</label>
+                               <description>The Controls for Zone 13</description>
+                       </channel-group>
+                       <channel-group id="zone14" typeId="zone">
+                               <label>Zone 14</label>
+                               <description>The Controls for Zone 14</description>
+                       </channel-group>
+                       <channel-group id="zone15" typeId="zone">
+                               <label>Zone 15</label>
+                               <description>The Controls for Zone 15</description>
+                       </channel-group>
+                       <channel-group id="zone16" typeId="zone">
+                               <label>Zone 16</label>
+                               <description>The Controls for Zone 16</description>
+                       </channel-group>
+                       <channel-group id="zone17" typeId="zone">
+                               <label>Zone 17</label>
+                               <description>The Controls for Zone 17</description>
+                       </channel-group>
+                       <channel-group id="zone18" typeId="zone">
+                               <label>Zone 18</label>
+                               <description>The Controls for Zone 18</description>
+                       </channel-group>
+               </channel-groups>
+
+               <config-description>
+                       <parameter name="serialPort" type="text" required="false">
+                               <context>serial-port</context>
+                               <limitToOptions>false</limitToOptions>
+                               <label>Serial Port</label>
+                               <description>Serial Port to Use for Connecting to the Monoprice Amplifier</description>
+                       </parameter>
+                       <parameter name="host" type="text" required="false">
+                               <context>network-address</context>
+                               <label>Address</label>
+                               <description>Host Name or IP Address of the Monoprice Amplifier or Serial over IP device</description>
+                       </parameter>
+                       <parameter name="port" type="integer" min="1" max="65535" required="false">
+                               <label>Port</label>
+                               <description>Communication Port for Serial over IP connection to the Monoprice Amplifier (8080 for amps with
+                                       built-in Serial over IP)</description>
+                       </parameter>
+                       <parameter name="numZones" type="integer" min="1" max="18" required="true">
+                               <label>Number of Zones</label>
+                               <description>Number of Zones on the Amplifier to Utilize in the Binding (Up to 18 Zones With 3 Amplifiers Connected
+                                       Together)</description>
+                               <default>6</default>
+                       </parameter>
+                       <parameter name="pollingInterval" type="integer" min="5" max="60" unit="s" required="false">
+                               <label>Polling Interval</label>
+                               <description>Configures How Often to Poll the Amplifier to Check for Zone Updates (5-60; Default 15)</description>
+                               <default>15</default>
+                       </parameter>
+                       <parameter name="ignoreZones" type="text" required="false">
+                               <label>Ignore Zones</label>
+                               <description>(Optional) A Comma Seperated List of Zone Numbers That Will Ignore the 'All Zone' (Except All Off)
+                                       Commands (ie: 1,6,10)</description>
+                       </parameter>
+                       <parameter name="initialAllVolume" type="integer" min="1" max="30" required="false">
+                               <label>Initial All Volume</label>
+                               <description>When 'All' Zones Are Activated, the Volume Will Reset to This Value (1-30; default 10) to Prevent
+                                       Excessive Blaring of Sound ;)</description>
+                               <default>10</default>
+                       </parameter>
+                       <parameter name="inputLabel1" type="text" required="false">
+                               <label>Source 1 Input Label</label>
+                               <description>Friendly Name for the Input Source to Be Displayed in the UI (ie: Chromecast, Radio, CD, etc.)</description>
+                               <default>Source 1</default>
+                       </parameter>
+                       <parameter name="inputLabel2" type="text" required="false">
+                               <label>Source 2 Input Label</label>
+                               <description>Friendly Name for the Input Source to Be Displayed in the UI (ie: Chromecast, Radio, CD, etc.)</description>
+                               <default>Source 2</default>
+                       </parameter>
+                       <parameter name="inputLabel3" type="text" required="false">
+                               <label>Source 3 Input Label</label>
+                               <description>Friendly Name for the Input Source to Be Displayed in the UI (ie: Chromecast, Radio, CD, etc.)</description>
+                               <default>Source 3</default>
+                       </parameter>
+                       <parameter name="inputLabel4" type="text" required="false">
+                               <label>Source 4 Input Label</label>
+                               <description>Friendly Name for the Input Source to Be Displayed in the UI (ie: Chromecast, Radio, CD, etc.)</description>
+                               <default>Source 4</default>
+                       </parameter>
+                       <parameter name="inputLabel5" type="text" required="false">
+                               <label>Source 5 Input Label</label>
+                               <description>Friendly Name for the Input Source to Be Displayed in the UI (ie: Chromecast, Radio, CD, etc.)</description>
+                               <default>Source 5</default>
+                       </parameter>
+                       <parameter name="inputLabel6" type="text" required="false">
+                               <label>Source 6 Input Label</label>
+                               <description>Friendly Name for the Input Source to Be Displayed in the UI (ie: Chromecast, Radio, CD, etc.)</description>
+                               <default>Source 6</default>
+                       </parameter>
+                       <parameter name="disableKeypadPolling" type="boolean" required="false">
+                               <label>Disable Keypad Polling</label>
+                               <description>If physical keypads are not used, this option will disable polling the amplifier for zone updates</description>
+                               <default>false</default>
+                       </parameter>
+               </config-description>
+       </thing-type>
+
+       <channel-group-type id="all">
+               <label>All Zones</label>
+               <description>Control All Zones Simultaneously</description>
+               <channels>
+                       <channel id="allpower" typeId="allpower"/>
+                       <channel id="allsource" typeId="source"/>
+                       <channel id="allvolume" typeId="system.volume"/>
+                       <channel id="allmute" typeId="system.mute"/>
+               </channels>
+       </channel-group-type>
+
+       <channel-group-type id="zone">
+               <label>Zone Controls</label>
+               <description>The Controls for the Zone</description>
+               <channels>
+                       <channel id="power" typeId="system.power"/>
+                       <channel id="source" typeId="source"/>
+                       <channel id="volume" typeId="system.volume"/>
+                       <channel id="mute" typeId="system.mute"/>
+                       <channel id="treble" typeId="treble"/>
+                       <channel id="bass" typeId="bass"/>
+                       <channel id="balance" typeId="balance"/>
+                       <channel id="dnd" typeId="dnd"/>
+                       <channel id="page" typeId="page"/>
+                       <channel id="keypad" typeId="keypad"/>
+               </channels>
+       </channel-group-type>
+
+       <channel-type id="allpower">
+               <item-type>Switch</item-type>
+               <label>All On</label>
+               <description>Turn All Zones On or Off</description>
+       </channel-type>
+
+       <channel-type id="source">
+               <item-type>Number</item-type>
+               <label>Source Input</label>
+               <description>Select the Source Input</description>
+               <state min="1" max="6"/>
+       </channel-type>
+
+       <channel-type id="treble">
+               <item-type>Number</item-type>
+               <label>Treble Adjustment</label>
+               <description>Adjust the Treble</description>
+               <state min="-7" max="7" step="1" pattern="%d"/>
+       </channel-type>
+
+       <channel-type id="bass">
+               <item-type>Number</item-type>
+               <label>Bass Adjustment</label>
+               <description>Adjust the Bass</description>
+               <state min="-7" max="7" step="1" pattern="%d"/>
+       </channel-type>
+
+       <channel-type id="balance">
+               <item-type>Number</item-type>
+               <label>Balance Adjustment</label>
+               <description>Adjust the Balance</description>
+               <state min="-10" max="10" step="1" pattern="%d"/>
+       </channel-type>
+
+       <channel-type id="dnd">
+               <item-type>Switch</item-type>
+               <label>Do Not Disturb</label>
+               <description>Controls if the Zone Should Ignore an Incoming Audio Page</description>
+       </channel-type>
+
+       <channel-type id="page">
+               <item-type>Contact</item-type>
+               <label>Page Active</label>
+               <description>Indicates if the Page Mode is Active for This Zone</description>
+               <state readOnly="true">
+                       <options>
+                               <option value="CLOSED">Inactive</option>
+                               <option value="OPEN">Active</option>
+                       </options>
+               </state>
+       </channel-type>
+
+       <channel-type id="keypad">
+               <item-type>Contact</item-type>
+               <label>Keypad Connected</label>
+               <description>Indicates if a Physical Keypad is Attached to This Zone</description>
+               <state readOnly="true">
+                       <options>
+                               <option value="CLOSED">Disconnected</option>
+                               <option value="OPEN">Connected</option>
+                       </options>
+               </state>
+       </channel-type>
+
+</thing:thing-descriptions>
diff --git a/bundles/org.openhab.binding.monopriceaudio/src/main/resources/OH-INF/thing/monoprice70.xml b/bundles/org.openhab.binding.monopriceaudio/src/main/resources/OH-INF/thing/monoprice70.xml
new file mode 100644 (file)
index 0000000..6c35916
--- /dev/null
@@ -0,0 +1,154 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<thing:thing-descriptions bindingId="monopriceaudio"
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
+       xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
+
+       <!-- Monoprice 31028/PAM1270 Amplifier Thing -->
+       <thing-type id="monoprice70">
+               <label>Monoprice 31028 70V Amplifier</label>
+               <description>
+                       A Multi-zone Whole House Amplifier System
+               </description>
+
+               <channel-groups>
+                       <channel-group id="all" typeId="monoprice70-all">
+                               <label>All Zones</label>
+                               <description>Control All Zones Simultaneously</description>
+                       </channel-group>
+                       <channel-group id="zone1" typeId="monoprice70-zone">
+                               <label>Zone 1</label>
+                               <description>The Controls for Zone 1</description>
+                       </channel-group>
+                       <channel-group id="zone2" typeId="monoprice70-zone">
+                               <label>Zone 2</label>
+                               <description>The Controls for Zone 2</description>
+                       </channel-group>
+                       <channel-group id="zone3" typeId="monoprice70-zone">
+                               <label>Zone 3</label>
+                               <description>The Controls for Zone 3</description>
+                       </channel-group>
+                       <channel-group id="zone4" typeId="monoprice70-zone">
+                               <label>Zone 4</label>
+                               <description>The Controls for Zone 4</description>
+                       </channel-group>
+                       <channel-group id="zone5" typeId="monoprice70-zone">
+                               <label>Zone 5</label>
+                               <description>The Controls for Zone 5</description>
+                       </channel-group>
+                       <channel-group id="zone6" typeId="monoprice70-zone">
+                               <label>Zone 6</label>
+                               <description>The Controls for Zone 6</description>
+                       </channel-group>
+               </channel-groups>
+
+               <config-description>
+                       <parameter name="serialPort" type="text" required="false">
+                               <context>serial-port</context>
+                               <limitToOptions>false</limitToOptions>
+                               <label>Serial Port</label>
+                               <description>Serial Port to Use for Connecting to the Monoprice Amplifier</description>
+                       </parameter>
+                       <parameter name="host" type="text" required="false">
+                               <context>network-address</context>
+                               <label>Address</label>
+                               <description>Host Name or IP Address of the Machine Connected to the Monoprice Amplifier (Serial over IP)</description>
+                       </parameter>
+                       <parameter name="port" type="integer" min="1" max="65535" required="false">
+                               <label>Port</label>
+                               <description>Communication Port for Serial over IP connection to the Monoprice Amplifier</description>
+                       </parameter>
+                       <parameter name="numZones" type="integer" min="1" max="6" required="true">
+                               <label>Number of Zones</label>
+                               <description>Number of Zones on the Amplifier to Utilize in the Binding</description>
+                               <default>6</default>
+                       </parameter>
+                       <parameter name="pollingInterval" type="integer" min="5" max="60" unit="s" required="false">
+                               <label>Polling Interval</label>
+                               <description>Configures How Often to Poll the Amplifier to Check for Zone Updates (5-60; Default 30)</description>
+                               <default>30</default>
+                       </parameter>
+                       <parameter name="ignoreZones" type="text" required="false">
+                               <label>Ignore Zones</label>
+                               <description>(Optional) A Comma Seperated List of Zone Numbers That Will Ignore the 'All Zone' (Except All Off)
+                                       Commands (ie: 1,5,6)</description>
+                       </parameter>
+                       <parameter name="initialAllVolume" type="integer" min="1" max="30" required="false">
+                               <label>Initial All Volume</label>
+                               <description>When 'All' Zones Are Activated, the Volume Will Reset to This Value (1-30; default 10) to Prevent
+                                       Excessive Blaring of Sound ;)</description>
+                               <default>10</default>
+                       </parameter>
+                       <parameter name="inputLabel1" type="text" required="false">
+                               <label>Source 0 Input Label</label>
+                               <description>Friendly Name for the Input Source to Be Displayed in the UI (ie: Chromecast, Radio, CD, etc.)</description>
+                               <default>Source 0 - Bus</default>
+                       </parameter>
+                       <parameter name="inputLabel2" type="text" required="false">
+                               <label>Source 1 Input Label</label>
+                               <description>Friendly Name for the Input Source to Be Displayed in the UI (ie: Chromecast, Radio, CD, etc.)</description>
+                               <default>Source 1 - Line</default>
+                       </parameter>
+               </config-description>
+       </thing-type>
+
+       <channel-group-type id="monoprice70-all">
+               <label>All Zones</label>
+               <description>Control All Zones Simultaneously</description>
+               <channels>
+                       <channel id="allpower" typeId="monoprice70-allpower"/>
+                       <channel id="allsource" typeId="monoprice70-source"/>
+                       <channel id="allvolume" typeId="system.volume"/>
+                       <channel id="allmute" typeId="system.mute"/>
+               </channels>
+       </channel-group-type>
+
+       <channel-group-type id="monoprice70-zone">
+               <label>Zone Controls</label>
+               <description>The Controls for the Zone</description>
+               <channels>
+                       <channel id="power" typeId="system.power"/>
+                       <channel id="source" typeId="monoprice70-source"/>
+                       <channel id="volume" typeId="system.volume"/>
+                       <channel id="mute" typeId="system.mute"/>
+                       <channel id="treble" typeId="monoprice70-treble"/>
+                       <channel id="bass" typeId="monoprice70-bass"/>
+                       <channel id="balance" typeId="monoprice70-balance"/>
+               </channels>
+       </channel-group-type>
+
+       <channel-type id="monoprice70-allpower">
+               <item-type>Switch</item-type>
+               <label>All On</label>
+               <description>Turn All Zones On or Off</description>
+       </channel-type>
+
+       <channel-type id="monoprice70-source">
+               <item-type>Number</item-type>
+               <label>Source Input</label>
+               <description>Select the Source Input</description>
+               <state min="0" max="1"/>
+       </channel-type>
+
+       <channel-type id="monoprice70-treble">
+               <item-type>Number</item-type>
+               <label>Treble Adjustment</label>
+               <description>Adjust the Treble</description>
+               <state min="-7" max="7" step="1" pattern="%d"/>
+       </channel-type>
+
+       <channel-type id="monoprice70-bass">
+               <item-type>Number</item-type>
+               <label>Bass Adjustment</label>
+               <description>Adjust the Bass</description>
+               <state min="-7" max="7" step="1" pattern="%d"/>
+       </channel-type>
+
+       <channel-type id="monoprice70-balance">
+               <item-type>Number</item-type>
+               <label>Balance Adjustment</label>
+               <description>Adjust the Balance</description>
+               <state min="-32" max="31" step="1" pattern="%d"/>
+       </channel-type>
+
+</thing:thing-descriptions>
diff --git a/bundles/org.openhab.binding.monopriceaudio/src/main/resources/OH-INF/thing/xantech.xml b/bundles/org.openhab.binding.monopriceaudio/src/main/resources/OH-INF/thing/xantech.xml
new file mode 100644 (file)
index 0000000..e0e3725
--- /dev/null
@@ -0,0 +1,256 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<thing:thing-descriptions bindingId="monopriceaudio"
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
+       xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
+
+       <!-- Xantech 8x8 Amplifier Thing -->
+       <thing-type id="xantech">
+               <label>Xantech 8x8 Amplifier</label>
+               <description>
+                       A Multi-zone Whole House Amplifier System
+               </description>
+
+               <channel-groups>
+                       <channel-group id="all" typeId="xantech-all">
+                               <label>All Zones</label>
+                               <description>Control All Zones Simultaneously</description>
+                       </channel-group>
+                       <channel-group id="zone1" typeId="xantech-zone">
+                               <label>Zone 1</label>
+                               <description>The Controls for Zone 1</description>
+                       </channel-group>
+                       <channel-group id="zone2" typeId="xantech-zone">
+                               <label>Zone 2</label>
+                               <description>The Controls for Zone 2</description>
+                       </channel-group>
+                       <channel-group id="zone3" typeId="xantech-zone">
+                               <label>Zone 3</label>
+                               <description>The Controls for Zone 3</description>
+                       </channel-group>
+                       <channel-group id="zone4" typeId="xantech-zone">
+                               <label>Zone 4</label>
+                               <description>The Controls for Zone 4</description>
+                       </channel-group>
+                       <channel-group id="zone5" typeId="xantech-zone">
+                               <label>Zone 5</label>
+                               <description>The Controls for Zone 5</description>
+                       </channel-group>
+                       <channel-group id="zone6" typeId="xantech-zone">
+                               <label>Zone 6</label>
+                               <description>The Controls for Zone 6</description>
+                       </channel-group>
+                       <channel-group id="zone7" typeId="xantech-zone">
+                               <label>Zone 7</label>
+                               <description>The Controls for Zone 7</description>
+                       </channel-group>
+                       <channel-group id="zone8" typeId="xantech-zone">
+                               <label>Zone 8</label>
+                               <description>The Controls for Zone 8</description>
+                       </channel-group>
+                       <channel-group id="zone9" typeId="xantech-zone">
+                               <label>Zone 9</label>
+                               <description>The Controls for Zone 9</description>
+                       </channel-group>
+                       <channel-group id="zone10" typeId="xantech-zone">
+                               <label>Zone 10</label>
+                               <description>The Controls for Zone 10</description>
+                       </channel-group>
+                       <channel-group id="zone11" typeId="xantech-zone">
+                               <label>Zone 11</label>
+                               <description>The Controls for Zone 11</description>
+                       </channel-group>
+                       <channel-group id="zone12" typeId="xantech-zone">
+                               <label>Zone 12</label>
+                               <description>The Controls for Zone 12</description>
+                       </channel-group>
+                       <channel-group id="zone13" typeId="xantech-zone">
+                               <label>Zone 13</label>
+                               <description>The Controls for Zone 13</description>
+                       </channel-group>
+                       <channel-group id="zone14" typeId="xantech-zone">
+                               <label>Zone 14</label>
+                               <description>The Controls for Zone 14</description>
+                       </channel-group>
+                       <channel-group id="zone15" typeId="xantech-zone">
+                               <label>Zone 15</label>
+                               <description>The Controls for Zone 15</description>
+                       </channel-group>
+                       <channel-group id="zone16" typeId="xantech-zone">
+                               <label>Zone 16</label>
+                               <description>The Controls for Zone 16</description>
+                       </channel-group>
+               </channel-groups>
+
+               <config-description>
+                       <parameter name="serialPort" type="text" required="false">
+                               <context>serial-port</context>
+                               <limitToOptions>false</limitToOptions>
+                               <label>Serial Port</label>
+                               <description>Serial Port to Use for Connecting to the Xantech Amplifier</description>
+                       </parameter>
+                       <parameter name="host" type="text" required="false">
+                               <context>network-address</context>
+                               <label>Address</label>
+                               <description>Host Name or IP Address of the Machine Connected to the Xantech Amplifier (Serial over IP)</description>
+                       </parameter>
+                       <parameter name="port" type="integer" min="1" max="65535" required="false">
+                               <label>Port</label>
+                               <description>Communication Port for Serial over IP connection to the Xantech Amplifier</description>
+                       </parameter>
+                       <parameter name="numZones" type="integer" min="1" max="16" required="true">
+                               <label>Number of Zones</label>
+                               <description>Number of Zones on the Amplifier to Utilize in the Binding (Up to 16 Zones With 2 Amplifiers Connected
+                                       Together)</description>
+                               <default>8</default>
+                       </parameter>
+                       <parameter name="pollingInterval" type="integer" min="5" max="60" unit="s" required="false">
+                               <label>Polling Interval</label>
+                               <description>Configures How Often to Poll the Amplifier to Check for Zone Updates (5-60; Default 30)</description>
+                               <default>30</default>
+                       </parameter>
+                       <parameter name="ignoreZones" type="text" required="false">
+                               <label>Ignore Zones</label>
+                               <description>(Optional) A Comma Seperated List of Zone Numbers That Will Ignore the 'All Zone' (Except All Off)
+                                       Commands (ie: 1,6,10)</description>
+                       </parameter>
+                       <parameter name="initialAllVolume" type="integer" min="1" max="30" required="false">
+                               <label>Initial All Volume</label>
+                               <description>When 'All' Zones Are Activated, the Volume Will Reset to This Value (1-30; default 10) to Prevent
+                                       Excessive Blaring of Sound ;)</description>
+                               <default>10</default>
+                       </parameter>
+                       <parameter name="inputLabel1" type="text" required="false">
+                               <label>Source 1 Input Label</label>
+                               <description>Friendly Name for the Input Source to Be Displayed in the UI (ie: Chromecast, Radio, CD, etc.)</description>
+                               <default>Source 1</default>
+                       </parameter>
+                       <parameter name="inputLabel2" type="text" required="false">
+                               <label>Source 2 Input Label</label>
+                               <description>Friendly Name for the Input Source to Be Displayed in the UI (ie: Chromecast, Radio, CD, etc.)</description>
+                               <default>Source 2</default>
+                       </parameter>
+                       <parameter name="inputLabel3" type="text" required="false">
+                               <label>Source 3 Input Label</label>
+                               <description>Friendly Name for the Input Source to Be Displayed in the UI (ie: Chromecast, Radio, CD, etc.)</description>
+                               <default>Source 3</default>
+                       </parameter>
+                       <parameter name="inputLabel4" type="text" required="false">
+                               <label>Source 4 Input Label</label>
+                               <description>Friendly Name for the Input Source to Be Displayed in the UI (ie: Chromecast, Radio, CD, etc.)</description>
+                               <default>Source 4</default>
+                       </parameter>
+                       <parameter name="inputLabel5" type="text" required="false">
+                               <label>Source 5 Input Label</label>
+                               <description>Friendly Name for the Input Source to Be Displayed in the UI (ie: Chromecast, Radio, CD, etc.)</description>
+                               <default>Source 5</default>
+                       </parameter>
+                       <parameter name="inputLabel6" type="text" required="false">
+                               <label>Source 6 Input Label</label>
+                               <description>Friendly Name for the Input Source to Be Displayed in the UI (ie: Chromecast, Radio, CD, etc.)</description>
+                               <default>Source 6</default>
+                       </parameter>
+                       <parameter name="inputLabel7" type="text" required="false">
+                               <label>Source 7 Input Label</label>
+                               <description>Friendly Name for the Input Source to Be Displayed in the UI (ie: Chromecast, Radio, CD, etc.)</description>
+                               <default>Source 7</default>
+                       </parameter>
+                       <parameter name="inputLabel8" type="text" required="false">
+                               <label>Source 8 Input Label</label>
+                               <description>Friendly Name for the Input Source to Be Displayed in the UI (ie: Chromecast, Radio, CD, etc.)</description>
+                               <default>Source 8</default>
+                       </parameter>
+                       <parameter name="disableKeypadPolling" type="boolean" required="false">
+                               <label>Disable Keypad Polling</label>
+                               <description>If physical keypads are not used, this option will disable polling the amplifier for zone updates</description>
+                               <default>false</default>
+                       </parameter>
+               </config-description>
+       </thing-type>
+
+       <channel-group-type id="xantech-all">
+               <label>All Zones</label>
+               <description>Control All Zones Simultaneously</description>
+               <channels>
+                       <channel id="allpower" typeId="xantech-allpower"/>
+                       <channel id="allsource" typeId="xantech-source"/>
+                       <channel id="allvolume" typeId="system.volume"/>
+                       <channel id="allmute" typeId="system.mute"/>
+               </channels>
+       </channel-group-type>
+
+       <channel-group-type id="xantech-zone">
+               <label>Zone Controls</label>
+               <description>The Controls for the Zone</description>
+               <channels>
+                       <channel id="power" typeId="system.power"/>
+                       <channel id="source" typeId="xantech-source"/>
+                       <channel id="volume" typeId="system.volume"/>
+                       <channel id="mute" typeId="system.mute"/>
+                       <channel id="treble" typeId="xantech-treble"/>
+                       <channel id="bass" typeId="xantech-bass"/>
+                       <channel id="balance" typeId="xantech-balance"/>
+                       <channel id="page" typeId="xantech-page"/>
+                       <channel id="keypad" typeId="xantech-keypad"/>
+               </channels>
+       </channel-group-type>
+
+       <channel-type id="xantech-allpower">
+               <item-type>Switch</item-type>
+               <label>All On</label>
+               <description>Turn All Zones On or Off</description>
+       </channel-type>
+
+       <channel-type id="xantech-source">
+               <item-type>Number</item-type>
+               <label>Source Input</label>
+               <description>Select the Source Input</description>
+               <state min="1" max="8"/>
+       </channel-type>
+
+       <channel-type id="xantech-treble">
+               <item-type>Number</item-type>
+               <label>Treble Adjustment</label>
+               <description>Adjust the Treble</description>
+               <state min="-7" max="7" step="1" pattern="%d"/>
+       </channel-type>
+
+       <channel-type id="xantech-bass">
+               <item-type>Number</item-type>
+               <label>Bass Adjustment</label>
+               <description>Adjust the Bass</description>
+               <state min="-7" max="7" step="1" pattern="%d"/>
+       </channel-type>
+
+       <channel-type id="xantech-balance">
+               <item-type>Number</item-type>
+               <label>Balance Adjustment</label>
+               <description>Adjust the Balance</description>
+               <state min="-32" max="31" step="1" pattern="%d"/>
+       </channel-type>
+
+       <channel-type id="xantech-page">
+               <item-type>Contact</item-type>
+               <label>Page Active</label>
+               <description>Indicates if the Page Mode is Active for This Zone</description>
+               <state readOnly="true">
+                       <options>
+                               <option value="CLOSED">Inactive</option>
+                               <option value="OPEN">Active</option>
+                       </options>
+               </state>
+       </channel-type>
+
+       <channel-type id="xantech-keypad">
+               <item-type>Contact</item-type>
+               <label>Keypad Connected</label>
+               <description>Indicates if a Physical Keypad is Attached to This Zone</description>
+               <state readOnly="true">
+                       <options>
+                               <option value="CLOSED">Disconnected</option>
+                               <option value="OPEN">Connected</option>
+                       </options>
+               </state>
+       </channel-type>
+
+</thing:thing-descriptions>