[monopriceaudio] Add support for additional amplifiers (#13936)

* 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>
This commit is contained in:
mlobstein 2023-05-11 09:14:55 -05:00 committed by GitHub
parent e6d84ab488
commit 2f7bdbe966
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 1840 additions and 551 deletions

View File

@ -1,48 +1,55 @@
# 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.
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:
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 Monoprice whole house amplifier device | Serial port name |
|------------------------|----------------------|--------------------------------------------------------------------------------------------------------------------------------|------------------|
| 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 (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 |
| 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 seperated list of Zone numbers that will ignore the 'All Zone' (except All Off) commands | ie: "1,6,10" |
| 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 |
@ -50,36 +57,55 @@ The thing has the following configuration parameters:
| 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:
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-6) (except ignoreZones) |
| all#allvolume | Dimmer | Control the volume for all zones simultaneously (0-100%) [translates to 0-38] (except ignoreZones) |
| 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-6) |
| zoneN#volume (where N= 1-18) | Dimmer | Control the volume for a zone (0-100%) [translates to 0-38] |
| 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 (-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#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 |
@ -89,17 +115,29 @@ The following channels are available:
monoprice.things:
```java
// serial port connection
// 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" ]
// serial over IP connection
// 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" ]
// 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)
}
```

View 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;
}

View 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;
}
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}

View File

@ -12,14 +12,14 @@
*/
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 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
* @param zone the zone to query for current treble, bass and balance status
*
* @throws MonopriceAudioException - In case of any problem
*/
public void sendCommand(MonopriceAudioZone zone, MonopriceAudioCommand 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);
} else {
throw new MonopriceAudioException("Send command \"" + messageStr + "\" failed: passed in value is null");
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 cmd the command to execute
*
* @throws MonopriceAudioException - In case of any problem
*/
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 (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 {
// 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();
logger.debug("handleIncomingMessage: {}", message);
if (READ_ERROR.equals(message)) {
dispatchKeyValue(KEY_ERROR, MSG_VALUE_ON);
if (pingResponseOnly) {
dispatchKeyValue(KEY_PING, EMPTY);
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));
String message = new String(incomingMessage, StandardCharsets.US_ASCII).trim();
if (EMPTY.equals(message)) {
return;
}
if (message.startsWith(amp.getRespPrefix())) {
logger.debug("handleIncomingMessage: {}", message);
dispatchKeyValue(KEY_ZONE_UPDATE, message);
} else {
logger.debug("no match on message: {}", message);
}

View 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);

View 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

View 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");

View 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

View File

@ -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;
}
}

View 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;
}

View File

@ -12,29 +12,42 @@
*/
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;
}
}

