diff --git a/bundles/org.openhab.binding.monopriceaudio/README.md b/bundles/org.openhab.binding.monopriceaudio/README.md index c8561f4ba..f6a866fde 100644 --- a/bundles/org.openhab.binding.monopriceaudio/README.md +++ b/bundles/org.openhab.binding.monopriceaudio/README.md @@ -1,105 +1,143 @@ # Monoprice Whole House Audio Binding -This binding can be used to control a Monoprice MPR-SG6Z (10761), Monoprice Passive Matrix (39261) & Dayton Audio DAX66 whole house multi-zone amplifier system. -All amplifier functions available through the serial port interface can be controlled by the binding. -Up to 18 zones can be controlled when 3 amps are connected together (if not all zones on the amp are used they can be excluded via configuration). -Activating the 'Page All Zones' feature can only be done through the +12v trigger input on the back of the amplifier. +This binding can be used to control the following types of whole house multi-zone amplifier systems: + +- Monoprice MPR-SG6Z (10761), Monoprice Passive Matrix (39261), Dayton Audio DAX66 or compatible clones +- Monoprice 31028 or OSD Audio PAM1270 **(untested)** +- Dayton Audio DAX88 **(untested)** +- Xantech MRC88, MX88, MRAUDIO8X8 or CM8X8 **(untested)** The binding supports two different kinds of connections: -- serial connection, +- serial port connection - serial over IP connection For users without a serial port on the server side, you can use a USB to serial adapter. You don't need to have your whole house amplifier device directly connected to your openHAB server. -Some newer versions of the amplifier have a built-in ethernet port that supports serial over IP. +Some newer versions of the amplifier have a built-in Ethernet port that supports serial over IP. Or you can connect it for example to a Raspberry Pi and use [ser2net Linux tool](https://sourceforge.net/projects/ser2net/) to make the serial connection available on the LAN (serial over IP). ## Supported Things -Monoprice 10761 & 39261 and Dayton Audio DAX66 Amplifiers use the `amplifier` thing id. Up to 18 zones with 3 linked amps, 6 source inputs. -Note: Compatible clones (including 4 zone versions) from McLELLAND, Factor, Soundavo, etc. should work as well. +Monoprice 10761 & 39261 or Dayton Audio DAX66 amplifiers use the `amplifier` thing id. Up to 18 zones with 3 linked amps and 6 source inputs are supported. +Note: Compatible clones (including 4 zone versions) from McLELLAND, Factor, Soundavo, etc. should work as well. + +***The following three thing types were implemented via available documentation only and have not been tested. Please open an issue for any bugs found when using these thing types.*** + +Monoprice 31028 or OSD Audio PAM1270 70 volt amplifiers use the `monoprice70` thing id. 6 zones per amp (not linkable) and 2 source inputs are supported. + +Dayton Audio DAX88 amplifiers use the `dax88` thing id. 8 zones (2 un-amplified) per amp (not linkable) and 8 source inputs are supported. + +Xantech MRC88, MX88, MRAUDIO8X8 or CM8X8 amplifiers use the `xantech` thing id. Up to 16 zones with 2 linked amps and 8 source inputs are supported. +Some Xantech amps provide unsolicited zone updates for keypad actions and may work with the `disableKeypadPolling` option set to true which will prevent un-necessary polling of the amplifier. +Note: MRC44 amps do not support serial control. ## Discovery Discovery is not supported. You have to add all things manually. -## Binding Configuration - -There are no overall binding configuration settings that need to be set. -All settings are through thing configuration parameters. - ## Thing Configuration -The thing has the following configuration parameters: +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 | -| Address | host | Host name or IP address of the amplifier or serial over IP device | Host name or IP | -| Port | port | Communication port (default 8080 for newer amps with built-in serial over IP) | TCP port number | -| Number of Zones | numZones | (Optional) Number of amplifier zones to utilize in the binding (up to 18 zones with 3 amplifiers connected together) | 1-18; default 6 | -| Polling Interval | pollingInterval | (Optional) Configures how often (in seconds) to poll the amplifier to check for zone updates | 5-60; default 15 | -| Ignore Zones | ignoreZones | (Optional) A comma seperated list of Zone numbers that will ignore the 'All Zone' (except All Off) commands | ie: "1,6,10" | -| Initial All Volume | initialAllVolume | (Optional) When 'All' zones are activated, the volume will reset to this value to prevent excessive blaring of sound ;) | 1-30; default 10 | -| Source 1 Input Label | inputLabel1 | (Optional) Friendly name for the input source to be displayed in the UI (ie: Chromecast, Radio, CD, etc.) (default "Source 1") | A free text name | -| Source 2 Input Label | inputLabel2 | (Optional) Friendly name for the input source to be displayed in the UI (ie: Chromecast, Radio, CD, etc.) (default "Source 2") | A free text name | -| Source 3 Input Label | inputLabel3 | (Optional) Friendly name for the input source to be displayed in the UI (ie: Chromecast, Radio, CD, etc.) (default "Source 3") | A free text name | -| Source 4 Input Label | inputLabel4 | (Optional) Friendly name for the input source to be displayed in the UI (ie: Chromecast, Radio, CD, etc.) (default "Source 4") | A free text name | -| Source 5 Input Label | inputLabel5 | (Optional) Friendly name for the input source to be displayed in the UI (ie: Chromecast, Radio, CD, etc.) (default "Source 5") | A free text name | -| Source 6 Input Label | inputLabel6 | (Optional) Friendly name for the input source to be displayed in the UI (ie: Chromecast, Radio, CD, etc.) (default "Source 6") | A free text name | +| Parameter Label | Parameter ID | Description | Accepted values | +|------------------------|----------------------|--------------------------------------------------------------------------------------------------------------------------------|------------------| +| Serial Port | serialPort | Serial port to use for connecting to the whole house amplifier device | Serial port name | +| Address | host | Host name or IP address of the amplifier or serial over IP device | Host name or IP | +| Port | port | Communication port (8080 for newer amps with built-in serial over IP) | TCP port number | +| Number of Zones | numZones | (Optional) Number of amplifier zones to utilize in the binding (See Supported Things for max number of zones per Thing type) | 1-18; default 6 | +| Polling Interval | pollingInterval | (Optional) Configures how often (in seconds) to poll the amplifier to check for zone updates | 5-60; default 15 | +| Ignore Zones | ignoreZones | (Optional) A comma separated list of Zone numbers that will ignore the 'All Zone' (except All Off) commands | ie: "1,6,10" | +| Initial All Volume | initialAllVolume | (Optional) When 'All' zones are activated, the volume will reset to this value to prevent excessive blaring of sound ;) | 1-30; default 10 | +| Source 1 Input Label | inputLabel1 | (Optional) Friendly name for the input source to be displayed in the UI (ie: Chromecast, Radio, CD, etc.) (default "Source 1") | A free text name | +| Source 2 Input Label | inputLabel2 | (Optional) Friendly name for the input source to be displayed in the UI (ie: Chromecast, Radio, CD, etc.) (default "Source 2") | A free text name | +| Source 3 Input Label | inputLabel3 | (Optional) Friendly name for the input source to be displayed in the UI (ie: Chromecast, Radio, CD, etc.) (default "Source 3") | A free text name | +| Source 4 Input Label | inputLabel4 | (Optional) Friendly name for the input source to be displayed in the UI (ie: Chromecast, Radio, CD, etc.) (default "Source 4") | A free text name | +| Source 5 Input Label | inputLabel5 | (Optional) Friendly name for the input source to be displayed in the UI (ie: Chromecast, Radio, CD, etc.) (default "Source 5") | A free text name | +| Source 6 Input Label | inputLabel6 | (Optional) Friendly name for the input source to be displayed in the UI (ie: Chromecast, Radio, CD, etc.) (default "Source 6") | A free text name | +| Source 7 Input Label | inputLabel7 | (Optional) Friendly name for the input source to be displayed in the UI (ie: Chromecast, Radio, CD, etc.) (default "Source 7") | A free text name | +| Source 8 Input Label | inputLabel8 | (Optional) Friendly name for the input source to be displayed in the UI (ie: Chromecast, Radio, CD, etc.) (default "Source 8") | A free text name | +| Disable Keypad Polling | disableKeypadPolling | Set to **true** if physical keypads are not used so the binding will not needlessly poll the amplifier zones for changes | true or false | Some notes: +- On the 10761/DAX66 amp, activating the 'Page All Zones' feature can only be done through the +12v trigger input on the back of the amplifier. + - On Linux, you may get an error stating the serial port cannot be opened when the MonopriceAudio binding tries to load. - You can get around this by adding the `openhab` user to the `dialout` group like this: `usermod -a -G dialout openhab`. - Also on Linux you may have issues with the USB if using two serial USB devices e.g. MonopriceAudio and RFXcom. - See the [general documentation about serial port configuration](/docs/administration/serial.html) for more on symlinking the USB ports. -- Here is an example of ser2net.conf you can use to share your serial port /dev/ttyUSB0 on IP port 8080 using [ser2net Linux tool](https://sourceforge.net/projects/ser2net/): +- Here is an example of ser2net.conf (for ser2net version < 4) you can use to share your serial port /dev/ttyUSB0 on IP port 8080 using [ser2net Linux tool](https://sourceforge.net/projects/ser2net/): ```text 8080:raw:0:/dev/ttyUSB0:9600 8DATABITS NONE 1STOPBIT LOCAL ``` +- Here is an example of ser2net.yaml (for ser2net version >= 4) you can use to share your serial port /dev/ttyUSB0 on IP port 8080 using [ser2net Linux tool](https://sourceforge.net/projects/ser2net/): + +```yaml +connection: &conMono + accepter: tcp,8080 + enable: on + options: + kickolduser: true + connector: serialdev, + /dev/ttyUSB0, + 9600n81,local +``` + ## Channels The following channels are available: +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#allmute | Switch | Mute or unmute all zones simultaneously (except ignoreZones) | -| zoneN#power (where N= 1-18) | Switch | Turn the power for a zone on or off | -| zoneN#source (where N= 1-18) | Number | Select the input source for a zone (1-6) | -| zoneN#volume (where N= 1-18) | Dimmer | Control the volume for a zone (0-100%) [translates to 0-38] | -| zoneN#mute (where N= 1-18) | Switch | Mute or unmute a zone | -| zoneN#treble (where N= 1-18) | Number | Adjust the treble control for a zone (-7 to 7) -7=none, 0=flat, 7=full | -| zoneN#bass (where N= 1-18) | Number | Adjust the bass control for a zone (-7 to 7) -7=none, 0=flat, 7=full | -| zoneN#balance (where N= 1-18) | Number | Adjust the balance control for a zone (-10 to 10) -10=left, 0=center, 10=right | -| zoneN#dnd (where N= 1-18) | Switch | Turn on or off the Do Not Disturb for the zone (for when the amplifier's external page trigger is activated) | -| zoneN#page (where N= 1-18) | Contact | Indicates if the page input is activated for the zone | -| zoneN#keypad (where N= 1-18) | Contact | Indicates if the physical keypad is attached to a zone | +| Channel ID | Item Type | Description | +|-------------------------------|-----------|---------------------------------------------------------------------------------------------------------------------------------------| +| all#allpower | Switch | Turn all zones on or off simultaneously (those specified by the ignoreZones config option will not turn on) | +| all#allsource | Number | Select the input source for all zones simultaneously (1-8) [number of sources is amplifier dependent] (except ignoreZones) | +| all#allvolume | Dimmer | Control the volume for all zones simultaneously (0-100%) [translates to the particular amplifier's volume range] (except ignoreZones) | +| all#allmute | Switch | Mute or unmute all zones simultaneously (except ignoreZones) | +| zoneN#power (where N= 1-18) | Switch | Turn the power for a zone on or off | +| zoneN#source (where N= 1-18) | Number | Select the input source for a zone (1-8) [number of sources is amplifier dependent] | +| zoneN#volume (where N= 1-18) | Dimmer | Control the volume for a zone (0-100%) [translates to the particular amplifier's volume range] | +| zoneN#mute (where N= 1-18) | Switch | Mute or unmute a zone | +| zoneN#treble (where N= 1-18) | Number | Adjust the treble control for a zone [range is amplifier dependent] | +| zoneN#bass (where N= 1-18) | Number | Adjust the bass control for a zone [range is amplifier dependent] | +| zoneN#balance (where N= 1-18) | Number | Adjust the balance control for a zone [0=center, range is amplifier dependent] | +| zoneN#dnd (where N= 1-18) | Switch | Turn on or off the Do Not Disturb for the zone (for when the amplifier's external page trigger is activated) | +| zoneN#page (where N= 1-18) | Contact | Indicates if the page input is activated for the zone | +| zoneN#keypad (where N= 1-18) | Contact | Indicates if the physical keypad is attached to a zone | ## Full Example monoprice.things: ```java -// serial port connection -monopriceaudio:amplifier:myamp "Monoprice WHA" [ serialPort="COM5", pollingInterval=15, numZones=6, inputLabel1="Chromecast", inputLabel2="Radio", inputLabel3="CD Player", inputLabel4="Bluetooth Audio", inputLabel5="HTPC", inputLabel6="Phono"] +// Monoprice 10761, 39261 / DAX66 (serial port connection) +monopriceaudio:amplifier:myamp "Monoprice WHA" [ serialPort="COM5", pollingInterval=15, numZones=6, inputLabel1="Chromecast", inputLabel2="Radio", inputLabel3="CD Player", inputLabel4="Bluetooth Audio", inputLabel5="HTPC", inputLabel6="Phono" ] -// 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 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) } ``` diff --git a/bundles/org.openhab.binding.monopriceaudio/src/main/java/org/openhab/binding/monopriceaudio/internal/MonopriceAudioBindingConstants.java b/bundles/org.openhab.binding.monopriceaudio/src/main/java/org/openhab/binding/monopriceaudio/internal/MonopriceAudioBindingConstants.java index 063218897..2f08c00de 100644 --- a/bundles/org.openhab.binding.monopriceaudio/src/main/java/org/openhab/binding/monopriceaudio/internal/MonopriceAudioBindingConstants.java +++ b/bundles/org.openhab.binding.monopriceaudio/src/main/java/org/openhab/binding/monopriceaudio/internal/MonopriceAudioBindingConstants.java @@ -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; } diff --git a/bundles/org.openhab.binding.monopriceaudio/src/main/java/org/openhab/binding/monopriceaudio/internal/MonopriceAudioHandlerFactory.java b/bundles/org.openhab.binding.monopriceaudio/src/main/java/org/openhab/binding/monopriceaudio/internal/MonopriceAudioHandlerFactory.java index df6a58edd..b8e91bd8b 100644 --- a/bundles/org.openhab.binding.monopriceaudio/src/main/java/org/openhab/binding/monopriceaudio/internal/MonopriceAudioHandlerFactory.java +++ b/bundles/org.openhab.binding.monopriceaudio/src/main/java/org/openhab/binding/monopriceaudio/internal/MonopriceAudioHandlerFactory.java @@ -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 SUPPORTED_THING_TYPES_UIDS = Collections.singleton(THING_TYPE_AMP); + private static final Set SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_MP, THING_TYPE_MP70, + THING_TYPE_DAX88, THING_TYPE_XT); private final SerialPortManager serialPortManager; private final MonopriceAudioStateDescriptionOptionProvider stateDescriptionProvider; + private final Logger logger = LoggerFactory.getLogger(MonopriceAudioHandlerFactory.class); + @Activate public MonopriceAudioHandlerFactory(final @Reference MonopriceAudioStateDescriptionOptionProvider provider, final @Reference SerialPortManager serialPortManager) { @@ -62,10 +68,26 @@ public class MonopriceAudioHandlerFactory extends BaseThingHandlerFactory { protected @Nullable ThingHandler createHandler(Thing thing) { ThingTypeUID thingTypeUID = thing.getThingTypeUID(); - if (SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID)) { - return new MonopriceAudioHandler(thing, stateDescriptionProvider, serialPortManager); + if (THING_TYPE_MP.equals(thingTypeUID)) { + return new MonopriceAudioHandler(thing, AmplifierModel.MONOPRICE, stateDescriptionProvider, + serialPortManager); } + if (THING_TYPE_MP70.equals(thingTypeUID)) { + return new MonopriceAudioHandler(thing, AmplifierModel.MONOPRICE70, stateDescriptionProvider, + serialPortManager); + } + + if (THING_TYPE_DAX88.equals(thingTypeUID)) { + return new MonopriceAudioHandler(thing, AmplifierModel.DAX88, stateDescriptionProvider, serialPortManager); + } + + if (THING_TYPE_XT.equals(thingTypeUID)) { + return new MonopriceAudioHandler(thing, AmplifierModel.XANTECH, stateDescriptionProvider, + serialPortManager); + } + + logger.warn("Unknown thing type: {}: {}", thingTypeUID.getId(), thingTypeUID.getBindingId()); return null; } } diff --git a/bundles/org.openhab.binding.monopriceaudio/src/main/java/org/openhab/binding/monopriceaudio/internal/communication/AmplifierModel.java b/bundles/org.openhab.binding.monopriceaudio/src/main/java/org/openhab/binding/monopriceaudio/internal/communication/AmplifierModel.java new file mode 100644 index 000000000..6fc2dc6c8 --- /dev/null +++ b/bundles/org.openhab.binding.monopriceaudio/src/main/java/org/openhab/binding/monopriceaudio/internal/communication/AmplifierModel.java @@ -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 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 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 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 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 zoneIds; + private Map 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 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 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 getZoneIds() { + return zoneIds; + } + + public String getFormattedValue(Integer value) { + if (padNumbers) { + return String.format("%02d", value); + } else { + return value.toString(); + } + } + + public String getOnStr() { + if (padNumbers) { + return ON_STR_PAD; + } else { + return ON_STR; + } + } + + public String getOffStr() { + if (padNumbers) { + return OFF_STR_PAD; + } else { + return OFF_STR; + } + } +} diff --git a/bundles/org.openhab.binding.monopriceaudio/src/main/java/org/openhab/binding/monopriceaudio/internal/communication/MonopriceAudioCommand.java b/bundles/org.openhab.binding.monopriceaudio/src/main/java/org/openhab/binding/monopriceaudio/internal/communication/MonopriceAudioCommand.java deleted file mode 100644 index 58fc0f4c2..000000000 --- a/bundles/org.openhab.binding.monopriceaudio/src/main/java/org/openhab/binding/monopriceaudio/internal/communication/MonopriceAudioCommand.java +++ /dev/null @@ -1,48 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.binding.monopriceaudio.internal.communication; - -import org.eclipse.jdt.annotation.NonNullByDefault; - -/** - * Represents the different kinds of commands - * - * @author Michael Lobstein - Initial contribution - */ -@NonNullByDefault -public enum MonopriceAudioCommand { - QUERY("?"), - POWER("PR"), - SOURCE("CH"), - VOLUME("VO"), - MUTE("MU"), - TREBLE("TR"), - BASS("BS"), - BALANCE("BL"), - DND("DT"); - - private final String value; - - MonopriceAudioCommand(String value) { - this.value = value; - } - - /** - * Get the command name - * - * @return the command name - */ - public String getValue() { - return value; - } -} diff --git a/bundles/org.openhab.binding.monopriceaudio/src/main/java/org/openhab/binding/monopriceaudio/internal/communication/MonopriceAudioConnector.java b/bundles/org.openhab.binding.monopriceaudio/src/main/java/org/openhab/binding/monopriceaudio/internal/communication/MonopriceAudioConnector.java index 1ad00ef90..4eb655271 100644 --- a/bundles/org.openhab.binding.monopriceaudio/src/main/java/org/openhab/binding/monopriceaudio/internal/communication/MonopriceAudioConnector.java +++ b/bundles/org.openhab.binding.monopriceaudio/src/main/java/org/openhab/binding/monopriceaudio/internal/communication/MonopriceAudioConnector.java @@ -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 = ""; + 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()); + } - 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); + /** + * 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 { - throw new MonopriceAudioException("Send command \"" + messageStr + "\" failed: passed in value is null"); + // otherwise send the raw cmd from the query() methods + messageStr = cmd + amp.getCmdSuffix(); } - messageStr += END_CMD; logger.debug("Send command {}", messageStr); OutputStream dataOut = this.dataOut; @@ -204,7 +246,7 @@ public abstract class MonopriceAudioConnector { dataOut.write(messageStr.getBytes(StandardCharsets.US_ASCII)); dataOut.flush(); } catch (IOException e) { - throw new MonopriceAudioException("Send command \"" + cmd.getValue() + "\" failed: " + e.getMessage(), e); + throw new MonopriceAudioException("Send command \"" + messageStr + "\" failed: " + e.getMessage(), e); } } @@ -232,20 +274,20 @@ public abstract class MonopriceAudioConnector { * @param incomingMessage the received message */ public void handleIncomingMessage(byte[] incomingMessage) { - String message = new String(incomingMessage, StandardCharsets.US_ASCII).trim(); - - 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); } diff --git a/bundles/org.openhab.binding.monopriceaudio/src/main/java/org/openhab/binding/monopriceaudio/internal/communication/MonopriceAudioDefaultConnector.java b/bundles/org.openhab.binding.monopriceaudio/src/main/java/org/openhab/binding/monopriceaudio/internal/communication/MonopriceAudioDefaultConnector.java index c15069914..715fe925c 100644 --- a/bundles/org.openhab.binding.monopriceaudio/src/main/java/org/openhab/binding/monopriceaudio/internal/communication/MonopriceAudioDefaultConnector.java +++ b/bundles/org.openhab.binding.monopriceaudio/src/main/java/org/openhab/binding/monopriceaudio/internal/communication/MonopriceAudioDefaultConnector.java @@ -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); diff --git a/bundles/org.openhab.binding.monopriceaudio/src/main/java/org/openhab/binding/monopriceaudio/internal/communication/MonopriceAudioIpConnector.java b/bundles/org.openhab.binding.monopriceaudio/src/main/java/org/openhab/binding/monopriceaudio/internal/communication/MonopriceAudioIpConnector.java index 3a0a850be..b8421e0c5 100644 --- a/bundles/org.openhab.binding.monopriceaudio/src/main/java/org/openhab/binding/monopriceaudio/internal/communication/MonopriceAudioIpConnector.java +++ b/bundles/org.openhab.binding.monopriceaudio/src/main/java/org/openhab/binding/monopriceaudio/internal/communication/MonopriceAudioIpConnector.java @@ -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 diff --git a/bundles/org.openhab.binding.monopriceaudio/src/main/java/org/openhab/binding/monopriceaudio/internal/communication/MonopriceAudioReaderThread.java b/bundles/org.openhab.binding.monopriceaudio/src/main/java/org/openhab/binding/monopriceaudio/internal/communication/MonopriceAudioReaderThread.java index 597652809..0a63b74b9 100644 --- a/bundles/org.openhab.binding.monopriceaudio/src/main/java/org/openhab/binding/monopriceaudio/internal/communication/MonopriceAudioReaderThread.java +++ b/bundles/org.openhab.binding.monopriceaudio/src/main/java/org/openhab/binding/monopriceaudio/internal/communication/MonopriceAudioReaderThread.java @@ -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"); diff --git a/bundles/org.openhab.binding.monopriceaudio/src/main/java/org/openhab/binding/monopriceaudio/internal/communication/MonopriceAudioSerialConnector.java b/bundles/org.openhab.binding.monopriceaudio/src/main/java/org/openhab/binding/monopriceaudio/internal/communication/MonopriceAudioSerialConnector.java index f086c858a..aae8f84ae 100644 --- a/bundles/org.openhab.binding.monopriceaudio/src/main/java/org/openhab/binding/monopriceaudio/internal/communication/MonopriceAudioSerialConnector.java +++ b/bundles/org.openhab.binding.monopriceaudio/src/main/java/org/openhab/binding/monopriceaudio/internal/communication/MonopriceAudioSerialConnector.java @@ -51,11 +51,14 @@ public class MonopriceAudioSerialConnector extends MonopriceAudioConnector { * @param serialPortManager the serial port manager * @param serialPortName the serial port name to be used * @param uid the thing uid string + * @param amp the AmplifierModel being used */ - public MonopriceAudioSerialConnector(SerialPortManager serialPortManager, String serialPortName, String uid) { + public MonopriceAudioSerialConnector(SerialPortManager serialPortManager, String serialPortName, String uid, + AmplifierModel amp) { this.serialPortManager = serialPortManager; this.serialPortName = serialPortName; this.uid = uid; + setAmplifierModel(amp); } @Override diff --git a/bundles/org.openhab.binding.monopriceaudio/src/main/java/org/openhab/binding/monopriceaudio/internal/communication/MonopriceAudioZone.java b/bundles/org.openhab.binding.monopriceaudio/src/main/java/org/openhab/binding/monopriceaudio/internal/communication/MonopriceAudioZone.java deleted file mode 100644 index 22065c6ef..000000000 --- a/bundles/org.openhab.binding.monopriceaudio/src/main/java/org/openhab/binding/monopriceaudio/internal/communication/MonopriceAudioZone.java +++ /dev/null @@ -1,77 +0,0 @@ -/** - * Copyright (c) 2010-2023 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.binding.monopriceaudio.internal.communication; - -import java.util.Arrays; -import java.util.List; -import java.util.stream.Collectors; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.binding.monopriceaudio.internal.MonopriceAudioException; - -/** - * Represents the different internal zone IDs of the Monoprice Whole House Amplifier - * - * @author Michael Lobstein - Initial contribution - */ -@NonNullByDefault -public enum MonopriceAudioZone { - - ALL("all"), - ZONE1("11"), - ZONE2("12"), - ZONE3("13"), - ZONE4("14"), - ZONE5("15"), - ZONE6("16"), - ZONE7("21"), - ZONE8("22"), - ZONE9("23"), - ZONE10("24"), - ZONE11("25"), - ZONE12("26"), - ZONE13("31"), - ZONE14("32"), - ZONE15("33"), - ZONE16("34"), - ZONE17("35"), - ZONE18("36"); - - private final String zoneId; - - // make a list of all valid zone names - public static final List 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 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; - } -} diff --git a/bundles/org.openhab.binding.monopriceaudio/src/main/java/org/openhab/binding/monopriceaudio/internal/configuration/MonopriceAudioThingConfiguration.java b/bundles/org.openhab.binding.monopriceaudio/src/main/java/org/openhab/binding/monopriceaudio/internal/configuration/MonopriceAudioThingConfiguration.java index f70f542d6..d64f90d75 100644 --- a/bundles/org.openhab.binding.monopriceaudio/src/main/java/org/openhab/binding/monopriceaudio/internal/configuration/MonopriceAudioThingConfiguration.java +++ b/bundles/org.openhab.binding.monopriceaudio/src/main/java/org/openhab/binding/monopriceaudio/internal/configuration/MonopriceAudioThingConfiguration.java @@ -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; } diff --git a/bundles/org.openhab.binding.monopriceaudio/src/main/java/org/openhab/binding/monopriceaudio/internal/dto/MonopriceAudioZoneDTO.java b/bundles/org.openhab.binding.monopriceaudio/src/main/java/org/openhab/binding/monopriceaudio/internal/dto/MonopriceAudioZoneDTO.java index b8fba4788..3994d18bd 100644 --- a/bundles/org.openhab.binding.monopriceaudio/src/main/java/org/openhab/binding/monopriceaudio/internal/dto/MonopriceAudioZoneDTO.java +++ b/bundles/org.openhab.binding.monopriceaudio/src/main/java/org/openhab/binding/monopriceaudio/internal/dto/MonopriceAudioZoneDTO.java @@ -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; } } diff --git a/bundles/org.openhab.binding.monopriceaudio/src/main/java/org/openhab/binding/monopriceaudio/internal/handler/MonopriceAudioHandler.java b/bundles/org.openhab.binding.monopriceaudio/src/main/java/org/openhab/binding/monopriceaudio/internal/handler/MonopriceAudioHandler.java index 9e244d9f2..2c3a2024a 100644 --- a/bundles/org.openhab.binding.monopriceaudio/src/main/java/org/openhab/binding/monopriceaudio/internal/handler/MonopriceAudioHandler.java +++ b/bundles/org.openhab.binding.monopriceaudio/src/main/java/org/openhab/binding/monopriceaudio/internal/handler/MonopriceAudioHandler.java @@ -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 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 zoneDataMap = Map.of(ZONE, new MonopriceAudioZoneDTO()); private Set 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 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 activeZones = IntStream.range(1, numZones + 1).boxed().collect(Collectors.toList()); + List 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 channels = new ArrayList<>(this.getThing().getChannels()); - List zonesToRemove = IntStream.range(numZones + 1, MAX_ZONES + 1).boxed() + List 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 zoneStream = MonopriceAudioZone.VALID_ZONES.stream().limit(numZones); + Stream zoneStream = amp.getZoneIds().stream().limit(numZones); try { switch (channelType) { case CHANNEL_TYPE_POWER: if (command instanceof OnOffType) { - connector.sendCommand(zone, MonopriceAudioCommand.POWER, command == OnOffType.ON ? 1 : 0); - zoneDataMap.get(zone.getZoneId()).setPower(command == OnOffType.ON ? ON_STR : OFF_STR); + connector.sendCommand(zoneId, amp.getPowerCmd(), command == OnOffType.ON ? ONE : ZERO); + zoneDataMap.get(zoneId) + .setPower(command == OnOffType.ON ? amp.getOnStr() : amp.getOffStr()); } break; case CHANNEL_TYPE_SOURCE: if (command instanceof DecimalType) { - int value = ((DecimalType) command).intValue(); - if (value >= ONE && value <= MAX_SRC) { - logger.debug("Got source command {} zone {}", value, zone); - connector.sendCommand(zone, MonopriceAudioCommand.SOURCE, value); - zoneDataMap.get(zone.getZoneId()).setSource(String.format("%02d", value)); + final int value = ((DecimalType) command).intValue(); + if (value >= ONE && value <= amp.getNumSources()) { + logger.debug("Got source command {} zone {}", value, zoneId); + connector.sendCommand(zoneId, amp.getSourceCmd(), value); + zoneDataMap.get(zoneId).setSource(amp.getFormattedValue(value)); } } break; case CHANNEL_TYPE_VOLUME: if (command instanceof PercentType) { - int value = (int) Math - .round(((PercentType) command).doubleValue() / 100.0 * (MAX_VOLUME - MIN_VOLUME)) + final int value = (int) Math.round( + ((PercentType) command).doubleValue() / 100.0 * (amp.getMaxVol() - MIN_VOLUME)) + MIN_VOLUME; - logger.debug("Got volume command {} zone {}", value, zone); - connector.sendCommand(zone, MonopriceAudioCommand.VOLUME, value); - zoneDataMap.get(zone.getZoneId()).setVolume(value); + logger.debug("Got volume command {} zone {}", value, zoneId); + connector.sendCommand(zoneId, amp.getVolumeCmd(), value); + zoneDataMap.get(zoneId).setVolume(value); } break; case CHANNEL_TYPE_MUTE: if (command instanceof OnOffType) { - connector.sendCommand(zone, MonopriceAudioCommand.MUTE, command == OnOffType.ON ? 1 : 0); - zoneDataMap.get(zone.getZoneId()).setMute(command == OnOffType.ON ? ON_STR : OFF_STR); + connector.sendCommand(zoneId, amp.getMuteCmd(), command == OnOffType.ON ? ONE : ZERO); + zoneDataMap.get(zoneId).setMute(command == OnOffType.ON ? amp.getOnStr() : amp.getOffStr()); } break; case CHANNEL_TYPE_TREBLE: if (command instanceof DecimalType) { - int value = ((DecimalType) command).intValue(); - if (value >= MIN_TONE && value <= MAX_TONE) { - logger.debug("Got treble command {} zone {}", value, zone); - connector.sendCommand(zone, MonopriceAudioCommand.TREBLE, value + TONE_OFFSET); - zoneDataMap.get(zone.getZoneId()).setTreble(value + TONE_OFFSET); + final int value = ((DecimalType) command).intValue(); + if (value >= amp.getMinTone() && value <= amp.getMaxTone()) { + logger.debug("Got treble command {} zone {}", value, zoneId); + connector.sendCommand(zoneId, amp.getTrebleCmd(), value + amp.getToneOffset()); + zoneDataMap.get(zoneId).setTreble(value + amp.getToneOffset()); } } break; case CHANNEL_TYPE_BASS: if (command instanceof DecimalType) { - int value = ((DecimalType) command).intValue(); - if (value >= MIN_TONE && value <= MAX_TONE) { - logger.debug("Got bass command {} zone {}", value, zone); - connector.sendCommand(zone, MonopriceAudioCommand.BASS, value + TONE_OFFSET); - zoneDataMap.get(zone.getZoneId()).setBass(value + TONE_OFFSET); + final int value = ((DecimalType) command).intValue(); + if (value >= amp.getMinTone() && value <= amp.getMaxTone()) { + logger.debug("Got bass command {} zone {}", value, zoneId); + connector.sendCommand(zoneId, amp.getBassCmd(), value + amp.getToneOffset()); + zoneDataMap.get(zoneId).setBass(value + amp.getToneOffset()); } } break; case CHANNEL_TYPE_BALANCE: if (command instanceof DecimalType) { - int value = ((DecimalType) command).intValue(); - if (value >= MIN_BALANCE && value <= MAX_BALANCE) { - logger.debug("Got balance command {} zone {}", value, zone); - connector.sendCommand(zone, MonopriceAudioCommand.BALANCE, value + BALANCE_OFFSET); - zoneDataMap.get(zone.getZoneId()).setBalance(value + BALANCE_OFFSET); + final int value = ((DecimalType) command).intValue(); + if (value >= amp.getMinBal() && value <= amp.getMaxBal()) { + logger.debug("Got balance command {} zone {}", value, zoneId); + connector.sendCommand(zoneId, amp.getBalanceCmd(), value + amp.getBalOffset()); + zoneDataMap.get(zoneId).setBalance(value + amp.getBalOffset()); } } break; case CHANNEL_TYPE_DND: if (command instanceof OnOffType) { - connector.sendCommand(zone, MonopriceAudioCommand.DND, command == OnOffType.ON ? 1 : 0); - zoneDataMap.get(zone.getZoneId()).setDnd(command == OnOffType.ON ? ON_STR : OFF_STR); + connector.sendCommand(zoneId, amp.getDndCmd(), command == OnOffType.ON ? ONE : ZERO); + zoneDataMap.get(zoneId).setDnd(command == OnOffType.ON ? amp.getOnStr() : amp.getOffStr()); } break; case CHANNEL_TYPE_ALLPOWER: if (command instanceof OnOffType) { - zoneStream.forEach((zoneName) -> { - if (command == OnOffType.OFF || !ignoreZones.contains(zoneName)) { + final int cmd = command == OnOffType.ON ? ONE : ZERO; + zoneStream.forEach((streamZoneId) -> { + if (command == OnOffType.OFF || !ignoreZones.contains(amp.getZoneName(streamZoneId))) { try { - connector.sendCommand(MonopriceAudioZone.valueOf(zoneName), - MonopriceAudioCommand.POWER, command == OnOffType.ON ? 1 : 0); + connector.sendCommand(streamZoneId, amp.getPowerCmd(), cmd); + zoneDataMap.get(streamZoneId).setPower(amp.getFormattedValue(cmd)); + updateChannelState(streamZoneId, CHANNEL_TYPE_POWER); + if (command == OnOffType.ON) { // reset the volume of each zone to allVolume - connector.sendCommand(MonopriceAudioZone.valueOf(zoneName), - MonopriceAudioCommand.VOLUME, allVolume); + connector.sendCommand(streamZoneId, amp.getVolumeCmd(), allVolume); + zoneDataMap.get(streamZoneId).setVolume(allVolume); + updateChannelState(streamZoneId, CHANNEL_TYPE_VOLUME); } } catch (MonopriceAudioException e) { - logger.warn("Error Turning All Zones On: {}", e.getMessage()); + logger.debug("Error Turning All Zones On: {}", e.getMessage()); } } @@ -357,15 +335,19 @@ public class MonopriceAudioHandler extends BaseThingHandler implements Monoprice break; case CHANNEL_TYPE_ALLSOURCE: if (command instanceof DecimalType) { - int value = ((DecimalType) command).intValue(); - if (value >= ONE && value <= MAX_SRC) { - zoneStream.forEach((zoneName) -> { - if (!ignoreZones.contains(zoneName)) { + final int value = ((DecimalType) command).intValue(); + if (value >= ONE && value <= amp.getNumSources()) { + zoneStream.forEach((streamZoneId) -> { + if (!ignoreZones.contains(amp.getZoneName(streamZoneId))) { try { - connector.sendCommand(MonopriceAudioZone.valueOf(zoneName), - MonopriceAudioCommand.SOURCE, value); + connector.sendCommand(streamZoneId, amp.getSourceCmd(), value); + if (zoneDataMap.get(streamZoneId).isPowerOn() + && !zoneDataMap.get(streamZoneId).isMuted()) { + zoneDataMap.get(streamZoneId).setSource(amp.getFormattedValue(value)); + updateChannelState(streamZoneId, CHANNEL_TYPE_SOURCE); + } } catch (MonopriceAudioException e) { - logger.warn("Error Setting Source for All Zones: {}", e.getMessage()); + logger.debug("Error Setting Source for All Zones: {}", e.getMessage()); } } }); @@ -374,17 +356,20 @@ public class MonopriceAudioHandler extends BaseThingHandler implements Monoprice break; case CHANNEL_TYPE_ALLVOLUME: if (command instanceof PercentType) { - int value = (int) Math - .round(((PercentType) command).doubleValue() / 100.0 * (MAX_VOLUME - MIN_VOLUME)) + allVolume = (int) Math.round( + ((PercentType) command).doubleValue() / 100.0 * (amp.getMaxVol() - MIN_VOLUME)) + MIN_VOLUME; - allVolume = value; - zoneStream.forEach((zoneName) -> { - if (!ignoreZones.contains(zoneName)) { + zoneStream.forEach((streamZoneId) -> { + if (!ignoreZones.contains(amp.getZoneName(streamZoneId))) { try { - connector.sendCommand(MonopriceAudioZone.valueOf(zoneName), - MonopriceAudioCommand.VOLUME, value); + connector.sendCommand(streamZoneId, amp.getVolumeCmd(), allVolume); + if (zoneDataMap.get(streamZoneId).isPowerOn() + && !zoneDataMap.get(streamZoneId).isMuted()) { + zoneDataMap.get(streamZoneId).setVolume(allVolume); + updateChannelState(streamZoneId, CHANNEL_TYPE_VOLUME); + } } catch (MonopriceAudioException e) { - logger.warn("Error Setting Volume for All Zones: {}", e.getMessage()); + logger.debug("Error Setting Volume for All Zones: {}", e.getMessage()); } } }); @@ -392,14 +377,17 @@ public class MonopriceAudioHandler extends BaseThingHandler implements Monoprice break; case CHANNEL_TYPE_ALLMUTE: if (command instanceof OnOffType) { - int cmd = command == OnOffType.ON ? 1 : 0; - zoneStream.forEach((zoneName) -> { - if (!ignoreZones.contains(zoneName)) { + final int cmd = command == OnOffType.ON ? ONE : ZERO; + zoneStream.forEach((streamZoneId) -> { + if (!ignoreZones.contains(amp.getZoneName(streamZoneId))) { try { - connector.sendCommand(MonopriceAudioZone.valueOf(zoneName), - MonopriceAudioCommand.MUTE, cmd); + connector.sendCommand(streamZoneId, amp.getMuteCmd(), cmd); + if (zoneDataMap.get(streamZoneId).isPowerOn()) { + zoneDataMap.get(streamZoneId).setMute(amp.getFormattedValue(cmd)); + updateChannelState(streamZoneId, CHANNEL_TYPE_MUTE); + } } catch (MonopriceAudioException e) { - logger.warn("Error Setting Mute for All Zones: {}", e.getMessage()); + logger.debug("Error Setting Mute for All Zones: {}", e.getMessage()); } } }); @@ -415,8 +403,9 @@ public class MonopriceAudioHandler extends BaseThingHandler implements Monoprice logger.trace("Command {} from channel {} succeeded", command, channel); } } catch (MonopriceAudioException e) { - logger.warn("Command {} from channel {} failed: {}", command, channel, e.getMessage()); - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Sending command failed"); + logger.debug("Command {} from channel {} failed: {}", command, channel, e.getMessage()); + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, + "@text/offline.communication-error-failed"); closeConnection(); scheduleReconnectJob(); } @@ -424,7 +413,7 @@ public class MonopriceAudioHandler extends BaseThingHandler implements Monoprice } /** - * Open the connection with the MonopriceAudio device + * Open the connection to the amplifier * * @return true if the connection is opened successfully or false if not */ @@ -440,7 +429,7 @@ public class MonopriceAudioHandler extends BaseThingHandler implements Monoprice } /** - * Close the connection with the MonopriceAudio device + * Close the connection to the amplifier */ private synchronized void closeConnection() { if (connector.isConnected()) { @@ -453,36 +442,29 @@ public class MonopriceAudioHandler extends BaseThingHandler implements Monoprice @Override public void onNewMessageEvent(MonopriceAudioMessageEvent evt) { String key = evt.getKey(); - String updateData = evt.getValue().trim(); - if (!MonopriceAudioConnector.KEY_ERROR.equals(key)) { - updateStatus(ThingStatus.ONLINE); - } - try { - switch (key) { - case MonopriceAudioConnector.KEY_ERROR: - logger.debug("Reading feedback message failed"); - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Reading thread ended"); - closeConnection(); - break; - case MonopriceAudioConnector.KEY_ZONE_UPDATE: - String zoneId = updateData.substring(0, 2); - MonopriceAudioZoneDTO zoneDTO = zoneDataMap.get(zoneId); - if (MonopriceAudioZone.VALID_ZONE_IDS.contains(zoneId) && zoneDTO != null) { - MonopriceAudioZone targetZone = MonopriceAudioZone.fromZoneId(zoneId); - processZoneUpdate(targetZone, zoneDTO, updateData); + switch (key) { + case MonopriceAudioConnector.KEY_ZONE_UPDATE: + MonopriceAudioZoneDTO newZoneData = amp.getZoneData(evt.getValue()); + MonopriceAudioZoneDTO zoneData = zoneDataMap.get(newZoneData.getZone()); + if (amp.getZoneIds().contains(newZoneData.getZone()) && zoneData != null) { + if (amp == AmplifierModel.MONOPRICE70) { + processMonoprice70Update(zoneData, newZoneData); } else { - logger.warn("invalid event: {} for key: {} or zone data null", evt.getValue(), key); + processZoneUpdate(zoneData, newZoneData); } - break; - default: - logger.debug("onNewMessageEvent: unhandled key {}", key); - break; - } - } catch (NumberFormatException e) { - logger.warn("Invalid value {} for key {}", updateData, key); - } catch (MonopriceAudioException e) { - logger.warn("Error processing zone update: {}", e.getMessage()); + } else { + logger.debug("invalid event: {} for key: {} or zone data null", evt.getValue(), key); + } + break; + + case MonopriceAudioConnector.KEY_PING: + lastPollingUpdate = System.currentTimeMillis(); + break; + + default: + logger.debug("onNewMessageEvent: unhandled key {}", key); + break; } } @@ -500,24 +482,39 @@ public class MonopriceAudioHandler extends BaseThingHandler implements Monoprice String error = null; if (openConnection()) { - try { - long prevUpdateTime = lastPollingUpdate; - connector.queryZone(MonopriceAudioZone.ZONE1); + long prevUpdateTime = lastPollingUpdate; + // poll all zones on the amplifier to get current state + amp.getZoneIds().stream().limit(numZones).forEach((streamZoneId) -> { + try { + connector.queryZone(streamZoneId); - // prevUpdateTime should have changed if a zone update was received - if (lastPollingUpdate == prevUpdateTime) { - error = "Controller not responding to status requests"; + if (amp == AmplifierModel.MONOPRICE70) { + connector.queryTrebBassBalance(streamZoneId); + } + } catch (MonopriceAudioException e) { + logger.debug("Polling error: {}", e.getMessage()); } + }); - } catch (MonopriceAudioException e) { - error = "First command after connection failed"; - logger.warn("{}: {}", error, e.getMessage()); - closeConnection(); + 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 = "@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..."); - // poll each zone up to the number of zones specified in the configuration - MonopriceAudioZone.VALID_ZONES.stream().limit(numZones).forEach((zoneName) -> { + if (!disableKeypadPolling) { + // poll each zone up to the number of zones specified in the configuration + amp.getZoneIds().stream().limit(numZones).forEach((streamZoneId) -> { + try { + connector.queryZone(streamZoneId); + } catch (MonopriceAudioException e) { + logger.debug("Polling error for zone id {}: {}", streamZoneId, e.getMessage()); + } + }); + } else { try { - connector.queryZone(MonopriceAudioZone.valueOf(zoneName)); + // ping only (no zone updates) to verify the connection is still alive + connector.sendPing(); } catch (MonopriceAudioException e) { - logger.warn("Polling error: {}", e.getMessage()); + logger.debug("Ping error: {}", e.getMessage()); } - }); + } - // if the last successful polling update was more than 2.25 intervals ago, the controller + // if the last successful polling update was more than 2.25 intervals ago, the amplifier // is either switched off or not responding even though the connection is still good if ((System.currentTimeMillis() - lastPollingUpdate) > (pollingInterval * 2.25 * 1000)) { - logger.warn("Controller not responding to status requests"); + logger.debug("Amplifier not responding to status requests"); updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, - "Controller not responding to status requests"); + "@text/offline.communication-error-polling"); closeConnection(); scheduleReconnectJob(); } @@ -585,125 +591,143 @@ public class MonopriceAudioHandler extends BaseThingHandler implements Monoprice } } - /** - * Update the state of a channel - * - * @param channel the channel - */ - private void updateChannelState(MonopriceAudioZone zone, String channelType, MonopriceAudioZoneDTO zoneData) { - String channel = zone.name().toLowerCase() + CHANNEL_DELIMIT + channelType; + private void processZoneUpdate(MonopriceAudioZoneDTO zoneData, MonopriceAudioZoneDTO newZoneData) { + // only process the update if something actually changed in this zone since the last polling update + if (!newZoneData.toString().equals(zoneData.toString())) { + if (!newZoneData.getPage().equals(zoneData.getPage())) { + zoneData.setPage(newZoneData.getPage()); + updateChannelState(zoneData.getZone(), CHANNEL_TYPE_PAGE); + } - if (!isLinked(channel)) { - return; - } + if (!newZoneData.getPower().equals(zoneData.getPower())) { + zoneData.setPower(newZoneData.getPower()); + updateChannelState(zoneData.getZone(), CHANNEL_TYPE_POWER); + } - State state = UnDefType.UNDEF; - switch (channelType) { - case CHANNEL_TYPE_POWER: - state = zoneData.isPowerOn() ? OnOffType.ON : OnOffType.OFF; - break; - case CHANNEL_TYPE_SOURCE: - state = new DecimalType(zoneData.getSource()); - break; - case CHANNEL_TYPE_VOLUME: - long volumePct = Math.round( - (double) (zoneData.getVolume() - MIN_VOLUME) / (double) (MAX_VOLUME - MIN_VOLUME) * 100.0); - state = new PercentType(BigDecimal.valueOf(volumePct)); - break; - case CHANNEL_TYPE_MUTE: - state = zoneData.isMuted() ? OnOffType.ON : OnOffType.OFF; - break; - case CHANNEL_TYPE_TREBLE: - state = new DecimalType(BigDecimal.valueOf(zoneData.getTreble() - TONE_OFFSET)); - break; - case CHANNEL_TYPE_BASS: - state = new DecimalType(BigDecimal.valueOf(zoneData.getBass() - TONE_OFFSET)); - break; - case CHANNEL_TYPE_BALANCE: - state = new DecimalType(BigDecimal.valueOf(zoneData.getBalance() - BALANCE_OFFSET)); - break; - case CHANNEL_TYPE_DND: - state = zoneData.isDndOn() ? OnOffType.ON : OnOffType.OFF; - break; - case CHANNEL_TYPE_PAGE: - state = zoneData.isPageActive() ? OpenClosedType.OPEN : OpenClosedType.CLOSED; - break; - case CHANNEL_TYPE_KEYPAD: - state = zoneData.isKeypadActive() ? OpenClosedType.OPEN : OpenClosedType.CLOSED; - break; - default: - break; - } - updateState(channel, state); - } + if (!newZoneData.getMute().equals(zoneData.getMute())) { + zoneData.setMute(newZoneData.getMute()); + updateChannelState(zoneData.getZone(), CHANNEL_TYPE_MUTE); + } - private void processZoneUpdate(MonopriceAudioZone zone, MonopriceAudioZoneDTO zoneData, String newZoneData) { - // only process the update if something actually changed in this zone since the last time through - if (!newZoneData.equals(zoneData.toString())) { - // example status string: 1200010000130809100601, matcher pattern from above: - // "^(\\d{2})(\\d{2})(\\d{2})(\\d{2})(\\d{2})(\\d{2})(\\d{2})(\\d{2})(\\d{2})(\\d{2})(\\d{2})" - Matcher matcher = PATTERN.matcher(newZoneData); - if (matcher.find()) { - zoneData.setZone(matcher.group(1)); + if (!newZoneData.getDnd().equals(zoneData.getDnd())) { + zoneData.setDnd(newZoneData.getDnd()); + updateChannelState(zoneData.getZone(), CHANNEL_TYPE_DND); + } - if (!matcher.group(2).equals(zoneData.getPage())) { - zoneData.setPage(matcher.group(2)); - updateChannelState(zone, CHANNEL_TYPE_PAGE, zoneData); - } + if (newZoneData.getVolume() != zoneData.getVolume()) { + zoneData.setVolume(newZoneData.getVolume()); + updateChannelState(zoneData.getZone(), CHANNEL_TYPE_VOLUME); + } - if (!matcher.group(3).equals(zoneData.getPower())) { - zoneData.setPower(matcher.group(3)); - updateChannelState(zone, CHANNEL_TYPE_POWER, zoneData); - } + if (newZoneData.getTreble() != zoneData.getTreble()) { + zoneData.setTreble(newZoneData.getTreble()); + updateChannelState(zoneData.getZone(), CHANNEL_TYPE_TREBLE); + } - if (!matcher.group(4).equals(zoneData.getMute())) { - zoneData.setMute(matcher.group(4)); - updateChannelState(zone, CHANNEL_TYPE_MUTE, zoneData); - } + if (newZoneData.getBass() != zoneData.getBass()) { + zoneData.setBass(newZoneData.getBass()); + updateChannelState(zoneData.getZone(), CHANNEL_TYPE_BASS); + } - if (!matcher.group(5).equals(zoneData.getDnd())) { - zoneData.setDnd(matcher.group(5)); - updateChannelState(zone, CHANNEL_TYPE_DND, zoneData); - } + if (newZoneData.getBalance() != zoneData.getBalance()) { + zoneData.setBalance(newZoneData.getBalance()); + updateChannelState(zoneData.getZone(), CHANNEL_TYPE_BALANCE); + } - int volume = Integer.parseInt(matcher.group(6)); - if (volume != zoneData.getVolume()) { - zoneData.setVolume(volume); - updateChannelState(zone, CHANNEL_TYPE_VOLUME, zoneData); - } + if (!newZoneData.getSource().equals(zoneData.getSource())) { + zoneData.setSource(newZoneData.getSource()); + updateChannelState(zoneData.getZone(), CHANNEL_TYPE_SOURCE); + } - 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); + 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 zoneId the zone id used to lookup the channel to be updated + * @param channelType the channel type to be updated + */ + private void updateChannelState(String zoneId, String channelType) { + MonopriceAudioZoneDTO zoneData = zoneDataMap.get(zoneId); + + if (zoneData != null) { + String channel = amp.getZoneName(zoneId) + CHANNEL_DELIMIT + channelType; + + if (!isLinked(channel)) { + return; + } + + logger.debug("updating channel state for zone: {}, channel type: {}", zoneId, channelType); + + State state = UnDefType.UNDEF; + switch (channelType) { + case CHANNEL_TYPE_POWER: + state = OnOffType.from(zoneData.isPowerOn()); + break; + case CHANNEL_TYPE_SOURCE: + state = new DecimalType(zoneData.getSource()); + break; + case CHANNEL_TYPE_VOLUME: + long volumePct = Math.round( + (zoneData.getVolume() - MIN_VOLUME) / (double) (amp.getMaxVol() - MIN_VOLUME) * 100.0); + state = new PercentType(BigDecimal.valueOf(volumePct)); + break; + case CHANNEL_TYPE_MUTE: + state = OnOffType.from(zoneData.isMuted()); + break; + case CHANNEL_TYPE_TREBLE: + state = new DecimalType(BigDecimal.valueOf(zoneData.getTreble() - amp.getToneOffset())); + break; + case CHANNEL_TYPE_BASS: + state = new DecimalType(BigDecimal.valueOf(zoneData.getBass() - amp.getToneOffset())); + break; + case CHANNEL_TYPE_BALANCE: + state = new DecimalType(BigDecimal.valueOf(zoneData.getBalance() - amp.getBalOffset())); + break; + case CHANNEL_TYPE_DND: + state = OnOffType.from(zoneData.isDndOn()); + break; + case CHANNEL_TYPE_PAGE: + state = zoneData.isPageActive() ? OpenClosedType.OPEN : OpenClosedType.CLOSED; + break; + case CHANNEL_TYPE_KEYPAD: + state = zoneData.isKeypadActive() ? OpenClosedType.OPEN : OpenClosedType.CLOSED; + break; + default: + break; + } + updateState(channel, state); + } + } } diff --git a/bundles/org.openhab.binding.monopriceaudio/src/main/resources/OH-INF/addon/addon.xml b/bundles/org.openhab.binding.monopriceaudio/src/main/resources/OH-INF/addon/addon.xml index 7590ce56f..572175333 100644 --- a/bundles/org.openhab.binding.monopriceaudio/src/main/resources/OH-INF/addon/addon.xml +++ b/bundles/org.openhab.binding.monopriceaudio/src/main/resources/OH-INF/addon/addon.xml @@ -5,7 +5,8 @@ binding Monoprice Whole House Audio Binding - Controls Monoprice and Dayton Audio Whole House Amplifiers. + Controls Monoprice, Dayton Audio and Xantech Whole House Amplifiers. + local diff --git a/bundles/org.openhab.binding.monopriceaudio/src/main/resources/OH-INF/i18n/monopriceaudio.properties b/bundles/org.openhab.binding.monopriceaudio/src/main/resources/OH-INF/i18n/monopriceaudio.properties index dd1c89728..5920f7336 100644 --- a/bundles/org.openhab.binding.monopriceaudio/src/main/resources/OH-INF/i18n/monopriceaudio.properties +++ b/bundles/org.openhab.binding.monopriceaudio/src/main/resources/OH-INF/i18n/monopriceaudio.properties @@ -1,7 +1,7 @@ # add-on addon.monopriceaudio.name = Monoprice Whole House Audio Binding -addon.monopriceaudio.description = Controls Monoprice and Dayton Audio Whole House Amplifiers. +addon.monopriceaudio.description = Controls Monoprice, Dayton Audio and Xantech Whole House Amplifiers. # thing types @@ -45,11 +45,85 @@ thing-type.monopriceaudio.amplifier.group.zone17.label = Zone 17 thing-type.monopriceaudio.amplifier.group.zone17.description = The Controls for Zone 17 thing-type.monopriceaudio.amplifier.group.zone18.label = Zone 18 thing-type.monopriceaudio.amplifier.group.zone18.description = The Controls for Zone 18 +thing-type.monopriceaudio.dax88.label = Dayton DAX88 Amplifier +thing-type.monopriceaudio.dax88.description = A Multi-zone Whole House Amplifier System +thing-type.monopriceaudio.dax88.group.all.label = All Zones +thing-type.monopriceaudio.dax88.group.all.description = Control All Zones Simultaneously +thing-type.monopriceaudio.dax88.group.zone1.label = Zone 1 +thing-type.monopriceaudio.dax88.group.zone1.description = The Controls for Zone 1 +thing-type.monopriceaudio.dax88.group.zone2.label = Zone 2 +thing-type.monopriceaudio.dax88.group.zone2.description = The Controls for Zone 2 +thing-type.monopriceaudio.dax88.group.zone3.label = Zone 3 +thing-type.monopriceaudio.dax88.group.zone3.description = The Controls for Zone 3 +thing-type.monopriceaudio.dax88.group.zone4.label = Zone 4 +thing-type.monopriceaudio.dax88.group.zone4.description = The Controls for Zone 4 +thing-type.monopriceaudio.dax88.group.zone5.label = Zone 5 +thing-type.monopriceaudio.dax88.group.zone5.description = The Controls for Zone 5 +thing-type.monopriceaudio.dax88.group.zone6.label = Zone 6 +thing-type.monopriceaudio.dax88.group.zone6.description = The Controls for Zone 6 +thing-type.monopriceaudio.dax88.group.zone7.label = Zone 7 +thing-type.monopriceaudio.dax88.group.zone7.description = The Controls for Zone 7 +thing-type.monopriceaudio.dax88.group.zone8.label = Zone 8 +thing-type.monopriceaudio.dax88.group.zone8.description = The Controls for Zone 8 +thing-type.monopriceaudio.monoprice70.label = Monoprice 31028 70V Amplifier +thing-type.monopriceaudio.monoprice70.description = A Multi-zone Whole House Amplifier System +thing-type.monopriceaudio.monoprice70.group.all.label = All Zones +thing-type.monopriceaudio.monoprice70.group.all.description = Control All Zones Simultaneously +thing-type.monopriceaudio.monoprice70.group.zone1.label = Zone 1 +thing-type.monopriceaudio.monoprice70.group.zone1.description = The Controls for Zone 1 +thing-type.monopriceaudio.monoprice70.group.zone2.label = Zone 2 +thing-type.monopriceaudio.monoprice70.group.zone2.description = The Controls for Zone 2 +thing-type.monopriceaudio.monoprice70.group.zone3.label = Zone 3 +thing-type.monopriceaudio.monoprice70.group.zone3.description = The Controls for Zone 3 +thing-type.monopriceaudio.monoprice70.group.zone4.label = Zone 4 +thing-type.monopriceaudio.monoprice70.group.zone4.description = The Controls for Zone 4 +thing-type.monopriceaudio.monoprice70.group.zone5.label = Zone 5 +thing-type.monopriceaudio.monoprice70.group.zone5.description = The Controls for Zone 5 +thing-type.monopriceaudio.monoprice70.group.zone6.label = Zone 6 +thing-type.monopriceaudio.monoprice70.group.zone6.description = The Controls for Zone 6 +thing-type.monopriceaudio.xantech.label = Xantech 8x8 Amplifier +thing-type.monopriceaudio.xantech.description = A Multi-zone Whole House Amplifier System +thing-type.monopriceaudio.xantech.group.all.label = All Zones +thing-type.monopriceaudio.xantech.group.all.description = Control All Zones Simultaneously +thing-type.monopriceaudio.xantech.group.zone1.label = Zone 1 +thing-type.monopriceaudio.xantech.group.zone1.description = The Controls for Zone 1 +thing-type.monopriceaudio.xantech.group.zone2.label = Zone 2 +thing-type.monopriceaudio.xantech.group.zone2.description = The Controls for Zone 2 +thing-type.monopriceaudio.xantech.group.zone3.label = Zone 3 +thing-type.monopriceaudio.xantech.group.zone3.description = The Controls for Zone 3 +thing-type.monopriceaudio.xantech.group.zone4.label = Zone 4 +thing-type.monopriceaudio.xantech.group.zone4.description = The Controls for Zone 4 +thing-type.monopriceaudio.xantech.group.zone5.label = Zone 5 +thing-type.monopriceaudio.xantech.group.zone5.description = The Controls for Zone 5 +thing-type.monopriceaudio.xantech.group.zone6.label = Zone 6 +thing-type.monopriceaudio.xantech.group.zone6.description = The Controls for Zone 6 +thing-type.monopriceaudio.xantech.group.zone7.label = Zone 7 +thing-type.monopriceaudio.xantech.group.zone7.description = The Controls for Zone 7 +thing-type.monopriceaudio.xantech.group.zone8.label = Zone 8 +thing-type.monopriceaudio.xantech.group.zone8.description = The Controls for Zone 8 +thing-type.monopriceaudio.xantech.group.zone9.label = Zone 9 +thing-type.monopriceaudio.xantech.group.zone9.description = The Controls for Zone 9 +thing-type.monopriceaudio.xantech.group.zone10.label = Zone 10 +thing-type.monopriceaudio.xantech.group.zone10.description = The Controls for Zone 10 +thing-type.monopriceaudio.xantech.group.zone11.label = Zone 11 +thing-type.monopriceaudio.xantech.group.zone11.description = The Controls for Zone 11 +thing-type.monopriceaudio.xantech.group.zone12.label = Zone 12 +thing-type.monopriceaudio.xantech.group.zone12.description = The Controls for Zone 12 +thing-type.monopriceaudio.xantech.group.zone13.label = Zone 13 +thing-type.monopriceaudio.xantech.group.zone13.description = The Controls for Zone 13 +thing-type.monopriceaudio.xantech.group.zone14.label = Zone 14 +thing-type.monopriceaudio.xantech.group.zone14.description = The Controls for Zone 14 +thing-type.monopriceaudio.xantech.group.zone15.label = Zone 15 +thing-type.monopriceaudio.xantech.group.zone15.description = The Controls for Zone 15 +thing-type.monopriceaudio.xantech.group.zone16.label = Zone 16 +thing-type.monopriceaudio.xantech.group.zone16.description = The Controls for Zone 16 # thing types config +thing-type.config.monopriceaudio.amplifier.disableKeypadPolling.label = Disable Keypad Polling +thing-type.config.monopriceaudio.amplifier.disableKeypadPolling.description = If physical keypads are not used, this option will disable polling the amplifier for zone updates thing-type.config.monopriceaudio.amplifier.host.label = Address -thing-type.config.monopriceaudio.amplifier.host.description = Host Name or IP Address of the Amplifier or Serial over IP device +thing-type.config.monopriceaudio.amplifier.host.description = Host Name or IP Address of the Monoprice Amplifier or Serial over IP device thing-type.config.monopriceaudio.amplifier.ignoreZones.label = Ignore Zones thing-type.config.monopriceaudio.amplifier.ignoreZones.description = (Optional) A Comma Seperated List of Zone Numbers That Will Ignore the 'All Zone' (Except All Off) Commands (ie: 1,6,10) thing-type.config.monopriceaudio.amplifier.initialAllVolume.label = Initial All Volume @@ -69,16 +143,110 @@ thing-type.config.monopriceaudio.amplifier.inputLabel6.description = Friendly Na thing-type.config.monopriceaudio.amplifier.numZones.label = Number of Zones thing-type.config.monopriceaudio.amplifier.numZones.description = Number of Zones on the Amplifier to Utilize in the Binding (Up to 18 Zones With 3 Amplifiers Connected Together) thing-type.config.monopriceaudio.amplifier.pollingInterval.label = Polling Interval -thing-type.config.monopriceaudio.amplifier.pollingInterval.description = Configures How Often to Poll the Controller to Check for Zone Updates (5-60; Default 15) +thing-type.config.monopriceaudio.amplifier.pollingInterval.description = Configures How Often to Poll the Amplifier to Check for Zone Updates (5-60; Default 15) thing-type.config.monopriceaudio.amplifier.port.label = Port -thing-type.config.monopriceaudio.amplifier.port.description = Communication Port for Serial over IP connection to the Amplifier (Default 8080 for amps with built-in Serial over IP) +thing-type.config.monopriceaudio.amplifier.port.description = Communication Port for Serial over IP connection to the Monoprice Amplifier (8080 for amps with built-in Serial over IP) thing-type.config.monopriceaudio.amplifier.serialPort.label = Serial Port thing-type.config.monopriceaudio.amplifier.serialPort.description = Serial Port to Use for Connecting to the Monoprice Amplifier +thing-type.config.monopriceaudio.dax88.disableKeypadPolling.label = Disable Keypad Polling +thing-type.config.monopriceaudio.dax88.disableKeypadPolling.description = If physical keypads are not used, this option will disable polling the amplifier for zone updates +thing-type.config.monopriceaudio.dax88.host.label = Address +thing-type.config.monopriceaudio.dax88.host.description = Host Name or IP Address of the Machine Connected to the Dayton Amplifier (Serial over IP) +thing-type.config.monopriceaudio.dax88.ignoreZones.label = Ignore Zones +thing-type.config.monopriceaudio.dax88.ignoreZones.description = (Optional) A Comma Seperated List of Zone Numbers That Will Ignore the 'All Zone' (Except All Off) Commands (ie: 1,6,10) +thing-type.config.monopriceaudio.dax88.initialAllVolume.label = Initial All Volume +thing-type.config.monopriceaudio.dax88.initialAllVolume.description = When 'All' Zones Are Activated, the Volume Will Reset to This Value (1-30; default 10) to Prevent Excessive Blaring of Sound ;) +thing-type.config.monopriceaudio.dax88.inputLabel1.label = Source 1 Input Label +thing-type.config.monopriceaudio.dax88.inputLabel1.description = Friendly Name for the Input Source to Be Displayed in the UI (ie: Chromecast, Radio, CD, etc.) +thing-type.config.monopriceaudio.dax88.inputLabel2.label = Source 2 Input Label +thing-type.config.monopriceaudio.dax88.inputLabel2.description = Friendly Name for the Input Source to Be Displayed in the UI (ie: Chromecast, Radio, CD, etc.) +thing-type.config.monopriceaudio.dax88.inputLabel3.label = Source 3 Input Label +thing-type.config.monopriceaudio.dax88.inputLabel3.description = Friendly Name for the Input Source to Be Displayed in the UI (ie: Chromecast, Radio, CD, etc.) +thing-type.config.monopriceaudio.dax88.inputLabel4.label = Source 4 Input Label +thing-type.config.monopriceaudio.dax88.inputLabel4.description = Friendly Name for the Input Source to Be Displayed in the UI (ie: Chromecast, Radio, CD, etc.) +thing-type.config.monopriceaudio.dax88.inputLabel5.label = Source 5 Input Label +thing-type.config.monopriceaudio.dax88.inputLabel5.description = Friendly Name for the Input Source to Be Displayed in the UI (ie: Chromecast, Radio, CD, etc.) +thing-type.config.monopriceaudio.dax88.inputLabel6.label = Source 6 Input Label +thing-type.config.monopriceaudio.dax88.inputLabel6.description = Friendly Name for the Input Source to Be Displayed in the UI (ie: Chromecast, Radio, CD, etc.) +thing-type.config.monopriceaudio.dax88.inputLabel7.label = Source 7 Input Label +thing-type.config.monopriceaudio.dax88.inputLabel7.description = Friendly Name for the Input Source to Be Displayed in the UI (ie: Chromecast, Radio, CD, etc.) +thing-type.config.monopriceaudio.dax88.inputLabel8.label = Source 8 Input Label +thing-type.config.monopriceaudio.dax88.inputLabel8.description = Friendly Name for the Input Source to Be Displayed in the UI (ie: Chromecast, Radio, CD, etc.) +thing-type.config.monopriceaudio.dax88.numZones.label = Number of Zones +thing-type.config.monopriceaudio.dax88.numZones.description = Number of Zones on the Amplifier to Utilize in the Binding +thing-type.config.monopriceaudio.dax88.pollingInterval.label = Polling Interval +thing-type.config.monopriceaudio.dax88.pollingInterval.description = Configures How Often to Poll the Amplifier to Check for Zone Updates (5-60; Default 15) +thing-type.config.monopriceaudio.dax88.port.label = Port +thing-type.config.monopriceaudio.dax88.port.description = Communication Port for Serial over IP connection to the Dayton Amplifier +thing-type.config.monopriceaudio.dax88.serialPort.label = Serial Port +thing-type.config.monopriceaudio.dax88.serialPort.description = Serial Port to Use for Connecting to the Dayton Amplifier +thing-type.config.monopriceaudio.monoprice70.host.label = Address +thing-type.config.monopriceaudio.monoprice70.host.description = Host Name or IP Address of the Machine Connected to the Monoprice Amplifier (Serial over IP) +thing-type.config.monopriceaudio.monoprice70.ignoreZones.label = Ignore Zones +thing-type.config.monopriceaudio.monoprice70.ignoreZones.description = (Optional) A Comma Seperated List of Zone Numbers That Will Ignore the 'All Zone' (Except All Off) Commands (ie: 1,5,6) +thing-type.config.monopriceaudio.monoprice70.initialAllVolume.label = Initial All Volume +thing-type.config.monopriceaudio.monoprice70.initialAllVolume.description = When 'All' Zones Are Activated, the Volume Will Reset to This Value (1-30; default 10) to Prevent Excessive Blaring of Sound ;) +thing-type.config.monopriceaudio.monoprice70.inputLabel1.label = Source 0 Input Label +thing-type.config.monopriceaudio.monoprice70.inputLabel1.description = Friendly Name for the Input Source to Be Displayed in the UI (ie: Chromecast, Radio, CD, etc.) +thing-type.config.monopriceaudio.monoprice70.inputLabel2.label = Source 1 Input Label +thing-type.config.monopriceaudio.monoprice70.inputLabel2.description = Friendly Name for the Input Source to Be Displayed in the UI (ie: Chromecast, Radio, CD, etc.) +thing-type.config.monopriceaudio.monoprice70.numZones.label = Number of Zones +thing-type.config.monopriceaudio.monoprice70.numZones.description = Number of Zones on the Amplifier to Utilize in the Binding +thing-type.config.monopriceaudio.monoprice70.pollingInterval.label = Polling Interval +thing-type.config.monopriceaudio.monoprice70.pollingInterval.description = Configures How Often to Poll the Amplifier to Check for Zone Updates (5-60; Default 30) +thing-type.config.monopriceaudio.monoprice70.port.label = Port +thing-type.config.monopriceaudio.monoprice70.port.description = Communication Port for Serial over IP connection to the Monoprice Amplifier +thing-type.config.monopriceaudio.monoprice70.serialPort.label = Serial Port +thing-type.config.monopriceaudio.monoprice70.serialPort.description = Serial Port to Use for Connecting to the Monoprice Amplifier +thing-type.config.monopriceaudio.xantech.disableKeypadPolling.label = Disable Keypad Polling +thing-type.config.monopriceaudio.xantech.disableKeypadPolling.description = If physical keypads are not used, this option will disable polling the amplifier for zone updates +thing-type.config.monopriceaudio.xantech.host.label = Address +thing-type.config.monopriceaudio.xantech.host.description = Host Name or IP Address of the Machine Connected to the Xantech Amplifier (Serial over IP) +thing-type.config.monopriceaudio.xantech.ignoreZones.label = Ignore Zones +thing-type.config.monopriceaudio.xantech.ignoreZones.description = (Optional) A Comma Seperated List of Zone Numbers That Will Ignore the 'All Zone' (Except All Off) Commands (ie: 1,6,10) +thing-type.config.monopriceaudio.xantech.initialAllVolume.label = Initial All Volume +thing-type.config.monopriceaudio.xantech.initialAllVolume.description = When 'All' Zones Are Activated, the Volume Will Reset to This Value (1-30; default 10) to Prevent Excessive Blaring of Sound ;) +thing-type.config.monopriceaudio.xantech.inputLabel1.label = Source 1 Input Label +thing-type.config.monopriceaudio.xantech.inputLabel1.description = Friendly Name for the Input Source to Be Displayed in the UI (ie: Chromecast, Radio, CD, etc.) +thing-type.config.monopriceaudio.xantech.inputLabel2.label = Source 2 Input Label +thing-type.config.monopriceaudio.xantech.inputLabel2.description = Friendly Name for the Input Source to Be Displayed in the UI (ie: Chromecast, Radio, CD, etc.) +thing-type.config.monopriceaudio.xantech.inputLabel3.label = Source 3 Input Label +thing-type.config.monopriceaudio.xantech.inputLabel3.description = Friendly Name for the Input Source to Be Displayed in the UI (ie: Chromecast, Radio, CD, etc.) +thing-type.config.monopriceaudio.xantech.inputLabel4.label = Source 4 Input Label +thing-type.config.monopriceaudio.xantech.inputLabel4.description = Friendly Name for the Input Source to Be Displayed in the UI (ie: Chromecast, Radio, CD, etc.) +thing-type.config.monopriceaudio.xantech.inputLabel5.label = Source 5 Input Label +thing-type.config.monopriceaudio.xantech.inputLabel5.description = Friendly Name for the Input Source to Be Displayed in the UI (ie: Chromecast, Radio, CD, etc.) +thing-type.config.monopriceaudio.xantech.inputLabel6.label = Source 6 Input Label +thing-type.config.monopriceaudio.xantech.inputLabel6.description = Friendly Name for the Input Source to Be Displayed in the UI (ie: Chromecast, Radio, CD, etc.) +thing-type.config.monopriceaudio.xantech.inputLabel7.label = Source 7 Input Label +thing-type.config.monopriceaudio.xantech.inputLabel7.description = Friendly Name for the Input Source to Be Displayed in the UI (ie: Chromecast, Radio, CD, etc.) +thing-type.config.monopriceaudio.xantech.inputLabel8.label = Source 8 Input Label +thing-type.config.monopriceaudio.xantech.inputLabel8.description = Friendly Name for the Input Source to Be Displayed in the UI (ie: Chromecast, Radio, CD, etc.) +thing-type.config.monopriceaudio.xantech.numZones.label = Number of Zones +thing-type.config.monopriceaudio.xantech.numZones.description = Number of Zones on the Amplifier to Utilize in the Binding (Up to 16 Zones With 2 Amplifiers Connected Together) +thing-type.config.monopriceaudio.xantech.pollingInterval.label = Polling Interval +thing-type.config.monopriceaudio.xantech.pollingInterval.description = Configures How Often to Poll the Amplifier to Check for Zone Updates (5-60; Default 30) +thing-type.config.monopriceaudio.xantech.port.label = Port +thing-type.config.monopriceaudio.xantech.port.description = Communication Port for Serial over IP connection to the Xantech Amplifier +thing-type.config.monopriceaudio.xantech.serialPort.label = Serial Port +thing-type.config.monopriceaudio.xantech.serialPort.description = Serial Port to Use for Connecting to the Xantech Amplifier # channel group types channel-group-type.monopriceaudio.all.label = All Zones channel-group-type.monopriceaudio.all.description = Control All Zones Simultaneously +channel-group-type.monopriceaudio.dax88-all.label = All Zones +channel-group-type.monopriceaudio.dax88-all.description = Control All Zones Simultaneously +channel-group-type.monopriceaudio.dax88-zone.label = Zone Controls +channel-group-type.monopriceaudio.dax88-zone.description = The Controls for the Zone +channel-group-type.monopriceaudio.monoprice70-all.label = All Zones +channel-group-type.monopriceaudio.monoprice70-all.description = Control All Zones Simultaneously +channel-group-type.monopriceaudio.monoprice70-zone.label = Zone Controls +channel-group-type.monopriceaudio.monoprice70-zone.description = The Controls for the Zone +channel-group-type.monopriceaudio.xantech-all.label = All Zones +channel-group-type.monopriceaudio.xantech-all.description = Control All Zones Simultaneously +channel-group-type.monopriceaudio.xantech-zone.label = Zone Controls +channel-group-type.monopriceaudio.xantech-zone.description = The Controls for the Zone channel-group-type.monopriceaudio.zone.label = Zone Controls channel-group-type.monopriceaudio.zone.description = The Controls for the Zone @@ -90,12 +258,42 @@ channel-type.monopriceaudio.balance.label = Balance Adjustment channel-type.monopriceaudio.balance.description = Adjust the Balance channel-type.monopriceaudio.bass.label = Bass Adjustment channel-type.monopriceaudio.bass.description = Adjust the Bass +channel-type.monopriceaudio.dax88-allpower.label = All On +channel-type.monopriceaudio.dax88-allpower.description = Turn All Zones On or Off +channel-type.monopriceaudio.dax88-balance.label = Balance Adjustment +channel-type.monopriceaudio.dax88-balance.description = Adjust the Balance +channel-type.monopriceaudio.dax88-bass.label = Bass Adjustment +channel-type.monopriceaudio.dax88-bass.description = Adjust the Bass +channel-type.monopriceaudio.dax88-dnd.label = Do Not Disturb +channel-type.monopriceaudio.dax88-dnd.description = Controls if the Zone Should Ignore an Incoming Audio Page +channel-type.monopriceaudio.dax88-keypad.label = Keypad Connected +channel-type.monopriceaudio.dax88-keypad.description = Indicates if a Physical Keypad is Attached to This Zone +channel-type.monopriceaudio.dax88-keypad.state.option.CLOSED = Disconnected +channel-type.monopriceaudio.dax88-keypad.state.option.OPEN = Connected +channel-type.monopriceaudio.dax88-page.label = Page Active +channel-type.monopriceaudio.dax88-page.description = Indicates if the Page Mode is Active for This Zone +channel-type.monopriceaudio.dax88-page.state.option.CLOSED = Inactive +channel-type.monopriceaudio.dax88-page.state.option.OPEN = Active +channel-type.monopriceaudio.dax88-source.label = Source Input +channel-type.monopriceaudio.dax88-source.description = Select the Source Input +channel-type.monopriceaudio.dax88-treble.label = Treble Adjustment +channel-type.monopriceaudio.dax88-treble.description = Adjust the Treble channel-type.monopriceaudio.dnd.label = Do Not Disturb channel-type.monopriceaudio.dnd.description = Controls if the Zone Should Ignore an Incoming Audio Page channel-type.monopriceaudio.keypad.label = Keypad Connected channel-type.monopriceaudio.keypad.description = Indicates if a Physical Keypad is Attached to This Zone channel-type.monopriceaudio.keypad.state.option.CLOSED = Disconnected channel-type.monopriceaudio.keypad.state.option.OPEN = Connected +channel-type.monopriceaudio.monoprice70-allpower.label = All On +channel-type.monopriceaudio.monoprice70-allpower.description = Turn All Zones On or Off +channel-type.monopriceaudio.monoprice70-balance.label = Balance Adjustment +channel-type.monopriceaudio.monoprice70-balance.description = Adjust the Balance +channel-type.monopriceaudio.monoprice70-bass.label = Bass Adjustment +channel-type.monopriceaudio.monoprice70-bass.description = Adjust the Bass +channel-type.monopriceaudio.monoprice70-source.label = Source Input +channel-type.monopriceaudio.monoprice70-source.description = Select the Source Input +channel-type.monopriceaudio.monoprice70-treble.label = Treble Adjustment +channel-type.monopriceaudio.monoprice70-treble.description = Adjust the Treble channel-type.monopriceaudio.page.label = Page Active channel-type.monopriceaudio.page.description = Indicates if the Page Mode is Active for This Zone channel-type.monopriceaudio.page.state.option.CLOSED = Inactive @@ -104,3 +302,30 @@ channel-type.monopriceaudio.source.label = Source Input channel-type.monopriceaudio.source.description = Select the Source Input channel-type.monopriceaudio.treble.label = Treble Adjustment channel-type.monopriceaudio.treble.description = Adjust the Treble +channel-type.monopriceaudio.xantech-allpower.label = All On +channel-type.monopriceaudio.xantech-allpower.description = Turn All Zones On or Off +channel-type.monopriceaudio.xantech-balance.label = Balance Adjustment +channel-type.monopriceaudio.xantech-balance.description = Adjust the Balance +channel-type.monopriceaudio.xantech-bass.label = Bass Adjustment +channel-type.monopriceaudio.xantech-bass.description = Adjust the Bass +channel-type.monopriceaudio.xantech-keypad.label = Keypad Connected +channel-type.monopriceaudio.xantech-keypad.description = Indicates if a Physical Keypad is Attached to This Zone +channel-type.monopriceaudio.xantech-keypad.state.option.CLOSED = Disconnected +channel-type.monopriceaudio.xantech-keypad.state.option.OPEN = Connected +channel-type.monopriceaudio.xantech-page.label = Page Active +channel-type.monopriceaudio.xantech-page.description = Indicates if the Page Mode is Active for This Zone +channel-type.monopriceaudio.xantech-page.state.option.CLOSED = Inactive +channel-type.monopriceaudio.xantech-page.state.option.OPEN = Active +channel-type.monopriceaudio.xantech-source.label = Source Input +channel-type.monopriceaudio.xantech-source.description = Select the Source Input +channel-type.monopriceaudio.xantech-treble.label = Treble Adjustment +channel-type.monopriceaudio.xantech-treble.description = Adjust the Treble + +# message strings + +offline.configuration-error-rfc2217 = Use Host and Port configuration settings for a serial over IP connection +offline.configuration-error-conflict = Serial port cannot be used at the same time that Host & Port is used +offline.configuration-error-missing = Either Serial port or Host & Port must be specifed +offline.communication-error-failed = Sending command failed +offline.communication-error-polling = Amplifier not responding to status requests +offline.communication-error-reconnection = Reconnection failed diff --git a/bundles/org.openhab.binding.monopriceaudio/src/main/resources/OH-INF/thing/dax88.xml b/bundles/org.openhab.binding.monopriceaudio/src/main/resources/OH-INF/thing/dax88.xml new file mode 100644 index 000000000..8683f7d35 --- /dev/null +++ b/bundles/org.openhab.binding.monopriceaudio/src/main/resources/OH-INF/thing/dax88.xml @@ -0,0 +1,230 @@ + + + + + + + + A Multi-zone Whole House Amplifier System + + + + + + Control All Zones Simultaneously + + + + The Controls for Zone 1 + + + + The Controls for Zone 2 + + + + The Controls for Zone 3 + + + + The Controls for Zone 4 + + + + The Controls for Zone 5 + + + + The Controls for Zone 6 + + + + The Controls for Zone 7 + + + + The Controls for Zone 8 + + + + + + serial-port + false + + Serial Port to Use for Connecting to the Dayton Amplifier + + + network-address + + Host Name or IP Address of the Machine Connected to the Dayton Amplifier (Serial over IP) + + + + Communication Port for Serial over IP connection to the Dayton Amplifier + + + + Number of Zones on the Amplifier to Utilize in the Binding + 8 + + + + Configures How Often to Poll the Amplifier to Check for Zone Updates (5-60; Default 15) + 15 + + + + (Optional) A Comma Seperated List of Zone Numbers That Will Ignore the 'All Zone' (Except All Off) + Commands (ie: 1,6,10) + + + + When 'All' Zones Are Activated, the Volume Will Reset to This Value (1-30; default 10) to Prevent + Excessive Blaring of Sound ;) + 10 + + + + Friendly Name for the Input Source to Be Displayed in the UI (ie: Chromecast, Radio, CD, etc.) + Source 1 + + + + Friendly Name for the Input Source to Be Displayed in the UI (ie: Chromecast, Radio, CD, etc.) + Source 2 + + + + Friendly Name for the Input Source to Be Displayed in the UI (ie: Chromecast, Radio, CD, etc.) + Source 3 + + + + Friendly Name for the Input Source to Be Displayed in the UI (ie: Chromecast, Radio, CD, etc.) + Source 4 + + + + Friendly Name for the Input Source to Be Displayed in the UI (ie: Chromecast, Radio, CD, etc.) + Source 5 + + + + Friendly Name for the Input Source to Be Displayed in the UI (ie: Chromecast, Radio, CD, etc.) + Source 6 + + + + Friendly Name for the Input Source to Be Displayed in the UI (ie: Chromecast, Radio, CD, etc.) + Source 7 + + + + Friendly Name for the Input Source to Be Displayed in the UI (ie: Chromecast, Radio, CD, etc.) + Wi-Fi streaming + + + + If physical keypads are not used, this option will disable polling the amplifier for zone updates + false + + + + + + + Control All Zones Simultaneously + + + + + + + + + + + The Controls for the Zone + + + + + + + + + + + + + + + + Switch + + Turn All Zones On or Off + + + + Number + + Select the Source Input + + + + + Number + + Adjust the Treble + + + + + Number + + Adjust the Bass + + + + + Number + + Adjust the Balance + + + + + Switch + + Controls if the Zone Should Ignore an Incoming Audio Page + + + + Contact + + Indicates if the Page Mode is Active for This Zone + + + + + + + + + + Contact + + Indicates if a Physical Keypad is Attached to This Zone + + + + + + + + + diff --git a/bundles/org.openhab.binding.monopriceaudio/src/main/resources/OH-INF/thing/channels.xml b/bundles/org.openhab.binding.monopriceaudio/src/main/resources/OH-INF/thing/monoprice.xml similarity index 94% rename from bundles/org.openhab.binding.monopriceaudio/src/main/resources/OH-INF/thing/channels.xml rename to bundles/org.openhab.binding.monopriceaudio/src/main/resources/OH-INF/thing/monoprice.xml index 105a14f49..6f94f01a5 100644 --- a/bundles/org.openhab.binding.monopriceaudio/src/main/resources/OH-INF/thing/channels.xml +++ b/bundles/org.openhab.binding.monopriceaudio/src/main/resources/OH-INF/thing/monoprice.xml @@ -100,13 +100,12 @@ network-address - Host Name or IP Address of the Amplifier or Serial over IP device + Host Name or IP Address of the Monoprice Amplifier or Serial over IP device - Communication Port for Serial over IP connection to the Amplifier (Default 8080 for amps with built-in - Serial over IP) - 8080 + Communication Port for Serial over IP connection to the Monoprice Amplifier (8080 for amps with + built-in Serial over IP) @@ -116,7 +115,7 @@ - Configures How Often to Poll the Controller to Check for Zone Updates (5-60; Default 15) + Configures How Often to Poll the Amplifier to Check for Zone Updates (5-60; Default 15) 15 @@ -160,6 +159,11 @@ Friendly Name for the Input Source to Be Displayed in the UI (ie: Chromecast, Radio, CD, etc.) Source 6 + + + If physical keypads are not used, this option will disable polling the amplifier for zone updates + false + diff --git a/bundles/org.openhab.binding.monopriceaudio/src/main/resources/OH-INF/thing/monoprice70.xml b/bundles/org.openhab.binding.monopriceaudio/src/main/resources/OH-INF/thing/monoprice70.xml new file mode 100644 index 000000000..6c35916a4 --- /dev/null +++ b/bundles/org.openhab.binding.monopriceaudio/src/main/resources/OH-INF/thing/monoprice70.xml @@ -0,0 +1,154 @@ + + + + + + + + A Multi-zone Whole House Amplifier System + + + + + + Control All Zones Simultaneously + + + + The Controls for Zone 1 + + + + The Controls for Zone 2 + + + + The Controls for Zone 3 + + + + The Controls for Zone 4 + + + + The Controls for Zone 5 + + + + The Controls for Zone 6 + + + + + + serial-port + false + + Serial Port to Use for Connecting to the Monoprice Amplifier + + + network-address + + Host Name or IP Address of the Machine Connected to the Monoprice Amplifier (Serial over IP) + + + + Communication Port for Serial over IP connection to the Monoprice Amplifier + + + + Number of Zones on the Amplifier to Utilize in the Binding + 6 + + + + Configures How Often to Poll the Amplifier to Check for Zone Updates (5-60; Default 30) + 30 + + + + (Optional) A Comma Seperated List of Zone Numbers That Will Ignore the 'All Zone' (Except All Off) + Commands (ie: 1,5,6) + + + + When 'All' Zones Are Activated, the Volume Will Reset to This Value (1-30; default 10) to Prevent + Excessive Blaring of Sound ;) + 10 + + + + Friendly Name for the Input Source to Be Displayed in the UI (ie: Chromecast, Radio, CD, etc.) + Source 0 - Bus + + + + Friendly Name for the Input Source to Be Displayed in the UI (ie: Chromecast, Radio, CD, etc.) + Source 1 - Line + + + + + + + Control All Zones Simultaneously + + + + + + + + + + + The Controls for the Zone + + + + + + + + + + + + + Switch + + Turn All Zones On or Off + + + + Number + + Select the Source Input + + + + + Number + + Adjust the Treble + + + + + Number + + Adjust the Bass + + + + + Number + + Adjust the Balance + + + + diff --git a/bundles/org.openhab.binding.monopriceaudio/src/main/resources/OH-INF/thing/xantech.xml b/bundles/org.openhab.binding.monopriceaudio/src/main/resources/OH-INF/thing/xantech.xml new file mode 100644 index 000000000..e0e3725a0 --- /dev/null +++ b/bundles/org.openhab.binding.monopriceaudio/src/main/resources/OH-INF/thing/xantech.xml @@ -0,0 +1,256 @@ + + + + + + + + A Multi-zone Whole House Amplifier System + + + + + + Control All Zones Simultaneously + + + + The Controls for Zone 1 + + + + The Controls for Zone 2 + + + + The Controls for Zone 3 + + + + The Controls for Zone 4 + + + + The Controls for Zone 5 + + + + The Controls for Zone 6 + + + + The Controls for Zone 7 + + + + The Controls for Zone 8 + + + + The Controls for Zone 9 + + + + The Controls for Zone 10 + + + + The Controls for Zone 11 + + + + The Controls for Zone 12 + + + + The Controls for Zone 13 + + + + The Controls for Zone 14 + + + + The Controls for Zone 15 + + + + The Controls for Zone 16 + + + + + + serial-port + false + + Serial Port to Use for Connecting to the Xantech Amplifier + + + network-address + + Host Name or IP Address of the Machine Connected to the Xantech Amplifier (Serial over IP) + + + + Communication Port for Serial over IP connection to the Xantech Amplifier + + + + Number of Zones on the Amplifier to Utilize in the Binding (Up to 16 Zones With 2 Amplifiers Connected + Together) + 8 + + + + Configures How Often to Poll the Amplifier to Check for Zone Updates (5-60; Default 30) + 30 + + + + (Optional) A Comma Seperated List of Zone Numbers That Will Ignore the 'All Zone' (Except All Off) + Commands (ie: 1,6,10) + + + + When 'All' Zones Are Activated, the Volume Will Reset to This Value (1-30; default 10) to Prevent + Excessive Blaring of Sound ;) + 10 + + + + Friendly Name for the Input Source to Be Displayed in the UI (ie: Chromecast, Radio, CD, etc.) + Source 1 + + + + Friendly Name for the Input Source to Be Displayed in the UI (ie: Chromecast, Radio, CD, etc.) + Source 2 + + + + Friendly Name for the Input Source to Be Displayed in the UI (ie: Chromecast, Radio, CD, etc.) + Source 3 + + + + Friendly Name for the Input Source to Be Displayed in the UI (ie: Chromecast, Radio, CD, etc.) + Source 4 + + + + Friendly Name for the Input Source to Be Displayed in the UI (ie: Chromecast, Radio, CD, etc.) + Source 5 + + + + Friendly Name for the Input Source to Be Displayed in the UI (ie: Chromecast, Radio, CD, etc.) + Source 6 + + + + Friendly Name for the Input Source to Be Displayed in the UI (ie: Chromecast, Radio, CD, etc.) + Source 7 + + + + Friendly Name for the Input Source to Be Displayed in the UI (ie: Chromecast, Radio, CD, etc.) + Source 8 + + + + If physical keypads are not used, this option will disable polling the amplifier for zone updates + false + + + + + + + Control All Zones Simultaneously + + + + + + + + + + + The Controls for the Zone + + + + + + + + + + + + + + + Switch + + Turn All Zones On or Off + + + + Number + + Select the Source Input + + + + + Number + + Adjust the Treble + + + + + Number + + Adjust the Bass + + + + + Number + + Adjust the Balance + + + + + Contact + + Indicates if the Page Mode is Active for This Zone + + + + + + + + + + Contact + + Indicates if a Physical Keypad is Attached to This Zone + + + + + + + + +