diff --git a/bundles/org.openhab.binding.nuvo/README.md b/bundles/org.openhab.binding.nuvo/README.md index 618b7b7ea..e4a407cd8 100644 --- a/bundles/org.openhab.binding.nuvo/README.md +++ b/bundles/org.openhab.binding.nuvo/README.md @@ -85,35 +85,36 @@ connection: &conNuvo The following channels are available: -| Channel ID | Item Type | Description | -|--------------------------------------|-------------|-----------------------------------------------------------------------------------------------------------------------------| -| system#alloff | Switch | Turn all zones off simultaneously | -| system#allmute | Switch | Mute or unmute all zones simultaneously | -| system#page | Switch | Turn on or off the Page All Zones feature (while on the amplifier switches to source 6) | -| system#sendcmd | String | Send a command to the amplifier | -| zoneN#power (where N= 1-20) | Switch | Turn the power for a zone on or off | -| zoneN#source (where N= 1-20) | Number | Select the source input for a zone (1-6) | -| zoneN#volume (where N= 1-20) | Dimmer | Control the volume for a zone (0-100%) [translates to 0-79] | -| zoneN#mute (where N= 1-20) | Switch | Mute or unmute a zone | -| zoneN#favorite (where N= 1-20) | Number | Select a preset Favorite for a zone (1-12) | -| zoneN#control (where N= 1-20) | Player | Simulate pressing the transport control buttons on the keypad e.g. play/pause/next/previous | -| zoneN#treble (where N= 1-20) | Number | Adjust the treble control for a zone (-18 to 18 [in increments of 2]) -18=none, 0=flat, 18=full | -| zoneN#bass (where N= 1-20) | Number | Adjust the bass control for a zone (-18 to 18 [in increments of 2]) -18=none, 0=flat, 18=full | -| zoneN#balance (where N= 1-20) | Number | Adjust the balance control for a zone (-18 to 18 [in increments of 2]) -18=left, 0=center, 18=right | -| zoneN#loudness (where N= 1-20) | Switch | Turn on or off the loudness compensation setting for the zone | -| zoneN#dnd (where N= 1-20) | Switch | Turn on or off the Do Not Disturb for the zone (for when the amplifier's Page All Zones feature is activated) | -| zoneN#lock (where N= 1-20) | Contact | Indicates if this zone is currently locked | -| zoneN#party (where N= 1-20) | Switch | Turn on or off the party mode feature with this zone as the host | -| sourceN#display_line1 (where N= 1-6) | String | 1st line of text being displayed on the keypad. Can be updated for a non NuvoNet source | -| sourceN#display_line2 (where N= 1-6) | String | 2nd line of text being displayed on the keypad. Can be updated for a non NuvoNet source | -| sourceN#display_line3 (where N= 1-6) | String | 3rd line of text being displayed on the keypad. Can be updated for a non NuvoNet source | -| sourceN#display_line4 (where N= 1-6) | String | 4th line of text being displayed on the keypad. Can be updated for a non NuvoNet source | -| sourceN#play_mode (where N= 1-6) | String | The current playback mode of the source, ie: Playing, Paused, etc. (ReadOnly) See rules example for updating | -| sourceN#track_length (where N= 1-6) | Number:Time | The total running time of the current playing track (ReadOnly) See rules example for updating | -| sourceN#track_position (where N= 1-6)| Number:Time | The running time elapsed of the current playing track (ReadOnly) See rules example for updating | -| sourceN#button_press (where N= 1-6) | String | Indicates the last button pressed on the keypad for a non NuvoNet source or openHAB NuvoNet source (ReadOnly) | -| sourceN#art_url (where N= 1-6) | String | MPS4 Only! The URL of the Album Art JPG for this source that is displayed on a CTP-36. See *very advanced* rules (SendOnly) | -| sourceN#album_art (where N= 1-6) | Image | The Album Art loaded from the art_url channel for display in a UI widget (ReadOnly) | +| Channel ID | Item Type | Description | +|--------------------------------------|-------------|--------------------------------------------------------------------------------------------------------------------------------| +| system#alloff | Switch | Turn all zones off simultaneously | +| system#allmute | Switch | Mute or unmute all zones simultaneously | +| system#page | Switch | Turn on or off the Page All Zones feature (while on the amplifier switches to source 6) | +| system#sendcmd | String | Send a command to the amplifier | +| system#buttonpress | String | Indicates the zone number followed by a comma and the last button pressed or NuvoNet menu item selected on a keypad (ReadOnly) | +| zoneN#power (where N= 1-20) | Switch | Turn the power for a zone on or off | +| zoneN#source (where N= 1-20) | Number | Select the source input for a zone (1-6) | +| zoneN#volume (where N= 1-20) | Dimmer | Control the volume for a zone (0-100%) [translates to 0-79] | +| zoneN#mute (where N= 1-20) | Switch | Mute or unmute a zone | +| zoneN#favorite (where N= 1-20) | Number | Select a preset Favorite for a zone (1-12) | +| zoneN#control (where N= 1-20) | Player | Simulate pressing the transport control buttons on the keypad e.g. play/pause/next/previous | +| zoneN#treble (where N= 1-20) | Number | Adjust the treble control for a zone (-18 to 18 [in increments of 2]) -18=none, 0=flat, 18=full | +| zoneN#bass (where N= 1-20) | Number | Adjust the bass control for a zone (-18 to 18 [in increments of 2]) -18=none, 0=flat, 18=full | +| zoneN#balance (where N= 1-20) | Number | Adjust the balance control for a zone (-18 to 18 [in increments of 2]) -18=left, 0=center, 18=right | +| zoneN#loudness (where N= 1-20) | Switch | Turn on or off the loudness compensation setting for the zone | +| zoneN#dnd (where N= 1-20) | Switch | Turn on or off the Do Not Disturb for the zone (for when the amplifier's Page All Zones feature is activated) | +| zoneN#lock (where N= 1-20) | Contact | Indicates if this zone is currently locked | +| zoneN#party (where N= 1-20) | Switch | Turn on or off the party mode feature with this zone as the host | +| sourceN#display_line1 (where N= 1-6) | String | 1st line of text being displayed on the keypad. Can be updated for a non NuvoNet source | +| sourceN#display_line2 (where N= 1-6) | String | 2nd line of text being displayed on the keypad. Can be updated for a non NuvoNet source | +| sourceN#display_line3 (where N= 1-6) | String | 3rd line of text being displayed on the keypad. Can be updated for a non NuvoNet source | +| sourceN#display_line4 (where N= 1-6) | String | 4th line of text being displayed on the keypad. Can be updated for a non NuvoNet source | +| sourceN#play_mode (where N= 1-6) | String | The current playback mode of the source, ie: Playing, Paused, etc. (ReadOnly) See rules example for updating | +| sourceN#track_length (where N= 1-6) | Number:Time | The total running time of the current playing track (ReadOnly) See rules example for updating | +| sourceN#track_position (where N= 1-6)| Number:Time | The running time elapsed of the current playing track (ReadOnly) See rules example for updating | +| sourceN#button_press (where N= 1-6) | String | Indicates the last button pressed on the keypad for a non NuvoNet source or openHAB NuvoNet source (ReadOnly) | +| sourceN#art_url (where N= 1-6) | String | MPS4 Only! The URL of the Album Art JPG for this source that is displayed on a CTP-36. See *very advanced* rules (SendOnly) | +| sourceN#album_art (where N= 1-6) | Image | The Album Art loaded from the art_url channel for display in a UI widget (ReadOnly) | ## Full Example @@ -139,6 +140,7 @@ Switch nuvo_system_alloff "All Zones Off" { channel="nuvo:amplifier:myamp:system Switch nuvo_system_allmute "All Zones Mute" { channel="nuvo:amplifier:myamp:system#allmute" } Switch nuvo_system_page "Page All Zones" { channel="nuvo:amplifier:myamp:system#page" } String nuvo_system_sendcmd "Send Command" { channel="nuvo:amplifier:myamp:system#sendcmd" } +String nuvo_system_buttonpress "Zone Button: [%s]" { channel="nuvo:amplifier:myamp:system#buttonpress" } // zones Switch nuvo_z1_power "Power" { channel="nuvo:amplifier:myamp:zone1#power" } @@ -473,12 +475,57 @@ A complete XML string for the desired menu is then stored in the `menuXmlSrcN` c menu3 x menu3 y + ``` When a menu item is selected, the text of the topmenu item and sub menu item (if applicable) will be sent to the button channel in a pipe delimited format. For example, when item `menu1 b` is selected, the text `Top menu 1|menu1 b` will be sent to the button channel. When the item `Top menu 2` is selected the text sent to the button channel will simply be `Top menu 2` since this menu item does not have any sub menu items. +### Rule to trigger an action based on which keypad zone where a button was pressed or menu item selected + +By using the `system#buttonpress` channel it is possible to trigger an action based on which keypad zone was used to send the action. +This channel appends the zone number and a comma before the button action or menu item selection. + +For example if the Play/Pause button is pressed on Zone 7, the channel will display: `7,PLAYPAUSE` +Also if a menu item from a custom menu was selected, ie: `Top menu 1` on Zone 5, the channel will display: `5,Top menu 1` + +The functionality can be used to create very powerful rules as demontrated below. The following rule triggered from a menu item turns off all zones except for the zone that triggered the rule. + +nuvo-turn-off-all-but-caller.rules: + +``` +rule "Turn off all zones except caller zone" +when + Item nuvo_system_buttonpress received update +then + var callerZone = newState.toString().split(",").get(0) + var button = newState.toString().split(",").get(1) + + if (button == "Turn off other zones") { + if (callerZone != "1") { + nuvo_z1_power.sendCommand(OFF) + } + if (callerZone != "2") { + nuvo_z2_power.sendCommand(OFF) + } + if (callerZone != "3") { + nuvo_z3_power.sendCommand(OFF) + } + if (callerZone != "4") { + nuvo_z4_power.sendCommand(OFF) + } + if (callerZone != "5") { + nuvo_z5_power.sendCommand(OFF) + } + if (callerZone != "6") { + nuvo_z6_power.sendCommand(OFF) + } + } +end + +``` + ### MPS4 openHAB NuvoNet source custom integration rules *(very advanced)* The following are a set of example rules necessary to integrate metadata and control of another openHAB connected source (ie: Chromecast) into an openHAB NuvoNet source. diff --git a/bundles/org.openhab.binding.nuvo/src/main/java/org/openhab/binding/nuvo/internal/NuvoBindingConstants.java b/bundles/org.openhab.binding.nuvo/src/main/java/org/openhab/binding/nuvo/internal/NuvoBindingConstants.java index c3cf2497c..d65fceda0 100644 --- a/bundles/org.openhab.binding.nuvo/src/main/java/org/openhab/binding/nuvo/internal/NuvoBindingConstants.java +++ b/bundles/org.openhab.binding.nuvo/src/main/java/org/openhab/binding/nuvo/internal/NuvoBindingConstants.java @@ -35,6 +35,7 @@ public class NuvoBindingConstants { public static final String CHANNEL_TYPE_ALLMUTE = "allmute"; public static final String CHANNEL_TYPE_PAGE = "page"; public static final String CHANNEL_TYPE_SENDCMD = "sendcmd"; + public static final String CHANNEL_TYPE_BUTTONPRESS = "buttonpress"; // zone public static final String CHANNEL_TYPE_POWER = "power"; @@ -71,14 +72,14 @@ public class NuvoBindingConstants { public static final String TYPE_PAGE = "page"; public static final String TYPE_SOURCE_UPDATE = "source_update"; public static final String TYPE_ZONE_UPDATE = "zone_update"; - public static final String TYPE_ZONE_BUTTON = "zone_button"; - public static final String TYPE_ZONE_BUTTON2 = "zone_button2"; - public static final String TYPE_ZONE_MENUREQ = "zone_menureq"; - public static final String TYPE_MENU_ITEM_SELECTED = "top_menu_button"; public static final String TYPE_ZONE_CONFIG = "zone_config"; - public static final String TYPE_ALBUM_ART_REQ = "album_art_req"; - public static final String TYPE_ALBUM_ART_FRAG_REQ = "album_art_frag_req"; - public static final String TYPE_FAVORITE_REQ = "favorite_req"; + public static final String TYPE_ZONE_SOURCE_BUTTON = "zone_source_button"; + public static final String TYPE_NN_BUTTON = "nn_button"; + public static final String TYPE_NN_MENUREQ = "nn_menureq"; + public static final String TYPE_NN_MENU_ITEM_SELECTED = "nn_menu_item_selected"; + public static final String TYPE_NN_ALBUM_ART_REQ = "nn_album_art_req"; + public static final String TYPE_NN_ALBUM_ART_FRAG_REQ = "nn_album_art_frag_req"; + public static final String TYPE_NN_FAVORITE_REQ = "nn_favorite_req"; // misc public static final String ON = "ON"; diff --git a/bundles/org.openhab.binding.nuvo/src/main/java/org/openhab/binding/nuvo/internal/communication/NuvoConnector.java b/bundles/org.openhab.binding.nuvo/src/main/java/org/openhab/binding/nuvo/internal/communication/NuvoConnector.java index bb084ffd7..c3321eb3c 100644 --- a/bundles/org.openhab.binding.nuvo/src/main/java/org/openhab/binding/nuvo/internal/communication/NuvoConnector.java +++ b/bundles/org.openhab.binding.nuvo/src/main/java/org/openhab/binding/nuvo/internal/communication/NuvoConnector.java @@ -54,21 +54,21 @@ public abstract class NuvoConnector { private static final Pattern SRC_PATTERN = Pattern.compile("^#S(\\d{1})(.*)$"); private static final Pattern ZONE_PATTERN = Pattern.compile("^#Z(\\d{1,2}),(.*)$"); - private static final Pattern ZONE_BUTTON_PATTERN = Pattern.compile("^#Z(\\d{1,2})S(\\d{1})(.*)$"); - private static final Pattern ZONE_MENUREQ_PATTERN = Pattern.compile("^#Z(\\d{1,2})S(\\d{1})MENUREQ(.*)$"); - private static final Pattern ZONE_BUTTON2_PATTERN = Pattern.compile("^#Z(\\d{1,2})S(\\d{1})BUTTON(.*)$"); - private static final Pattern ZONE_BUTTONTWO_PATTERN = Pattern.compile("^#Z(\\d{1,2})S(\\d{1})BUTTONTWO(.*)$"); + private static final Pattern ZONE_SOURCE_PATTERN = Pattern.compile("^#Z(\\d{1,2})S(\\d{1})(.*)$"); + private static final Pattern NN_MENUREQ_PATTERN = Pattern.compile("^#Z(\\d{1,2})S(\\d{1})MENUREQ(.*)$"); + private static final Pattern NN_BUTTON_PATTERN = Pattern.compile("^#Z(\\d{1,2})S(\\d{1})BUTTON(.*)$"); + private static final Pattern NN_BUTTONTWO_PATTERN = Pattern.compile("^#Z(\\d{1,2})S(\\d{1})BUTTONTWO(.*)$"); private static final Pattern ZONE_CFG_PATTERN = Pattern.compile("^#ZCFG(\\d{1,2}),(.*)$"); // S2ALBUMARTREQ0x620FD879,80,80,2,0x00C0C0C0,0,0,0,0,1 - private static final Pattern ALBUM_ART_REQ = Pattern.compile("^#S(\\d{1})ALBUMARTREQ(.*)$"); + private static final Pattern NN_ALBUM_ART_REQ = Pattern.compile("^#S(\\d{1})ALBUMARTREQ(.*)$"); // S2ALBUMARTFRAGREQ0x620FD879,0,750 - private static final Pattern ALBUM_ART_FRAG_REQ = Pattern.compile("^#S(\\d{1})ALBUMARTFRAGREQ(.*)$"); + private static final Pattern NN_ALBUM_ART_FRAG_REQ = Pattern.compile("^#S(\\d{1})ALBUMARTFRAGREQ(.*)$"); // S6FAVORITE0x000003E8 - private static final Pattern FAVORITE_PATTERN = Pattern.compile("^#S(\\d{1})FAVORITE0x(.*)$"); + private static final Pattern NN_FAVORITE_PATTERN = Pattern.compile("^#S(\\d{1})FAVORITE0x(.*)$"); private final Logger logger = LoggerFactory.getLogger(NuvoConnector.class); @@ -88,6 +88,7 @@ public abstract class NuvoConnector { private List listeners = new ArrayList<>(); private boolean isEssentia = true; + private boolean isAnyOhNuvoNet = false; /** * Get whether the connection is established or not @@ -116,6 +117,15 @@ public abstract class NuvoConnector { this.isEssentia = isEssentia; } + /** + * Tell the connector to listen for NuvoNet source messages + * + * @param true if any sources are configured as openHAB NuvoNet sources + */ + public void setAnyOhNuvoNet(boolean isAnyOhNuvoNet) { + this.isAnyOhNuvoNet = isAnyOhNuvoNet; + } + /** * Set the thread that handles the feedback messages * @@ -307,7 +317,7 @@ public abstract class NuvoConnector { } /** - * Analyze an incoming message and dispatch corresponding (type, key, value) to the event listeners + * Analyze an incoming message and dispatch corresponding (type, zone, src, value) to the event listeners * * @param incomingMessage the received message */ @@ -327,119 +337,117 @@ public abstract class NuvoConnector { } catch (NuvoException e) { logger.debug("Error sending response to PING command"); } - dispatchKeyValue(TYPE_PING, BLANK, BLANK); + dispatchKeyValue(TYPE_PING, BLANK); return; } if (RESTART.equals(message)) { - dispatchKeyValue(TYPE_RESTART, BLANK, BLANK); + dispatchKeyValue(TYPE_RESTART, BLANK); return; } if (message.contains(VER_STR_E6) || message.contains(VER_STR_GC)) { // example: #VER"NV-E6G FWv2.66 HWv0" // split on " and return the version number - dispatchKeyValue(TYPE_VERSION, "", message.split("\"")[1]); + dispatchKeyValue(TYPE_VERSION, message.split("\"")[1]); return; } if (message.equals(ALL_OFF)) { - dispatchKeyValue(TYPE_ALLOFF, BLANK, BLANK); + dispatchKeyValue(TYPE_ALLOFF, BLANK); return; } if (message.contains(MUTE)) { - dispatchKeyValue(TYPE_ALLMUTE, BLANK, message.substring(message.length() - 1)); + dispatchKeyValue(TYPE_ALLMUTE, message.substring(message.length() - 1)); return; } if (message.contains(PAGE)) { - dispatchKeyValue(TYPE_PAGE, BLANK, message.substring(message.length() - 1)); + dispatchKeyValue(TYPE_PAGE, message.substring(message.length() - 1)); return; } - // Amp controller sent an album art request - Matcher matcher = ALBUM_ART_REQ.matcher(message); - if (matcher.find()) { - // pull out the source id and the remainder of the message - dispatchKeyValue(TYPE_ALBUM_ART_REQ, matcher.group(1), matcher.group(2)); - return; - } + Matcher matcher; - // Amp controller sent an album art fragment request - matcher = ALBUM_ART_FRAG_REQ.matcher(message); - if (matcher.find()) { - // pull out the source id and the remainder of the message - dispatchKeyValue(TYPE_ALBUM_ART_FRAG_REQ, matcher.group(1), matcher.group(2)); - return; - } + if (isAnyOhNuvoNet) { + // Amp controller sent a NuvoNet album art request + matcher = NN_ALBUM_ART_REQ.matcher(message); + if (matcher.find()) { + dispatchKeyValue(TYPE_NN_ALBUM_ART_REQ, BLANK, matcher.group(1), matcher.group(2)); + return; + } - // Amp controller sent a request to play a favorite - matcher = FAVORITE_PATTERN.matcher(message); - if (matcher.find()) { - // pull out the source id and the remainder of the message - dispatchKeyValue(TYPE_FAVORITE_REQ, matcher.group(1), matcher.group(2)); - return; + // Amp controller sent a NuvoNet album art fragment request + matcher = NN_ALBUM_ART_FRAG_REQ.matcher(message); + if (matcher.find()) { + dispatchKeyValue(TYPE_NN_ALBUM_ART_FRAG_REQ, BLANK, matcher.group(1), matcher.group(2)); + return; + } + + // Amp controller sent a request for a NuvoNet source to play a favorite + matcher = NN_FAVORITE_PATTERN.matcher(message); + if (matcher.find()) { + dispatchKeyValue(TYPE_NN_FAVORITE_REQ, BLANK, matcher.group(1), matcher.group(2)); + return; + } } // Amp controller sent a source update ie: #S2DISPINFO,DUR3380,POS3090,STATUS2 // or #S2DISPLINE1,"1 of 17" matcher = SRC_PATTERN.matcher(message); if (matcher.find()) { - // pull out the source id and the remainder of the message - dispatchKeyValue(TYPE_SOURCE_UPDATE, matcher.group(1), matcher.group(2)); + dispatchKeyValue(TYPE_SOURCE_UPDATE, BLANK, matcher.group(1), matcher.group(2)); return; } // Amp controller sent a zone update ie: #Z11,ON,SRC3,VOL63,DND0,LOCK0 matcher = ZONE_PATTERN.matcher(message); if (matcher.find()) { - // pull out the zone id and the remainder of the message - dispatchKeyValue(TYPE_ZONE_UPDATE, matcher.group(1), matcher.group(2)); + dispatchKeyValue(TYPE_ZONE_UPDATE, matcher.group(1), BLANK, matcher.group(2)); return; } - // Amp controller sent a zone BUTTONTWO press event ie: #Z11S3BUTTONTWO4,2,0,0,0 - matcher = ZONE_BUTTONTWO_PATTERN.matcher(message); - if (matcher.find()) { - // redundant - ignore - return; - } - - // Amp controller sent a zone BUTTON press event ie: #Z4S6BUTTON1,1,0xFFFFFFFF,1,3 - matcher = ZONE_BUTTON2_PATTERN.matcher(message); - if (matcher.find()) { - // pull out the remainder of the message: button #, action, menuid, itemid, itemidx - String[] buttonSplit = matcher.group(3).split(COMMA); - - // second field is button action, only send DOWNUP (0) or DOWN (1), ignore UP (2) - if (ZERO.equals(buttonSplit[1]) || ONE.equals(buttonSplit[1])) { - // a button in a menu was pressed, send SxZy,menuid,itemidx - if (!ZERO.equals(buttonSplit[2])) { - dispatchKeyValue(TYPE_MENU_ITEM_SELECTED, matcher.group(2), SRC_KEY + matcher.group(2) + ZONE_KEY - + matcher.group(1) + COMMA + buttonSplit[2] + COMMA + buttonSplit[3]); - } else { - // send the button # in the event, don't send extra fields menuid, itemid, etc.. - dispatchKeyValue(TYPE_ZONE_BUTTON2, matcher.group(2), buttonSplit[0]); - } + if (isAnyOhNuvoNet) { + // Amp controller sent a NuvoNet zone/source BUTTONTWO press event ie: #Z11S3BUTTONTWO4,2,0,0,0 + matcher = NN_BUTTONTWO_PATTERN.matcher(message); + if (matcher.find()) { + // redundant - ignore + return; + } + + // Amp controller sent a NuvoNet zone/source BUTTON press event ie: #Z4S6BUTTON1,1,0xFFFFFFFF,1,3 + matcher = NN_BUTTON_PATTERN.matcher(message); + if (matcher.find()) { + // pull out the remainder of the message: button #, action, menuid, itemid, itemidx + String[] buttonSplit = matcher.group(3).split(COMMA); + + // second field is button action, only send DOWNUP (0) or DOWN (1), ignore UP (2) + if (ZERO.equals(buttonSplit[1]) || ONE.equals(buttonSplit[1])) { + // a button in a menu was pressed, send 'menuid,itemidx' + if (!ZERO.equals(buttonSplit[2])) { + dispatchKeyValue(TYPE_NN_MENU_ITEM_SELECTED, matcher.group(1), matcher.group(2), + buttonSplit[2] + COMMA + buttonSplit[3]); + } else { + // send the button # in the event, don't send extra fields menuid, itemid, etc.. + dispatchKeyValue(TYPE_NN_BUTTON, matcher.group(1), matcher.group(2), buttonSplit[0]); + } + } + return; + } + + // Amp controller sent a NuvoNet zone/source menu request event ie: #Z2S6MENUREQ0x0000000B,1,0,0 + matcher = NN_MENUREQ_PATTERN.matcher(message); + if (matcher.find()) { + dispatchKeyValue(TYPE_NN_MENUREQ, matcher.group(1), matcher.group(2), matcher.group(3)); + return; } - return; } - // Amp controller sent a menu request event ie: #Z2S6MENUREQ0x0000000B,1,0,0 - matcher = ZONE_MENUREQ_PATTERN.matcher(message); + // Amp controller sent a zone/source button press event ie: #Z11S3PLAYPAUSE + matcher = ZONE_SOURCE_PATTERN.matcher(message); if (matcher.find()) { - // pull out the source id and send SxZy plus the remainder of the message - dispatchKeyValue(TYPE_ZONE_MENUREQ, matcher.group(2), - SRC_KEY + matcher.group(2) + ZONE_KEY + matcher.group(1) + COMMA + matcher.group(3)); - return; - } - - // Amp controller sent a zone button press event ie: #Z11S3PLAYPAUSE - matcher = ZONE_BUTTON_PATTERN.matcher(message); - if (matcher.find()) { - // pull out the source id and the remainder of the message, ignore the zone id - dispatchKeyValue(TYPE_ZONE_BUTTON, matcher.group(2), matcher.group(3)); + dispatchKeyValue(TYPE_ZONE_SOURCE_BUTTON, matcher.group(1), matcher.group(2), matcher.group(3)); return; } @@ -447,7 +455,7 @@ public abstract class NuvoConnector { matcher = ZONE_CFG_PATTERN.matcher(message); if (matcher.find()) { // pull out the zone id and the remainder of the message - dispatchKeyValue(TYPE_ZONE_CONFIG, matcher.group(1), matcher.group(2)); + dispatchKeyValue(TYPE_ZONE_CONFIG, matcher.group(1), BLANK, matcher.group(2)); return; } @@ -455,14 +463,25 @@ public abstract class NuvoConnector { } /** - * Dispatch an event (type, key, value) to the event listeners + * Dispatch a system level event without zone or src to the event listeners * * @param type the type - * @param key the key * @param value the value */ - private void dispatchKeyValue(String type, String key, String value) { - NuvoMessageEvent event = new NuvoMessageEvent(this, type, key, value); + private void dispatchKeyValue(String type, String value) { + dispatchKeyValue(type, BLANK, BLANK, value); + } + + /** + * Dispatch an event (type, zone, src, value) to the event listeners + * + * @param type the type + * @param zone the zone id + * @param src the source id + * @param value the value + */ + private void dispatchKeyValue(String type, String zone, String src, String value) { + NuvoMessageEvent event = new NuvoMessageEvent(this, type, zone, src, value); listeners.forEach(l -> l.onNewMessageEvent(event)); } } diff --git a/bundles/org.openhab.binding.nuvo/src/main/java/org/openhab/binding/nuvo/internal/communication/NuvoMessageEvent.java b/bundles/org.openhab.binding.nuvo/src/main/java/org/openhab/binding/nuvo/internal/communication/NuvoMessageEvent.java index 7539c941b..2e6cd59ae 100644 --- a/bundles/org.openhab.binding.nuvo/src/main/java/org/openhab/binding/nuvo/internal/communication/NuvoMessageEvent.java +++ b/bundles/org.openhab.binding.nuvo/src/main/java/org/openhab/binding/nuvo/internal/communication/NuvoMessageEvent.java @@ -25,13 +25,15 @@ import org.eclipse.jdt.annotation.NonNullByDefault; public class NuvoMessageEvent extends EventObject { private static final long serialVersionUID = 1L; private final String type; - private final String key; + private final String zone; + private final String src; private final String value; - public NuvoMessageEvent(Object source, String type, String key, String value) { + public NuvoMessageEvent(Object source, String type, String zone, String src, String value) { super(source); this.type = type; - this.key = key; + this.zone = zone; + this.src = src; this.value = value; } @@ -39,8 +41,12 @@ public class NuvoMessageEvent extends EventObject { return type; } - public String getKey() { - return key; + public String getZone() { + return zone; + } + + public String getSrc() { + return src; } public String getValue() { diff --git a/bundles/org.openhab.binding.nuvo/src/main/java/org/openhab/binding/nuvo/internal/handler/NuvoHandler.java b/bundles/org.openhab.binding.nuvo/src/main/java/org/openhab/binding/nuvo/internal/handler/NuvoHandler.java index 56320cb5c..bfa27e083 100644 --- a/bundles/org.openhab.binding.nuvo/src/main/java/org/openhab/binding/nuvo/internal/handler/NuvoHandler.java +++ b/bundles/org.openhab.binding.nuvo/src/main/java/org/openhab/binding/nuvo/internal/handler/NuvoHandler.java @@ -215,7 +215,7 @@ public class NuvoHandler extends BaseThingHandler implements NuvoMessageEventLis if (serialPort != null && !serialPort.isEmpty()) { connector = new NuvoSerialConnector(serialPortManager, serialPort, uid); - } else if (port != null) { + } else if (host != null && port != null) { connector = new NuvoIpConnector(host, port, uid); this.isMps4 = (port.intValue() == MPS4_PORT); } else { @@ -239,6 +239,7 @@ public class NuvoHandler extends BaseThingHandler implements NuvoMessageEventLis if (this.isAnyOhNuvoNet) { logger.debug("At least one source is configured as an openHAB NuvoNet source"); + connector.setAnyOhNuvoNet(true); loadMenuConfiguration(config); favoriteMap.put("1", @@ -640,11 +641,12 @@ public class NuvoHandler extends BaseThingHandler implements NuvoMessageEventLis */ @Override public void onNewMessageEvent(NuvoMessageEvent evt) { - logger.debug("onNewMessageEvent: key {} = {}", evt.getKey(), evt.getValue()); + logger.debug("onNewMessageEvent: zone {}, source {}, value {}", evt.getZone(), evt.getSrc(), evt.getValue()); lastEventReceived = System.currentTimeMillis(); String type = evt.getType(); - String key = evt.getKey(); + String zoneId = evt.getZone(); + String srcId = evt.getSrc(); String updateData = evt.getValue().trim(); if (this.getThing().getStatus() != ThingStatus.ONLINE) { updateStatus(ThingStatus.ONLINE, ThingStatusDetail.NONE, this.versionString); @@ -687,8 +689,8 @@ public class NuvoHandler extends BaseThingHandler implements NuvoMessageEventLis updateChannelState(NuvoEnum.SYSTEM, CHANNEL_TYPE_PAGE, ONE.equals(updateData) ? ON : OFF); break; case TYPE_SOURCE_UPDATE: - logger.debug("Source update: Source: {} - Value: {}", key, updateData); - NuvoEnum targetSource = NuvoEnum.valueOf(SOURCE + key); + logger.debug("Source update: Source: {} - Value: {}", srcId, updateData); + NuvoEnum targetSource = NuvoEnum.valueOf(SOURCE + srcId); if (updateData.contains(DISPLINE)) { // example: DISPLINE2,"Play My Song (Featuring Dee Ajayi)" @@ -712,16 +714,16 @@ public class NuvoHandler extends BaseThingHandler implements NuvoMessageEventLis } else if (updateData.contains(NAME_QUOTE)) { // example: NAME"Ipod" String name = updateData.split("\"")[1]; - sourceLabels.put(key, name); + sourceLabels.put(srcId, name); } break; case TYPE_ZONE_UPDATE: - logger.debug("Zone update: Zone: {} - Value: {}", key, updateData); + logger.debug("Zone update: Zone: {} - Value: {}", zoneId, updateData); // example : OFF // or: ON,SRC3,VOL63,DND0,LOCK0 // or: ON,SRC3,MUTE,DND0,LOCK0 - NuvoEnum targetZone = NuvoEnum.valueOf(ZONE + key); + NuvoEnum targetZone = NuvoEnum.valueOf(ZONE + zoneId); if (OFF.equals(updateData)) { updateChannelState(targetZone, CHANNEL_TYPE_POWER, OFF); @@ -746,35 +748,43 @@ public class NuvoHandler extends BaseThingHandler implements NuvoMessageEventLis } } break; - case TYPE_ZONE_BUTTON: - logger.debug("Zone Button pressed: Source: {} - Button: {}", key, updateData); - updateChannelState(NuvoEnum.valueOf(SOURCE + key), CHANNEL_BUTTON_PRESS, updateData); + case TYPE_ZONE_SOURCE_BUTTON: + logger.debug("Source Button pressed: Source: {} - Button: {}", srcId, updateData); + updateChannelState(NuvoEnum.valueOf(SOURCE + srcId), CHANNEL_BUTTON_PRESS, updateData); + updateChannelState(NuvoEnum.SYSTEM, CHANNEL_TYPE_BUTTONPRESS, zoneId + COMMA + updateData); break; - case TYPE_ZONE_BUTTON2: + case TYPE_NN_BUTTON: String buttonAction = NuvoStatusCodes.BUTTON_CODE.get(updateData); if (buttonAction != null) { - logger.debug("Zone NuvoNet Button pressed: Source: {} - Button: {}", key, buttonAction); - updateChannelState(NuvoEnum.valueOf(SOURCE + key), CHANNEL_BUTTON_PRESS, buttonAction); + logger.debug("NuvoNet Source Button pressed: Source: {} - Button: {}", srcId, buttonAction); + updateChannelState(NuvoEnum.valueOf(SOURCE + srcId), CHANNEL_BUTTON_PRESS, buttonAction); + updateChannelState(NuvoEnum.SYSTEM, CHANNEL_TYPE_BUTTONPRESS, zoneId + COMMA + buttonAction); } else { - logger.debug("Zone NuvoNet Button pressed: Source: {} - Unknown button code: {}", key, updateData); - updateChannelState(NuvoEnum.valueOf(SOURCE + key), CHANNEL_BUTTON_PRESS, updateData); + logger.debug("NuvoNet Source Button pressed: Source: {} - Unknown button code: {}", srcId, + updateData); + updateChannelState(NuvoEnum.valueOf(SOURCE + srcId), CHANNEL_BUTTON_PRESS, updateData); + updateChannelState(NuvoEnum.SYSTEM, CHANNEL_TYPE_BUTTONPRESS, zoneId + COMMA + updateData); } break; - case TYPE_MENU_ITEM_SELECTED: + case TYPE_NN_MENU_ITEM_SELECTED: // ignore this update unless openHAB is handling this source - if (nuvoNetSrcMap.get(key).equals(2)) { + if (nuvoNetSrcMap.get(srcId).equals(2)) { + String sourceZone = SRC_KEY + srcId + ZONE_KEY + zoneId; String[] updateDataSplit = updateData.split(COMMA); - String zoneSource = updateDataSplit[0]; - String menuId = updateDataSplit[1]; - int menuItemIdx = Integer.parseInt(updateDataSplit[2]) - 1; + String menuId = updateDataSplit[0]; + int menuItemIdx = Integer.parseInt(updateDataSplit[1]) - 1; boolean exitMenu = false; if ("0xFFFFFFFF".equals(menuId)) { - TopMenu topMenuItem = nuvoMenus.getSource().get(Integer.parseInt(key) - 1).getTopMenu() + TopMenu topMenuItem = nuvoMenus.getSource().get(Integer.parseInt(srcId) - 1).getTopMenu() .get(menuItemIdx); - logger.debug("Top Menu item selected: Source: {} - Menu Item: {}", key, topMenuItem.getText()); - updateChannelState(NuvoEnum.valueOf(SOURCE + key), CHANNEL_BUTTON_PRESS, topMenuItem.getText()); + logger.debug("Top Menu item selected: Source: {} - Menu Item: {}", srcId, + topMenuItem.getText()); + updateChannelState(NuvoEnum.valueOf(SOURCE + srcId), CHANNEL_BUTTON_PRESS, + topMenuItem.getText()); + updateChannelState(NuvoEnum.SYSTEM, CHANNEL_TYPE_BUTTONPRESS, + zoneId + COMMA + topMenuItem.getText()); List subMenuItems = topMenuItem.getItems(); @@ -784,135 +794,139 @@ public class NuvoHandler extends BaseThingHandler implements NuvoMessageEventLis // send submenu (maximum of 20 items) int subMenuSize = subMenuItems.size() < 20 ? subMenuItems.size() : 20; try { - connector.sendCommand(zoneSource + "MENU" + (menuItemIdx + 11) + ",0,0," + subMenuSize + connector.sendCommand(sourceZone + "MENU" + (menuItemIdx + 11) + ",0,0," + subMenuSize + ",0,0," + subMenuSize + ",\"" + topMenuItem.getText() + "\""); Thread.sleep(SLEEP_BETWEEN_CMD_MS); for (int i = 0; i < subMenuSize; i++) { connector.sendCommand( - zoneSource + "MENUITEM" + (i + 1) + ",0,0,\"" + subMenuItems.get(i) + "\""); + sourceZone + "MENUITEM" + (i + 1) + ",0,0,\"" + subMenuItems.get(i) + "\""); } } catch (NuvoException | InterruptedException e) { - logger.debug("Error sending sub menu for {}", zoneSource); + logger.debug("Error sending sub menu to {}", sourceZone); } } } else { // a sub menu item was selected - TopMenu topMenuItem = nuvoMenus.getSource().get(Integer.parseInt(key) - 1).getTopMenu() + TopMenu topMenuItem = nuvoMenus.getSource().get(Integer.parseInt(srcId) - 1).getTopMenu() .get(Integer.decode(menuId) - 11); String subMenuItem = topMenuItem.getItems().get(menuItemIdx); - logger.debug("Sub Menu item selected: Source: {} - Menu Item: {}", key, + logger.debug("Sub Menu item selected: Source: {} - Menu Item: {}", srcId, topMenuItem.getText() + "|" + subMenuItem); - updateChannelState(NuvoEnum.valueOf(SOURCE + key), CHANNEL_BUTTON_PRESS, + updateChannelState(NuvoEnum.valueOf(SOURCE + srcId), CHANNEL_BUTTON_PRESS, topMenuItem.getText() + "|" + subMenuItem); + updateChannelState(NuvoEnum.SYSTEM, CHANNEL_TYPE_BUTTONPRESS, + zoneId + COMMA + topMenuItem.getText() + "|" + subMenuItem); exitMenu = true; } if (exitMenu) { try { // tell the zone to exit the menu - connector.sendCommand(zoneSource + "MENU0,0,0,0,0,0,0,\"\""); + connector.sendCommand(sourceZone + "MENU0,0,0,0,0,0,0,\"\""); } catch (NuvoException e) { - logger.debug("Error sending exit menu command for {}", zoneSource); + logger.debug("Error sending exit menu command to {}", sourceZone); } } } break; - case TYPE_ZONE_MENUREQ: + case TYPE_NN_MENUREQ: // ignore this update unless openHAB is handling this source - if (nuvoNetSrcMap.get(key).equals(2)) { - logger.debug("Menu Request: Source: {} - Value: {}", key, updateData); - // For now we only support one level deep menus. If third field is '1', indicates go back to main + if (nuvoNetSrcMap.get(srcId).equals(2)) { + logger.debug("Menu Request: Source: {} - Value: {}", srcId, updateData); + String sourceZone = SRC_KEY + srcId + ZONE_KEY + zoneId; + // For now we only support one level deep menus. If second field is '1', indicates go back to main // menu. - String[] menuDataSplit = updateData.split(","); - if (menuDataSplit.length > 3 && ONE.equals(menuDataSplit[2])) { + String[] menuDataSplit = updateData.split(COMMA); + if (menuDataSplit.length > 2 && ONE.equals(menuDataSplit[1])) { try { - connector.sendCommand(menuDataSplit[0] + "MENU0xFFFFFFFF,0,0,0,0,0,0,\"\""); + connector.sendCommand(sourceZone + "MENU0xFFFFFFFF,0,0,0,0,0,0,\"\""); } catch (NuvoException e) { - logger.debug("Error sending main menu command for {}", menuDataSplit[0]); + logger.debug("Error sending main menu command to {}", sourceZone); } } } break; case TYPE_ZONE_CONFIG: - logger.debug("Zone Configuration: Zone: {} - Value: {}", key, updateData); + logger.debug("Zone Configuration: Zone: {} - Value: {}", zoneId, updateData); // example: BASS1,TREB-2,BALR2,LOUDCMP1 Matcher matcher = ZONE_CFG_PATTERN.matcher(updateData); if (matcher.find()) { - updateChannelState(NuvoEnum.valueOf(ZONE + key), CHANNEL_TYPE_BASS, matcher.group(1)); - updateChannelState(NuvoEnum.valueOf(ZONE + key), CHANNEL_TYPE_TREBLE, matcher.group(2)); - updateChannelState(NuvoEnum.valueOf(ZONE + key), CHANNEL_TYPE_BALANCE, + updateChannelState(NuvoEnum.valueOf(ZONE + zoneId), CHANNEL_TYPE_BASS, matcher.group(1)); + updateChannelState(NuvoEnum.valueOf(ZONE + zoneId), CHANNEL_TYPE_TREBLE, matcher.group(2)); + updateChannelState(NuvoEnum.valueOf(ZONE + zoneId), CHANNEL_TYPE_BALANCE, NuvoStatusCodes.getBalanceFromStr(matcher.group(3))); - updateChannelState(NuvoEnum.valueOf(ZONE + key), CHANNEL_TYPE_LOUDNESS, + updateChannelState(NuvoEnum.valueOf(ZONE + zoneId), CHANNEL_TYPE_LOUDNESS, ONE.equals(matcher.group(4)) ? ON : OFF); } else { logger.debug("no match on message: {}", updateData); } break; - case TYPE_ALBUM_ART_REQ: + case TYPE_NN_ALBUM_ART_REQ: // ignore this update unless openHAB is handling this source - if (nuvoNetSrcMap.get(key).equals(2)) { - logger.debug("Album Art Request for Source: {} - Data: {}", key, updateData); + if (nuvoNetSrcMap.get(srcId).equals(2)) { + logger.debug("Album Art Request for Source: {} - Data: {}", srcId, updateData); // 0x620FD879,80,80,2,0x00C0C0C0,0,0,0,0,1 String[] albumArtReq = updateData.split(COMMA); - albumArtIds.put(SRC_KEY + key, Integer.decode(albumArtReq[0])); + albumArtIds.put(SRC_KEY + srcId, Integer.decode(albumArtReq[0])); try { - if (albumArtMap.get(SRC_KEY + key).length > 1) { - connector.sendCommand(SRC_KEY + key + ALBUM_ART_AVAILABLE + albumArtIds.get(SRC_KEY + key) - + COMMA + albumArtMap.get(SRC_KEY + key).length); + if (albumArtMap.get(SRC_KEY + srcId).length > 1) { + connector.sendCommand( + SRC_KEY + srcId + ALBUM_ART_AVAILABLE + albumArtIds.get(SRC_KEY + srcId) + COMMA + + albumArtMap.get(SRC_KEY + srcId).length); } else { - connector.sendCommand(SRC_KEY + key + ALBUM_ART_AVAILABLE + ZERO_COMMA); + connector.sendCommand(SRC_KEY + srcId + ALBUM_ART_AVAILABLE + ZERO_COMMA); } } catch (NuvoException e) { - logger.debug("Error sending ALBUMARTAVAILABLE command for source: {}", key); + logger.debug("Error sending ALBUMARTAVAILABLE command for source: {}", srcId); } } break; - case TYPE_ALBUM_ART_FRAG_REQ: + case TYPE_NN_ALBUM_ART_FRAG_REQ: // ignore this update unless openHAB is handling this source - if (nuvoNetSrcMap.get(key).equals(2)) { - logger.debug("Album Art Fragment Request for Source: {} - Data: {}", key, updateData); + if (nuvoNetSrcMap.get(srcId).equals(2)) { + logger.debug("Album Art Fragment Request for Source: {} - Data: {}", srcId, updateData); // 0x620FD879,0,750 (id, requested offset from start of image, byte length requested) String[] albumArtFragReq = updateData.split(COMMA); int requestedId = Integer.decode(albumArtFragReq[0]); int offset = Integer.parseInt(albumArtFragReq[1]); int length = Integer.parseInt(albumArtFragReq[2]); - if (requestedId == albumArtIds.get(SRC_KEY + key)) { + if (requestedId == albumArtIds.get(SRC_KEY + srcId)) { byte[] chunk = new byte[length]; - byte[] albumArtBytes = albumArtMap.get(SRC_KEY + key); + byte[] albumArtBytes = albumArtMap.get(SRC_KEY + srcId); if (albumArtBytes != null) { System.arraycopy(albumArtBytes, offset, chunk, 0, length); final String frag = Base64.getEncoder().encodeToString(chunk); try { - connector.sendCommand(SRC_KEY + key + ALBUM_ART_FRAG + requestedId + COMMA + offset + connector.sendCommand(SRC_KEY + srcId + ALBUM_ART_FRAG + requestedId + COMMA + offset + COMMA + frag.length() + COMMA + frag); } catch (NuvoException e) { - logger.debug("Error sending ALBUMARTFRAG command for source: {}, artId: {}", key, + logger.debug("Error sending ALBUMARTFRAG command for source: {}, artId: {}", srcId, requestedId); } } } } break; - case TYPE_FAVORITE_REQ: + case TYPE_NN_FAVORITE_REQ: // ignore this update unless openHAB is handling this source - if (nuvoNetSrcMap.get(key).equals(2)) { - logger.debug("Favorite request for source: {} - favoriteId: {}", key, updateData); + if (nuvoNetSrcMap.get(srcId).equals(2)) { + logger.debug("Favorite request for source: {} - favoriteId: {}", srcId, updateData); try { int playlistIdx = Integer.parseInt(updateData, 16) - 1000; - updateChannelState(NuvoEnum.valueOf(SOURCE + key), CHANNEL_BUTTON_PRESS, - "PLAY_MUSIC_PRESET:" + favoriteMap.get(key)[playlistIdx]); + updateChannelState(NuvoEnum.valueOf(SOURCE + srcId), CHANNEL_BUTTON_PRESS, + "PLAY_MUSIC_PRESET:" + favoriteMap.get(srcId)[playlistIdx]); } catch (NumberFormatException nfe) { logger.debug("Unable to parse favoriteId: {}", updateData); } } break; default: - logger.debug("onNewMessageEvent: unhandled key {}", key); + logger.debug("onNewMessageEvent: unhandled event type {}", type); // Return here because receiving an unknown message does not indicate that one can poll return; } diff --git a/bundles/org.openhab.binding.nuvo/src/main/resources/OH-INF/i18n/nuvo.properties b/bundles/org.openhab.binding.nuvo/src/main/resources/OH-INF/i18n/nuvo.properties index b882e74e5..4d69eef37 100644 --- a/bundles/org.openhab.binding.nuvo/src/main/resources/OH-INF/i18n/nuvo.properties +++ b/bundles/org.openhab.binding.nuvo/src/main/resources/OH-INF/i18n/nuvo.properties @@ -165,7 +165,7 @@ channel-type.nuvo.balance.description = Adjust the Balance Setting for the Zone channel-type.nuvo.bass.label = Bass Adjustment channel-type.nuvo.bass.description = Adjust the Bass Setting for the Zone channel-type.nuvo.button_press.label = Button Pressed -channel-type.nuvo.button_press.description = Indicates the Last Button Pressed On the Keypad for a Non NuvoNet Source +channel-type.nuvo.button_press.description = Indicates the Last Button Pressed On the Keypad channel-type.nuvo.control.label = Control channel-type.nuvo.control.description = Transport Controls e.g. Play/Pause/Next/Previous for the Current Source channel-type.nuvo.display_line1.label = Display Line 1 diff --git a/bundles/org.openhab.binding.nuvo/src/main/resources/OH-INF/thing/channels.xml b/bundles/org.openhab.binding.nuvo/src/main/resources/OH-INF/thing/channels.xml index 023af7f53..3b35889c4 100644 --- a/bundles/org.openhab.binding.nuvo/src/main/resources/OH-INF/thing/channels.xml +++ b/bundles/org.openhab.binding.nuvo/src/main/resources/OH-INF/thing/channels.xml @@ -342,6 +342,7 @@ + @@ -518,7 +519,7 @@ String - Indicates the Last Button Pressed On the Keypad for a Non NuvoNet Source + Indicates the Last Button Pressed On the Keypad