View 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";
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"@text/offline.configuration-error-rfc2217");
return;
}
} else {
if (port == null) {
configError = "undefined port configuration setting";
} else if (port <= 0) {
configError = "invalid port configuration setting";
}
}
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,37 +442,30 @@ 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();
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 {
processZoneUpdate(zoneData, newZoneData);
}
} else {
logger.debug("invalid event: {} for key: {} or zone data null", evt.getValue(), key);
}
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);
} else {
logger.warn("invalid event: {} for key: {} or zone data null", evt.getValue(), key);
}
case MonopriceAudioConnector.KEY_PING:
lastPollingUpdate = System.currentTimeMillis();
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());
}
}
/**
@ -500,24 +482,39 @@ public class MonopriceAudioHandler extends BaseThingHandler implements Monoprice
String error = null;
if (openConnection()) {
try {
long prevUpdateTime = lastPollingUpdate;
connector.queryZone(MonopriceAudioZone.ZONE1);
// 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());
}
}
// prevUpdateTime should have changed if a zone update was received
if (lastPollingUpdate == prevUpdateTime) {
error = "Controller not responding to status requests";
}
} catch (MonopriceAudioException e) {
error = "First command after connection failed";
logger.warn("{}: {}", error, e.getMessage());
closeConnection();
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...");
logger.debug("Polling the amplifier for updated status...");
if (!disableKeypadPolling) {
// poll each zone up to the number of zones specified in the configuration
MonopriceAudioZone.VALID_ZONES.stream().limit(numZones).forEach((zoneName) -> {
amp.getZoneIds().stream().limit(numZones).forEach((streamZoneId) -> {
try {
connector.queryZone(MonopriceAudioZone.valueOf(zoneName));
connector.queryZone(streamZoneId);
} catch (MonopriceAudioException e) {
logger.warn("Polling error: {}", e.getMessage());
logger.debug("Polling error for zone id {}: {}", streamZoneId, e.getMessage());
}
});
} else {
try {
// ping only (no zone updates) to verify the connection is still alive
connector.sendPing();
} catch (MonopriceAudioException e) {
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,45 +591,132 @@ public class MonopriceAudioHandler extends BaseThingHandler implements Monoprice
}
}
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 (!newZoneData.getPower().equals(zoneData.getPower())) {
zoneData.setPower(newZoneData.getPower());
updateChannelState(zoneData.getZone(), CHANNEL_TYPE_POWER);
}
if (!newZoneData.getMute().equals(zoneData.getMute())) {
zoneData.setMute(newZoneData.getMute());
updateChannelState(zoneData.getZone(), CHANNEL_TYPE_MUTE);
}
if (!newZoneData.getDnd().equals(zoneData.getDnd())) {
zoneData.setDnd(newZoneData.getDnd());
updateChannelState(zoneData.getZone(), CHANNEL_TYPE_DND);
}
if (newZoneData.getVolume() != zoneData.getVolume()) {
zoneData.setVolume(newZoneData.getVolume());
updateChannelState(zoneData.getZone(), CHANNEL_TYPE_VOLUME);
}
if (newZoneData.getTreble() != zoneData.getTreble()) {
zoneData.setTreble(newZoneData.getTreble());
updateChannelState(zoneData.getZone(), CHANNEL_TYPE_TREBLE);
}
if (newZoneData.getBass() != zoneData.getBass()) {
zoneData.setBass(newZoneData.getBass());
updateChannelState(zoneData.getZone(), CHANNEL_TYPE_BASS);
}
if (newZoneData.getBalance() != zoneData.getBalance()) {
zoneData.setBalance(newZoneData.getBalance());
updateChannelState(zoneData.getZone(), CHANNEL_TYPE_BALANCE);
}
if (!newZoneData.getSource().equals(zoneData.getSource())) {
zoneData.setSource(newZoneData.getSource());
updateChannelState(zoneData.getZone(), CHANNEL_TYPE_SOURCE);
}
if (!newZoneData.getKeypad().equals(zoneData.getKeypad())) {
zoneData.setKeypad(newZoneData.getKeypad());
updateChannelState(zoneData.getZone(), CHANNEL_TYPE_KEYPAD);
}
}
lastPollingUpdate = System.currentTimeMillis();
}
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);
}
lastPollingUpdate = System.currentTimeMillis();
}
/**
* Update the state of a channel
*
* @param channel the 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(MonopriceAudioZone zone, String channelType, MonopriceAudioZoneDTO zoneData) {
String channel = zone.name().toLowerCase() + CHANNEL_DELIMIT + channelType;
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 = zoneData.isPowerOn() ? OnOffType.ON : OnOffType.OFF;
state = OnOffType.from(zoneData.isPowerOn());
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);
(zoneData.getVolume() - MIN_VOLUME) / (double) (amp.getMaxVol() - MIN_VOLUME) * 100.0);
state = new PercentType(BigDecimal.valueOf(volumePct));
break;
case CHANNEL_TYPE_MUTE:
state = zoneData.isMuted() ? OnOffType.ON : OnOffType.OFF;
state = OnOffType.from(zoneData.isMuted());
break;
case CHANNEL_TYPE_TREBLE:
state = new DecimalType(BigDecimal.valueOf(zoneData.getTreble() - TONE_OFFSET));
state = new DecimalType(BigDecimal.valueOf(zoneData.getTreble() - amp.getToneOffset()));
break;
case CHANNEL_TYPE_BASS:
state = new DecimalType(BigDecimal.valueOf(zoneData.getBass() - TONE_OFFSET));
state = new DecimalType(BigDecimal.valueOf(zoneData.getBass() - amp.getToneOffset()));
break;
case CHANNEL_TYPE_BALANCE:
state = new DecimalType(BigDecimal.valueOf(zoneData.getBalance() - BALANCE_OFFSET));
state = new DecimalType(BigDecimal.valueOf(zoneData.getBalance() - amp.getBalOffset()));
break;
case CHANNEL_TYPE_DND:
state = zoneData.isDndOn() ? OnOffType.ON : OnOffType.OFF;
state = OnOffType.from(zoneData.isDndOn());
break;
case CHANNEL_TYPE_PAGE:
state = zoneData.isPageActive() ? OpenClosedType.OPEN : OpenClosedType.CLOSED;
@ -636,74 +729,5 @@ public class MonopriceAudioHandler extends BaseThingHandler implements Monoprice
}
updateState(channel, state);
}
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 (!matcher.group(3).equals(zoneData.getPower())) {
zoneData.setPower(matcher.group(3));
updateChannelState(zone, CHANNEL_TYPE_POWER, zoneData);
}
if (!matcher.group(4).equals(zoneData.getMute())) {
zoneData.setMute(matcher.group(4));
updateChannelState(zone, CHANNEL_TYPE_MUTE, zoneData);
}
if (!matcher.group(5).equals(zoneData.getDnd())) {
zoneData.setDnd(matcher.group(5));
updateChannelState(zone, CHANNEL_TYPE_DND, zoneData);
}
int volume = Integer.parseInt(matcher.group(6));
if (volume != zoneData.getVolume()) {
zoneData.setVolume(volume);
updateChannelState(zone, CHANNEL_TYPE_VOLUME, zoneData);
}
int treble = Integer.parseInt(matcher.group(7));
if (treble != zoneData.getTreble()) {
zoneData.setTreble(treble);
updateChannelState(zone, CHANNEL_TYPE_TREBLE, zoneData);
}
int bass = Integer.parseInt(matcher.group(8));
if (bass != zoneData.getBass()) {
zoneData.setBass(bass);
updateChannelState(zone, CHANNEL_TYPE_BASS, zoneData);
}
int balance = Integer.parseInt(matcher.group(9));
if (balance != zoneData.getBalance()) {
zoneData.setBalance(balance);
updateChannelState(zone, CHANNEL_TYPE_BALANCE, zoneData);
}
if (!matcher.group(10).equals(zoneData.getSource())) {
zoneData.setSource(matcher.group(10));
updateChannelState(zone, CHANNEL_TYPE_SOURCE, zoneData);
}
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();
}
}

View 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>

View 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

View File

@ -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>

View File

@ -100,13 +100,12 @@
<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>
<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 Amplifier (Default 8080 for amps with built-in
Serial over IP)</description>
<default>8080</default>
<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>
@ -116,7 +115,7 @@
</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>
<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">
@ -160,6 +159,11 @@
<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>

View File

@ -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>

View File

@ -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>