mirror of
https://github.com/openhab/openhab-addons.git
synced 2025-01-10 15:11:59 +01:00
[nuvo] Add NuvoNet source communication capabilities (#12042)
* Add NuvoNet source communication Signed-off-by: Michael Lobstein <michael.lobstein@gmail.com> * fix readme Signed-off-by: Michael Lobstein <michael.lobstein@gmail.com> * remove commented code Signed-off-by: Michael Lobstein <michael.lobstein@gmail.com> * Add startup/shutdown keypad messages Signed-off-by: Michael Lobstein <michael.lobstein@gmail.com> * Minor cleanup before code review Signed-off-by: Michael Lobstein <michael.lobstein@gmail.com> * Add configurable favorites labels Signed-off-by: Michael Lobstein <michael.lobstein@gmail.com> * add new config item to i18n properties Signed-off-by: Michael Lobstein <michael.lobstein@gmail.com> * Fix restart detection and improve version matching Signed-off-by: Michael Lobstein <michael.lobstein@gmail.com> * review changes Signed-off-by: Michael Lobstein <michael.lobstein@gmail.com> * Increment version number Signed-off-by: Michael Lobstein <michael.lobstein@gmail.com> * remove repeated word in channels.xml Signed-off-by: Michael Lobstein <michael.lobstein@gmail.com> * Review changes Signed-off-by: Michael Lobstein <michael.lobstein@gmail.com> Signed-off-by: Michael Lobstein <michael.lobstein@gmail.com>
This commit is contained in:
parent
cbe41951fd
commit
89c73a0d81
@ -1,7 +1,7 @@
|
|||||||
# Nuvo Grand Concerto & Essentia G Binding
|
# Nuvo Grand Concerto & Essentia G Binding
|
||||||
|
|
||||||
This binding can be used to control the Nuvo Grand Concerto or Essentia G whole house multi-zone amplifier.
|
This binding can be used to control the Nuvo Grand Concerto or Essentia G whole house multi-zone amplifier.
|
||||||
Up to 20 keypad zones can be controlled when zone expansion modules are used (if not all zones on the amp are used they can be excluded via configuration).
|
Up to 20 keypad zones can be controlled when zone expansion modules are used (if not all zones on the amp are used, they can be excluded via configuration).
|
||||||
|
|
||||||
The binding supports three different kinds of connections:
|
The binding supports three different kinds of connections:
|
||||||
|
|
||||||
@ -12,6 +12,7 @@ The binding supports three different kinds of connections:
|
|||||||
For users without a serial connector on the server side, you can use a USB to serial adapter.
|
For users without a serial connector on the server side, you can use a USB to serial adapter.
|
||||||
|
|
||||||
If you are using the Nuvo MPS4 music server with your Grand Concerto or Essentia G, the binding can connect to the server's IP address on port 5006.
|
If you are using the Nuvo MPS4 music server with your Grand Concerto or Essentia G, the binding can connect to the server's IP address on port 5006.
|
||||||
|
Using the MPS4 connection will also allow for greater interaction with the keypads to include custom menus, custom favorite lists and album art display on the CTP-36 keypad.
|
||||||
|
|
||||||
You don't need to have your Grand Concerto or Essentia G whole house amplifier device directly connected to your openHAB server.
|
You don't need to have your Grand Concerto or Essentia G whole house amplifier device directly connected to your openHAB server.
|
||||||
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).
|
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).
|
||||||
@ -36,39 +37,60 @@ All settings are through thing configuration parameters.
|
|||||||
The thing has the following configuration parameters:
|
The thing has the following configuration parameters:
|
||||||
|
|
||||||
| Parameter Label | Parameter ID | Description | Accepted values |
|
| Parameter Label | Parameter ID | Description | Accepted values |
|
||||||
|-------------------------|--------------|------------------------------------------------------------------------------------------------------------------------------------|------------------------|
|
|--------------------------|--------------- |-------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------|
|
||||||
| Serial Port | serialPort | Serial port to use for connecting to the Nuvo whole house amplifier device | a comm port name |
|
| Serial Port | serialPort | Serial port to use for connecting to the Nuvo whole house amplifier device | a comm port name |
|
||||||
| Address | host | Host name or IP address of the machine connected to the Nuvo whole house amplifier serial port (serial over IP) or MPS4 server | host name or ip |
|
| Address | host | Host name or IP address of the machine connected to the Nuvo whole house amplifier serial port (serial over IP) or MPS4 server | host name or ip |
|
||||||
| Port | port | Communication port (serial over IP). | ip port number |
|
| Port | port | Communication port (serial over IP). | ip port number |
|
||||||
| Number of Zones | numZones | (Optional) Number of zones on the amplifier to utilize in the binding (up to 20 zones when zone expansion modules are used) | (1-20; default 6) |
|
| Number of Zones | numZones | (Optional) Number of zones on the amplifier to utilize in the binding (up to 20 zones when zone expansion modules are used) | (1-20; default 6) |
|
||||||
|
| Favorite Labels | favoriteLabels | A comma separated list of up to 12 label names that are loaded into the 'favorites' channel of each zone. These represent keypad favorites 1-12 | Optional; Comma separated list, max 12 items. ie: Favorite 1,Favorite 2,Favorite 3 |
|
||||||
| Sync Clock on GConcerto | clockSync | (Optional) If set to true, the binding will sync the internal clock on the Grand Concerto to match the openHAB host's system clock | Boolean; default false |
|
| Sync Clock on GConcerto | clockSync | (Optional) If set to true, the binding will sync the internal clock on the Grand Concerto to match the openHAB host's system clock | Boolean; default false |
|
||||||
|
| Source N is NuvoNet | nuvoNetSrcN | MPS4 Only! Indicate if the source is a NuvoNet source in the MPS4 or in openHAB. Nuvo tuners & iPod docks and all others set to 0 | 0 = Non-NuvoNet source, 1 = Source is a used by MPS4, 2 = openHAB NuvoNet Source |
|
||||||
|
| Source N Favorites | favoritesSrcN | MPS4 Only! A comma separated list of favorite names to load into the global favorites list for Source N. See *very advanced* rules | Comma separated list, max 20 items. Each item max 40 chars, ie: Oldies,Pop,Rock |
|
||||||
|
| Source N Favorite Prefix | favPrefixN | MPS4 Only! To quickly locate a Source's favorites, this prefix will be added to the favorite names. See *very advanced* rules | Text; ie: 'S2-' will cause the favorite names to be prefixed, e.g. 'S2-Rock' |
|
||||||
|
| Source N Menu XML | menuXmlSrcN | MPS4 Only! Will load a custom menu for a given source into the keypads. Up to 10 items in the top menu and up to 20 items in each sub menu | XML Text string; see examples below and *very advanced* rules for usage |
|
||||||
|
|
||||||
Some notes:
|
Some notes:
|
||||||
|
|
||||||
* If the port is set to 5006, the binding will adjust its protocol to connect to the Nuvo amplifier thing via an MPS4 IP connection.
|
* If the port is set to 5006, the binding will adjust its protocol to connect to the Nuvo amplifier thing via an MPS4 IP connection.
|
||||||
* MPS4 connections do not support custom commands using `SxDISPINFO` including those outlined in the advanced rules section below.
|
* MPS4 connections do not support commands using `SxDISPINFO`& `SxDISPLINE` (display_lineN channels) including those outlined in the advanced rules section below.
|
||||||
|
* As of OH 3.4.0, the binding supports NuvoNet source communication for any/all of the amplifier's 6 inputs but only when using an MPS4 connection.
|
||||||
|
* By implementing NuvoNet communication, the binding can now support sending custom menus, custom favorite lists, album art, etc. to the Nuvo keypads for each source configured as an openHAB NuvoNet source.
|
||||||
* If a zone has a maximum volume limit configured by the Nuvo configurator, the volume slider will automatically drop back to that level if set above the configured limit.
|
* If a zone has a maximum volume limit configured by the Nuvo configurator, the volume slider will automatically drop back to that level if set above the configured limit.
|
||||||
* Source display_line1 thru 4 can only be updated on non NuvoNet sources.
|
* Source display_line1 thru 4 can only be updated on non NuvoNet sources when not using an MPS4 connection.
|
||||||
* The track_position channel does not update continuously for NuvoNet sources. It only changes when the track changes or playback is paused/unpaused.
|
* The track_position channel does not update continuously for NuvoNet sources. It only changes when the track changes or playback is paused/unpaused.
|
||||||
|
|
||||||
* On Linux, you may get an error stating the serial port cannot be opened when the Nuvo binding tries to load.
|
* On Linux, you may get an error stating the serial port cannot be opened when the Nuvo 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`.
|
* 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. Nuvo and RFXcom. See the [general documentation about serial port configuration](/docs/administration/serial.html) for more on symlinking the USB ports.
|
* Also on Linux you may have issues with the USB if using two serial USB devices e.g. Nuvo 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 4444 using [ser2net Linux tool](https://sourceforge.net/projects/ser2net/) (take care, the baud rate is specific to the Nuvo amplifier):
|
* Here is an example of ser2net.conf (for ser2net version < 4) you can use to share your serial port /dev/ttyUSB0 on IP port 4444 using [ser2net Linux tool](https://sourceforge.net/projects/ser2net/) (take care, the baud rate is specific to the Nuvo amplifier):
|
||||||
|
|
||||||
```
|
```
|
||||||
4444:raw:0:/dev/ttyUSB0:57600 8DATABITS NONE 1STOPBIT LOCAL
|
4444:raw:0:/dev/ttyUSB0:57600 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 4444 using [ser2net Linux tool](https://sourceforge.net/projects/ser2net/) (take care, the baud rate is specific to the Nuvo amplifier):
|
||||||
|
|
||||||
|
```
|
||||||
|
connection: &conNuvo
|
||||||
|
accepter: tcp,4444
|
||||||
|
enable: on
|
||||||
|
options:
|
||||||
|
kickolduser: true
|
||||||
|
connector: serialdev,
|
||||||
|
/dev/ttyUSB0,
|
||||||
|
57600n81,local
|
||||||
|
```
|
||||||
|
|
||||||
## Channels
|
## Channels
|
||||||
|
|
||||||
The following channels are available:
|
The following channels are available:
|
||||||
|
|
||||||
| Channel ID | Item Type | Description |
|
| Channel ID | Item Type | Description |
|
||||||
|--------------------------------------|-------------|---------------------------------------------------------------------------------------------------------------|
|
|--------------------------------------|-------------|-----------------------------------------------------------------------------------------------------------------------------|
|
||||||
| system#alloff | Switch | Turn all zones off simultaneously |
|
| system#alloff | Switch | Turn all zones off simultaneously |
|
||||||
| system#allmute | Switch | Mute or unmute all zones 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#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#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#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#volume (where N= 1-20) | Dimmer | Control the volume for a zone (0-100%) [translates to 0-79] |
|
||||||
@ -79,7 +101,7 @@ The following channels are available:
|
|||||||
| 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#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#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#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 amplifiers's Page All Zones feature is activated)|
|
| 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#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 |
|
| 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_line1 (where N= 1-6) | String | 1st line of text being displayed on the keypad. Can be updated for a non NuvoNet source |
|
||||||
@ -89,7 +111,8 @@ The following channels are available:
|
|||||||
| 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#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_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#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 (ReadOnly) |
|
| 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) |
|
||||||
|
|
||||||
## Full Example
|
## Full Example
|
||||||
|
|
||||||
@ -114,6 +137,7 @@ nuvo.items:
|
|||||||
Switch nuvo_system_alloff "All Zones Off" { channel="nuvo:amplifier:myamp:system#alloff" }
|
Switch nuvo_system_alloff "All Zones Off" { channel="nuvo:amplifier:myamp:system#alloff" }
|
||||||
Switch nuvo_system_allmute "All Zones Mute" { channel="nuvo:amplifier:myamp:system#allmute" }
|
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" }
|
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" }
|
||||||
|
|
||||||
// zones
|
// zones
|
||||||
Switch nuvo_z1_power "Power" { channel="nuvo:amplifier:myamp:zone1#power" }
|
Switch nuvo_z1_power "Power" { channel="nuvo:amplifier:myamp:zone1#power" }
|
||||||
@ -141,6 +165,7 @@ String nuvo_s1_play_mode "Play Mode: [%s]" { channel="nuvo:amplifier:myamp:sourc
|
|||||||
Number:Time nuvo_s1_track_length "Track Length: [%s s]" { channel="nuvo:amplifier:myamp:source1#track_length" }
|
Number:Time nuvo_s1_track_length "Track Length: [%s s]" { channel="nuvo:amplifier:myamp:source1#track_length" }
|
||||||
Number:Time nuvo_s1_track_position "Track Position: [%s s]" { channel="nuvo:amplifier:myamp:source1#track_position" }
|
Number:Time nuvo_s1_track_position "Track Position: [%s s]" { channel="nuvo:amplifier:myamp:source1#track_position" }
|
||||||
String nuvo_s1_button_press "Button: [%s]" { channel="nuvo:amplifier:myamp:source1#button_press" }
|
String nuvo_s1_button_press "Button: [%s]" { channel="nuvo:amplifier:myamp:source1#button_press" }
|
||||||
|
// String nuvo_s1_art_url "URL: [%s]" { channel="nuvo:amplifier:myamp:source1#art_url" }
|
||||||
|
|
||||||
String nuvo_s2_display_line1 "Line 1: [%s]" { channel="nuvo:amplifier:myamp:source2#display_line1" }
|
String nuvo_s2_display_line1 "Line 1: [%s]" { channel="nuvo:amplifier:myamp:source2#display_line1" }
|
||||||
String nuvo_s2_display_line2 "Line 2: [%s]" { channel="nuvo:amplifier:myamp:source2#display_line2" }
|
String nuvo_s2_display_line2 "Line 2: [%s]" { channel="nuvo:amplifier:myamp:source2#display_line2" }
|
||||||
@ -150,6 +175,7 @@ String nuvo_s2_play_mode "Play Mode: [%s]" { channel="nuvo:amplifier:myamp:sourc
|
|||||||
Number:Time nuvo_s2_track_length "Track Length: [%s s]" { channel="nuvo:amplifier:myamp:source2#track_length" }
|
Number:Time nuvo_s2_track_length "Track Length: [%s s]" { channel="nuvo:amplifier:myamp:source2#track_length" }
|
||||||
Number:Time nuvo_s2_track_position "Track Position: [%s s]" { channel="nuvo:amplifier:myamp:source2#track_position" }
|
Number:Time nuvo_s2_track_position "Track Position: [%s s]" { channel="nuvo:amplifier:myamp:source2#track_position" }
|
||||||
String nuvo_s2_button_press "Button: [%s]" { channel="nuvo:amplifier:myamp:source2#button_press" }
|
String nuvo_s2_button_press "Button: [%s]" { channel="nuvo:amplifier:myamp:source2#button_press" }
|
||||||
|
// String nuvo_s2_art_url "URL: [%s]" { channel="nuvo:amplifier:myamp:source2#art_url" }
|
||||||
|
|
||||||
String nuvo_s3_display_line1 "Line 1: [%s]" { channel="nuvo:amplifier:myamp:source3#display_line1" }
|
String nuvo_s3_display_line1 "Line 1: [%s]" { channel="nuvo:amplifier:myamp:source3#display_line1" }
|
||||||
String nuvo_s3_display_line2 "Line 2: [%s]" { channel="nuvo:amplifier:myamp:source3#display_line2" }
|
String nuvo_s3_display_line2 "Line 2: [%s]" { channel="nuvo:amplifier:myamp:source3#display_line2" }
|
||||||
@ -159,6 +185,7 @@ String nuvo_s3_play_mode "Play Mode: [%s]" { channel="nuvo:amplifier:myamp:sourc
|
|||||||
Number:Time nuvo_s3_track_length "Track Length: [%s s]" { channel="nuvo:amplifier:myamp:source3#track_length" }
|
Number:Time nuvo_s3_track_length "Track Length: [%s s]" { channel="nuvo:amplifier:myamp:source3#track_length" }
|
||||||
Number:Time nuvo_s3_track_position "Track Position: [%s s]" { channel="nuvo:amplifier:myamp:source3#track_position" }
|
Number:Time nuvo_s3_track_position "Track Position: [%s s]" { channel="nuvo:amplifier:myamp:source3#track_position" }
|
||||||
String nuvo_s3_button_press "Button: [%s]" { channel="nuvo:amplifier:myamp:source3#button_press" }
|
String nuvo_s3_button_press "Button: [%s]" { channel="nuvo:amplifier:myamp:source3#button_press" }
|
||||||
|
// String nuvo_s3_art_url "URL: [%s]" { channel="nuvo:amplifier:myamp:source3#art_url" }
|
||||||
|
|
||||||
String nuvo_s4_display_line1 "Line 1: [%s]" { channel="nuvo:amplifier:myamp:source4#display_line1" }
|
String nuvo_s4_display_line1 "Line 1: [%s]" { channel="nuvo:amplifier:myamp:source4#display_line1" }
|
||||||
String nuvo_s4_display_line2 "Line 2: [%s]" { channel="nuvo:amplifier:myamp:source4#display_line2" }
|
String nuvo_s4_display_line2 "Line 2: [%s]" { channel="nuvo:amplifier:myamp:source4#display_line2" }
|
||||||
@ -168,6 +195,7 @@ String nuvo_s4_play_mode "Play Mode: [%s]" { channel="nuvo:amplifier:myamp:sourc
|
|||||||
Number:Time nuvo_s4_track_length "Track Length: [%s s]" { channel="nuvo:amplifier:myamp:source4#track_length" }
|
Number:Time nuvo_s4_track_length "Track Length: [%s s]" { channel="nuvo:amplifier:myamp:source4#track_length" }
|
||||||
Number:Time nuvo_s4_track_position "Track Position: [%s s]" { channel="nuvo:amplifier:myamp:source4#track_position" }
|
Number:Time nuvo_s4_track_position "Track Position: [%s s]" { channel="nuvo:amplifier:myamp:source4#track_position" }
|
||||||
String nuvo_s4_button_press "Button: [%s]" { channel="nuvo:amplifier:myamp:source4#button_press" }
|
String nuvo_s4_button_press "Button: [%s]" { channel="nuvo:amplifier:myamp:source4#button_press" }
|
||||||
|
// String nuvo_s4_art_url "URL: [%s]" { channel="nuvo:amplifier:myamp:source4#art_url" }
|
||||||
|
|
||||||
String nuvo_s5_display_line1 "Line 1: [%s]" { channel="nuvo:amplifier:myamp:source5#display_line1" }
|
String nuvo_s5_display_line1 "Line 1: [%s]" { channel="nuvo:amplifier:myamp:source5#display_line1" }
|
||||||
String nuvo_s5_display_line2 "Line 2: [%s]" { channel="nuvo:amplifier:myamp:source5#display_line2" }
|
String nuvo_s5_display_line2 "Line 2: [%s]" { channel="nuvo:amplifier:myamp:source5#display_line2" }
|
||||||
@ -177,6 +205,7 @@ String nuvo_s5_play_mode "Play Mode: [%s]" { channel="nuvo:amplifier:myamp:sourc
|
|||||||
Number:Time nuvo_s5_track_length "Track Length: [%s s]" { channel="nuvo:amplifier:myamp:source5#track_length" }
|
Number:Time nuvo_s5_track_length "Track Length: [%s s]" { channel="nuvo:amplifier:myamp:source5#track_length" }
|
||||||
Number:Time nuvo_s5_track_position "Track Position: [%s s]" { channel="nuvo:amplifier:myamp:source5#track_position" }
|
Number:Time nuvo_s5_track_position "Track Position: [%s s]" { channel="nuvo:amplifier:myamp:source5#track_position" }
|
||||||
String nuvo_s5_button_press "Button: [%s]" { channel="nuvo:amplifier:myamp:source5#button_press" }
|
String nuvo_s5_button_press "Button: [%s]" { channel="nuvo:amplifier:myamp:source5#button_press" }
|
||||||
|
// String nuvo_s5_art_url "URL: [%s]" { channel="nuvo:amplifier:myamp:source5#art_url" }
|
||||||
|
|
||||||
String nuvo_s6_display_line1 "Line 1: [%s]" { channel="nuvo:amplifier:myamp:source6#display_line1" }
|
String nuvo_s6_display_line1 "Line 1: [%s]" { channel="nuvo:amplifier:myamp:source6#display_line1" }
|
||||||
String nuvo_s6_display_line2 "Line 2: [%s]" { channel="nuvo:amplifier:myamp:source6#display_line2" }
|
String nuvo_s6_display_line2 "Line 2: [%s]" { channel="nuvo:amplifier:myamp:source6#display_line2" }
|
||||||
@ -186,6 +215,7 @@ String nuvo_s6_play_mode "Play Mode: [%s]" { channel="nuvo:amplifier:myamp:sourc
|
|||||||
Number:Time nuvo_s6_track_length "Track Length: [%s s]" { channel="nuvo:amplifier:myamp:source6#track_length" }
|
Number:Time nuvo_s6_track_length "Track Length: [%s s]" { channel="nuvo:amplifier:myamp:source6#track_length" }
|
||||||
Number:Time nuvo_s6_track_position "Track Position: [%s s]" { channel="nuvo:amplifier:myamp:source6#track_position" }
|
Number:Time nuvo_s6_track_position "Track Position: [%s s]" { channel="nuvo:amplifier:myamp:source6#track_position" }
|
||||||
String nuvo_s6_button_press "Button: [%s]" { channel="nuvo:amplifier:myamp:source6#button_press" }
|
String nuvo_s6_button_press "Button: [%s]" { channel="nuvo:amplifier:myamp:source6#button_press" }
|
||||||
|
// String nuvo_s6_art_url "URL: [%s]" { channel="nuvo:amplifier:myamp:source6#art_url" }
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -205,8 +235,7 @@ sitemap nuvo label="Audio Control" {
|
|||||||
// Volume can be a Setpoint also
|
// Volume can be a Setpoint also
|
||||||
Slider item=nuvo_z1_volume minValue=0 maxValue=100 step=1 visibility=[nuvo_z1_power==ON] icon="soundvolume"
|
Slider item=nuvo_z1_volume minValue=0 maxValue=100 step=1 visibility=[nuvo_z1_power==ON] icon="soundvolume"
|
||||||
Switch item=nuvo_z1_mute visibility=[nuvo_z1_power==ON] icon="soundvolume_mute"
|
Switch item=nuvo_z1_mute visibility=[nuvo_z1_power==ON] icon="soundvolume_mute"
|
||||||
// mappings is optional to override the default dropdown item labels
|
Selection item=nuvo_z1_favorite visibility=[nuvo_z1_power==ON] icon="player"
|
||||||
Selection item=nuvo_z1_favorite visibility=[nuvo_z1_power==ON] icon="player" //mappings=[1="WNYC", 2="BBC One", 3="My Playlist"]
|
|
||||||
Default item=nuvo_z1_control visibility=[nuvo_z1_power==ON]
|
Default item=nuvo_z1_control visibility=[nuvo_z1_power==ON]
|
||||||
|
|
||||||
Text item=nuvo_s1_display_line1 visibility=[nuvo_z1_source=="1"] icon="zoom"
|
Text item=nuvo_s1_display_line1 visibility=[nuvo_z1_source=="1"] icon="zoom"
|
||||||
@ -284,6 +313,8 @@ nuvo.rules:
|
|||||||
```
|
```
|
||||||
import java.text.Normalizer
|
import java.text.Normalizer
|
||||||
|
|
||||||
|
// To be used with a direct serial port or serial over IP connection
|
||||||
|
|
||||||
val actions = getActions("nuvo","nuvo:amplifier:myamp")
|
val actions = getActions("nuvo","nuvo:amplifier:myamp")
|
||||||
|
|
||||||
// send command a custom command to the Nuvo Amplifier
|
// send command a custom command to the Nuvo Amplifier
|
||||||
@ -335,13 +366,13 @@ rule "Load track name for Source 3"
|
|||||||
when
|
when
|
||||||
Item Item_Containing_TrackName changed
|
Item Item_Containing_TrackName changed
|
||||||
then
|
then
|
||||||
// The Nuvo keypad cannot display extended ASCII characters (accent, umulat, etc.)
|
// The Nuvo keypad cannot display extended ASCII characters (accent, umlaut, etc.)
|
||||||
// Below we transform extended ASCII chars into their basic counterparts
|
// Below we transform extended ASCII chars into their basic counterparts
|
||||||
// example: 'La Touché' becomes 'La Touche' and 'Nöel' becomes 'Noel'
|
// example: 'La Touché' becomes 'La Touche' and 'Nöel' becomes 'Noel'
|
||||||
var trackName = Normalizer::normalize(Item_Containing_TrackName.state.toString, Normalizer.Form.NFD).replaceAll("[^\\p{ASCII}]", "")
|
var trackName = Normalizer::normalize(Item_Containing_TrackName.state.toString, Normalizer.Form.NFD).replaceAll("[^\\p{ASCII}]", "")
|
||||||
|
|
||||||
nuvo_s3_display_line4.sendCommand(trackName)
|
sendCommand(nuvo_s3_display_line4, trackName)
|
||||||
nuvo_s3_display_line1.sendCommand("")
|
sendCommand(nuvo_s3_display_line1, "")
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -352,7 +383,7 @@ then
|
|||||||
// fix extended ASCII chars
|
// fix extended ASCII chars
|
||||||
var albumName = Normalizer::normalize(Item_Containing_AlbumName.state.toString, Normalizer.Form.NFD).replaceAll("[^\\p{ASCII}]", "")
|
var albumName = Normalizer::normalize(Item_Containing_AlbumName.state.toString, Normalizer.Form.NFD).replaceAll("[^\\p{ASCII}]", "")
|
||||||
|
|
||||||
nuvo_s3_display_line2.sendCommand(albumName)
|
sendCommand(nuvo_s3_display_line2, albumName)
|
||||||
end
|
end
|
||||||
|
|
||||||
rule "Load artist name for Source 3"
|
rule "Load artist name for Source 3"
|
||||||
@ -362,7 +393,7 @@ then
|
|||||||
// fix extended ASCII chars
|
// fix extended ASCII chars
|
||||||
var artistName = Normalizer::normalize(Item_Containing_ArtistName.state.toString, Normalizer.Form.NFD).replaceAll("[^\\p{ASCII}]", "")
|
var artistName = Normalizer::normalize(Item_Containing_ArtistName.state.toString, Normalizer.Form.NFD).replaceAll("[^\\p{ASCII}]", "")
|
||||||
|
|
||||||
nuvo_s3_display_line3.sendCommand(artistName)
|
sendCommand(nuvo_s3_display_line3, artistName)
|
||||||
end
|
end
|
||||||
|
|
||||||
// In this rule we have three items: Item_Containing_PlayMode, Item_Containing_TrackLength & Item_Containing_TrackPosition
|
// In this rule we have three items: Item_Containing_PlayMode, Item_Containing_TrackLength & Item_Containing_TrackPosition
|
||||||
@ -404,3 +435,221 @@ then
|
|||||||
end
|
end
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### XML Menu Examples
|
||||||
|
|
||||||
|
By using an MPS4 connection to the Nuvo amplifier, it is possible to send custom menus for each source that will be displayed in the physical keypads.
|
||||||
|
When the menu item is selected on the keypad, the text of that menu item will be sent to the button_press channel for the given source.
|
||||||
|
By using rules, it is possible to execute an action on any other openHAB item as a result of selecting a menu item on the physical keypad.
|
||||||
|
|
||||||
|
Below is an example of the XML format that is used to create the menu structure.
|
||||||
|
Up to 10 top menu items can be added. The string inside the text attribute of the topmenu tag will be displayed.
|
||||||
|
Each `<topmenu>` item can have up to 20 `<item>` tags contained within.
|
||||||
|
The topmenu item does not need to have any sub menu items if not desired as seen in the 'Top menu 2' example.
|
||||||
|
A complete XML string for the desired menu is then stored in the `menuXmlSrcN` configuration parameter for a given source and will be loaded into the Nuvo keypads during binding initialization.
|
||||||
|
|
||||||
|
```
|
||||||
|
<topmenu text="Top menu 1">
|
||||||
|
<item>menu1 a</item>
|
||||||
|
<item>menu1 b</item>
|
||||||
|
<item>menu1 c</item>
|
||||||
|
</topmenu>
|
||||||
|
<topmenu text="Top menu 2"/>
|
||||||
|
<topmenu text="Top menu 3">
|
||||||
|
<item>menu3 x</item>
|
||||||
|
<item>menu3 y</item>
|
||||||
|
</topmenu>
|
||||||
|
```
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
### 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.
|
||||||
|
By using these rules, it is possible to have artist, album and track names displayed on the keypad, transport button presses from the keypad relayed to the source, and album art displayed if using a Nuvo CTP-36 keypad.
|
||||||
|
Global Favorites selection and Menu selections from the custom menus described above are also processed by these rules.
|
||||||
|
The list of favorite names should be playable via another thing connected to openHAB and this thing should have a means to accept a text string that tells it to play a particular favorite/playlist.
|
||||||
|
|
||||||
|
nuvo-advanced.rules:
|
||||||
|
|
||||||
|
```
|
||||||
|
import java.text.Normalizer
|
||||||
|
|
||||||
|
// all examples using Source 6
|
||||||
|
var source = "S6"
|
||||||
|
|
||||||
|
var artistName = ""
|
||||||
|
var albumName = ""
|
||||||
|
var trackName = ""
|
||||||
|
|
||||||
|
// supportedactions bitmask tells the keypad what buttons to display
|
||||||
|
// detailed in SourceCommunicationProtocolForNNA_v1.0.pdf
|
||||||
|
// 0 : play/pause only
|
||||||
|
// 196615 : play/pause/skip
|
||||||
|
// 196639 : play/pause/skip/shuffle/repeat
|
||||||
|
// 245791 : play/pause/skip/shuffle/repeat/thumbsup/thumbsdown
|
||||||
|
var supportedActionsMask = "196639"
|
||||||
|
|
||||||
|
// a very basic example to display text on all 4 lines and load an example image as album art
|
||||||
|
rule "Basic keypad communication example rule"
|
||||||
|
when
|
||||||
|
System started
|
||||||
|
then
|
||||||
|
sendCommand(nuvo_system_sendcmd, source + "DISPLINES0,0,0,\"Hello World\",\"Welcome to openHAB!\",\"Example Text\",\"Displayed On Keypad\"")
|
||||||
|
sendCommand(nuvo_system_sendcmd, source + "DISPINFOTWO0,0,1,albumartid,2,1,0")
|
||||||
|
sendCommand(nuvo_s6_art_url, "https://icon-library.com/images/sample-icon/sample-icon-22.jpg")
|
||||||
|
end
|
||||||
|
|
||||||
|
rule "Music Source nuvo button press"
|
||||||
|
when
|
||||||
|
Item nuvo_s6_button_press received update
|
||||||
|
then
|
||||||
|
var button = nuvo_s6_button_press.state.toString()
|
||||||
|
|
||||||
|
// If a favorite is selected it will be prepended for easier identification from other buttons
|
||||||
|
// ie: 'PLAY_MUSIC_PRESET:Rock'
|
||||||
|
if (button.startsWith("PLAY_MUSIC_PRESET:")) {
|
||||||
|
sendCommand(music_Music_PlayFavorite, button.replace("PLAY_MUSIC_PRESET:", ""))
|
||||||
|
} else {
|
||||||
|
// these proxy the Nuvo button presses to the appropriate Music Source button press
|
||||||
|
switch button {
|
||||||
|
case "PLAYPAUSE": {
|
||||||
|
sendCommand(music_Music_Control, PAUSE)
|
||||||
|
}
|
||||||
|
case "NEXT": {
|
||||||
|
sendCommand(music_Music_Control, NEXT)
|
||||||
|
}
|
||||||
|
case "PREV": {
|
||||||
|
sendCommand(music_Music_Control, PREVIOUS)
|
||||||
|
}
|
||||||
|
case "SHUFFLETOGGLE": {
|
||||||
|
if (music_Music_Random.state == ON) {
|
||||||
|
sendCommand(music_Music_Random, OFF)
|
||||||
|
} else {
|
||||||
|
sendCommand(music_Music_Random, ON)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "REPEATTOGGLE": {
|
||||||
|
if (music_Music_Repeat.state == ON) {
|
||||||
|
sendCommand(music_Music_Repeat, OFF)
|
||||||
|
} else {
|
||||||
|
sendCommand(music_Music_Repeat, ON)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Handle menu item selections
|
||||||
|
case "Top menu 1|menu1 a": {
|
||||||
|
logInfo("nuvo src 6", "'Top menu 1, menu 1 a' was selected")
|
||||||
|
}
|
||||||
|
case "Top menu 1|menu1 b": {
|
||||||
|
logInfo("nuvo src 6", "'Top menu 1, menu 1 b' was selected")
|
||||||
|
}
|
||||||
|
case "Top menu 2": {
|
||||||
|
logInfo("nuvo src 6", "'Top menu 2' was selected")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
rule "Music Source load album art URL to Nuvo Source 6"
|
||||||
|
when
|
||||||
|
Item music_Detail_CoverUrl changed // an item that gets updated with the cover art url
|
||||||
|
then
|
||||||
|
// when the CoverUrl changes, pass the new JPG image url to the Nuvo binding
|
||||||
|
// the binding automatically downloads the JPG and converts it to a format that can be displayed on the CTP-36
|
||||||
|
// smaller images will yield better performance when the binding resizes the image to 80 x 80 pixels
|
||||||
|
// note that the CTP-36 keypad may crash/reboot if it receives an invalid image
|
||||||
|
sendCommand(nuvo_s6_art_url, music_Detail_CoverUrl.state.toString)
|
||||||
|
end
|
||||||
|
|
||||||
|
// if album, artist and track names are maintained in different items, these three rules are necessary
|
||||||
|
// if the names can be received in one item, then this can condense to one rule sending the lines in one DISPLINES message
|
||||||
|
// the names can be up to 80 characters and should have any embedded double quotes removed
|
||||||
|
rule "Music Source update album name"
|
||||||
|
when
|
||||||
|
Item music_Music_Album received update
|
||||||
|
then
|
||||||
|
if (music_Music_Album.state.toString() != "") {
|
||||||
|
albumName = Normalizer::normalize(music_Music_Album.state.toString, Normalizer.Form.NFD).replaceAll("[^\\p{ASCII}]", "")
|
||||||
|
sendCommand(nuvo_system_sendcmd, source + "DISPLINES0,0,0,\"\",\"" + albumName + "\",\"" + artistName + "\",\"" + trackName + "\"")
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
rule "Music Source update artist name"
|
||||||
|
when
|
||||||
|
Item music_Music_Artist received update
|
||||||
|
then
|
||||||
|
if (music_Music_Artist.state.toString() != "") {
|
||||||
|
artistName = Normalizer::normalize(music_Music_Artist.state.toString, Normalizer.Form.NFD).replaceAll("[^\\p{ASCII}]", "")
|
||||||
|
sendCommand(nuvo_system_sendcmd, source + "DISPLINES0,0,0,\"\",\"" + albumName + "\",\"" + artistName + "\",\"" + trackName + "\"")
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
rule "Music Source update track name"
|
||||||
|
when
|
||||||
|
Item music_Music_Track received update
|
||||||
|
then
|
||||||
|
if (music_Music_Track.state.toString() != "") {
|
||||||
|
trackName = Normalizer::normalize(music_Music_Track.state.toString, Normalizer.Form.NFD).replaceAll("[^\\p{ASCII}]", "")
|
||||||
|
sendCommand(nuvo_system_sendcmd, source + "DISPLINES0,0,0,\"\",\"" + albumName + "\",\"" + artistName + "\",\"" + trackName + "\"")
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
rule "Music Source update song elapsed time"
|
||||||
|
when
|
||||||
|
Item music_Music_TrackPosition received update or
|
||||||
|
Item music_Music_Random received update or
|
||||||
|
Item music_Music_Repeat received update
|
||||||
|
then
|
||||||
|
var int trackLength = Integer::parseInt(music_Music_TrackLength.state.toString.replaceAll("[\\D]", "")) * 10
|
||||||
|
// track position should not update continuously to prevent excessive amounts of DISPINFOTWO messages from being sent
|
||||||
|
// the keypad counts up the time on its own after a DISPINFOTWO message is received
|
||||||
|
var int trackPosition = Integer::parseInt(music_Music_TrackPosition.state.toString.replaceAll("[\\D]", "")) * 10
|
||||||
|
var playState = music_Music_PlayMode.state.toString()
|
||||||
|
var randomMode = music_Music_Random.state
|
||||||
|
var repeatMode = music_Music_Repeat.state
|
||||||
|
|
||||||
|
// the source status mask tells the keypad the button states to display
|
||||||
|
// sourcestatus masks for play and pause when random and repeat are both off
|
||||||
|
var playMask = "2"
|
||||||
|
var pauseMask = "4"
|
||||||
|
|
||||||
|
if (randomMode == ON && repeatMode == OFF) {
|
||||||
|
playMask = "34"
|
||||||
|
pauseMask = "36"
|
||||||
|
} else if (randomMode == OFF && repeatMode == ON) {
|
||||||
|
playMask = "66"
|
||||||
|
pauseMask = "68"
|
||||||
|
} else if (randomMode == ON && repeatMode == ON) {
|
||||||
|
playMask = "98"
|
||||||
|
pauseMask = "100"
|
||||||
|
}
|
||||||
|
|
||||||
|
// DISPINFOTWO sends track time, play state, album art id, source status, etc. all in one command message
|
||||||
|
//*SsDISPINFOTWOduration,position,deprecatedstatus,albumartid,sourcemode,sourcestatus,supportedactions
|
||||||
|
|
||||||
|
// The binding will automatically substitute the 'albumartid' token with the id of the JPG processed by the `art_url` channel
|
||||||
|
if (playState == "Playing") {
|
||||||
|
// first '2' indicates deprecatedstatus = playing, second '2' is sourcemode = Music Server Mode
|
||||||
|
// The Nuvo keypad will now begin counting up the elapsed time displayed (starting from trackPosition)
|
||||||
|
// The elapsed time may reset on randomMode & repeatMode toggles unless a current trackPosition is also sent
|
||||||
|
sendCommand(nuvo_system_sendcmd, source + "DISPINFOTWO" + trackLength.toString() + "," + trackPosition.toString() + ",2,albumartid,2," + playMask + "," + supportedActionsMask)
|
||||||
|
}
|
||||||
|
if (playState == "Paused") {
|
||||||
|
sendCommand(nuvo_system_sendcmd, source + "DISPINFOTWO" + trackLength.toString() + "," + trackPosition.toString() + ",3,albumartid,2," + pauseMask + "," + supportedActionsMask)
|
||||||
|
}
|
||||||
|
if (playState == "Stopped") {
|
||||||
|
// send '0x0' instead of 'albumartid' since no art should be displayed while stopped
|
||||||
|
sendCommand(nuvo_system_sendcmd, source + "DISPINFOTWO0,0,1,0x0,2,1,0")
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
rule "Music Source update song playing info - stopped"
|
||||||
|
when
|
||||||
|
Item music_Music_PlayMode changed to "Stopped"
|
||||||
|
then
|
||||||
|
sendCommand(nuvo_system_sendcmd, source + "DISPLINES0,0,0,\"\",\"Nothing Playing\",\"\",\"\"")
|
||||||
|
end
|
||||||
|
|
||||||
|
```
|
||||||
|
31
bundles/org.openhab.binding.nuvo/doc/NuvoMenu.xsd
Normal file
31
bundles/org.openhab.binding.nuvo/doc/NuvoMenu.xsd
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Created with Liquid Technologies Online Tools 1.0 (https://www.liquid-technologies.com) -->
|
||||||
|
<!-- To regenerate the NuvoMenu DTO from the XSD, use the following command: -->
|
||||||
|
<!-- xjc -d ../src/main/java -p org.openhab.binding.nuvo.internal.dto NuvoMenu.xsd -->
|
||||||
|
<xs:schema attributeFormDefault="unqualified" elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:jaxb="http://java.sun.com/xml/ns/jaxb" jaxb:version="2.1">
|
||||||
|
<xs:element name="menu">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:appinfo>
|
||||||
|
<jaxb:class name="NuvoMenu"/>
|
||||||
|
</xs:appinfo>
|
||||||
|
</xs:annotation>
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:sequence>
|
||||||
|
<xs:element maxOccurs="unbounded" name="source">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:sequence minOccurs="0">
|
||||||
|
<xs:element maxOccurs="unbounded" name="topmenu">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:sequence minOccurs="0">
|
||||||
|
<xs:element maxOccurs="unbounded" name="item" type="xs:string" />
|
||||||
|
</xs:sequence>
|
||||||
|
<xs:attribute name="text" type="xs:string" use="required" />
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
</xs:sequence>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
</xs:sequence>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
</xs:schema>
|
@ -61,6 +61,7 @@ public class NuvoBindingConstants {
|
|||||||
public static final String CHANNEL_TRACK_LENGTH = "track_length";
|
public static final String CHANNEL_TRACK_LENGTH = "track_length";
|
||||||
public static final String CHANNEL_TRACK_POSITION = "track_position";
|
public static final String CHANNEL_TRACK_POSITION = "track_position";
|
||||||
public static final String CHANNEL_BUTTON_PRESS = "button_press";
|
public static final String CHANNEL_BUTTON_PRESS = "button_press";
|
||||||
|
public static final String CHANNEL_ART_URL = "art_url";
|
||||||
|
|
||||||
// Message types
|
// Message types
|
||||||
public static final String TYPE_VERSION = "version";
|
public static final String TYPE_VERSION = "version";
|
||||||
@ -70,20 +71,41 @@ public class NuvoBindingConstants {
|
|||||||
public static final String TYPE_SOURCE_UPDATE = "source_update";
|
public static final String TYPE_SOURCE_UPDATE = "source_update";
|
||||||
public static final String TYPE_ZONE_UPDATE = "zone_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_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_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";
|
||||||
|
|
||||||
// misc
|
// misc
|
||||||
public static final String ON = "ON";
|
public static final String ON = "ON";
|
||||||
public static final String OFF = "OFF";
|
public static final String OFF = "OFF";
|
||||||
|
public static final String TWO = "2";
|
||||||
public static final String ONE = "1";
|
public static final String ONE = "1";
|
||||||
public static final String ZERO = "0";
|
public static final String ZERO = "0";
|
||||||
public static final String BLANK = "";
|
public static final String BLANK = "";
|
||||||
|
public static final String SPACE = " ";
|
||||||
|
public static final String COMMA = ",";
|
||||||
public static final String DISPLINE = "DISPLINE";
|
public static final String DISPLINE = "DISPLINE";
|
||||||
public static final String DISPINFO = "DISPINFO,"; // yes comma here
|
public static final String DISPINFO = "DISPINFO,"; // yes comma here
|
||||||
|
public static final String DISP_INFO_TWO = "DISPINFOTWO";
|
||||||
public static final String NAME_QUOTE = "NAME\"";
|
public static final String NAME_QUOTE = "NAME\"";
|
||||||
public static final String MUTE = "MUTE";
|
public static final String MUTE = "MUTE";
|
||||||
public static final String VOL = "VOL";
|
public static final String VOL = "VOL";
|
||||||
|
public static final String OFFSET_ZERO = "0x";
|
||||||
|
public static final String ZERO_COMMA = "0,0";
|
||||||
|
|
||||||
// MPS4
|
// MPS4
|
||||||
public static final String TYPE_PING = "PING";
|
public static final String TYPE_PING = "PING";
|
||||||
|
public static final String TYPE_RESTART = "RESTART";
|
||||||
|
public static final String DISABLE = "disable";
|
||||||
|
public static final String ALBUM_ART_ID = "albumartid";
|
||||||
|
public static final String SRC_KEY = "S";
|
||||||
|
public static final String ZONE_KEY = "Z";
|
||||||
|
public static final String ALBUM_ART_AVAILABLE = "ALBUMARTAVAILABLE";
|
||||||
|
public static final String ALBUM_ART_FRAG = "ALBUMARTFRAG";
|
||||||
|
public static final String HTTP = "http://";
|
||||||
|
public static final String HTTPS = "https://";
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,9 @@ import java.util.Set;
|
|||||||
|
|
||||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
import org.eclipse.jdt.annotation.Nullable;
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
|
import org.eclipse.jetty.client.HttpClient;
|
||||||
import org.openhab.binding.nuvo.internal.handler.NuvoHandler;
|
import org.openhab.binding.nuvo.internal.handler.NuvoHandler;
|
||||||
|
import org.openhab.core.io.net.http.HttpClientFactory;
|
||||||
import org.openhab.core.io.transport.serial.SerialPortManager;
|
import org.openhab.core.io.transport.serial.SerialPortManager;
|
||||||
import org.openhab.core.thing.Thing;
|
import org.openhab.core.thing.Thing;
|
||||||
import org.openhab.core.thing.ThingTypeUID;
|
import org.openhab.core.thing.ThingTypeUID;
|
||||||
@ -46,6 +48,8 @@ public class NuvoHandlerFactory extends BaseThingHandlerFactory {
|
|||||||
|
|
||||||
private final NuvoStateDescriptionOptionProvider stateDescriptionProvider;
|
private final NuvoStateDescriptionOptionProvider stateDescriptionProvider;
|
||||||
|
|
||||||
|
private final HttpClient httpClient;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
|
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
|
||||||
return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
|
return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
|
||||||
@ -53,9 +57,11 @@ public class NuvoHandlerFactory extends BaseThingHandlerFactory {
|
|||||||
|
|
||||||
@Activate
|
@Activate
|
||||||
public NuvoHandlerFactory(final @Reference NuvoStateDescriptionOptionProvider provider,
|
public NuvoHandlerFactory(final @Reference NuvoStateDescriptionOptionProvider provider,
|
||||||
final @Reference SerialPortManager serialPortManager) {
|
final @Reference SerialPortManager serialPortManager,
|
||||||
|
final @Reference HttpClientFactory httpClientFactory) {
|
||||||
this.stateDescriptionProvider = provider;
|
this.stateDescriptionProvider = provider;
|
||||||
this.serialPortManager = serialPortManager;
|
this.serialPortManager = serialPortManager;
|
||||||
|
this.httpClient = httpClientFactory.getCommonHttpClient();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -63,7 +69,7 @@ public class NuvoHandlerFactory extends BaseThingHandlerFactory {
|
|||||||
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
|
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
|
||||||
|
|
||||||
if (SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID)) {
|
if (SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID)) {
|
||||||
return new NuvoHandler(thing, stateDescriptionProvider, serialPortManager);
|
return new NuvoHandler(thing, stateDescriptionProvider, serialPortManager, httpClient);
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
@ -41,19 +41,35 @@ public abstract class NuvoConnector {
|
|||||||
private static final String BEGIN_CMD = "*";
|
private static final String BEGIN_CMD = "*";
|
||||||
private static final String END_CMD = "\r";
|
private static final String END_CMD = "\r";
|
||||||
private static final String QUERY = "?";
|
private static final String QUERY = "?";
|
||||||
private static final String VER_STR = "#VER\"NV-";
|
private static final String VER_STR_E6 = "#VER\"NV-E6G";
|
||||||
|
private static final String VER_STR_GC = "#VER\"NV-I8G";
|
||||||
private static final String ALL_OFF = "#ALLOFF";
|
private static final String ALL_OFF = "#ALLOFF";
|
||||||
private static final String MUTE = "#MUTE";
|
private static final String MUTE = "#MUTE";
|
||||||
private static final String PAGE = "#PAGE";
|
private static final String PAGE = "#PAGE";
|
||||||
|
private static final String RESTART = "#RESTART\"NuVoNet\"";
|
||||||
private static final String PING = "#PING";
|
private static final String PING = "#PING";
|
||||||
|
private static final String PING_RESPONSE = "PING";
|
||||||
|
|
||||||
private static final byte[] WAKE_STR = "\r".getBytes(StandardCharsets.US_ASCII);
|
private static final byte[] WAKE_STR = "\r".getBytes(StandardCharsets.US_ASCII);
|
||||||
|
|
||||||
private static final Pattern SRC_PATTERN = Pattern.compile("^#S(\\d{1})(.*)$");
|
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_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_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_CFG_PATTERN = Pattern.compile("^#ZCFG(\\d{1,2}),(.*)$");
|
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(.*)$");
|
||||||
|
|
||||||
|
// S2ALBUMARTFRAGREQ0x620FD879,0,750
|
||||||
|
private static final Pattern ALBUM_ART_FRAG_REQ = Pattern.compile("^#S(\\d{1})ALBUMARTFRAGREQ(.*)$");
|
||||||
|
|
||||||
|
// S6FAVORITE0x000003E8
|
||||||
|
private static final Pattern FAVORITE_PATTERN = Pattern.compile("^#S(\\d{1})FAVORITE0x(.*)$");
|
||||||
|
|
||||||
private final Logger logger = LoggerFactory.getLogger(NuvoConnector.class);
|
private final Logger logger = LoggerFactory.getLogger(NuvoConnector.class);
|
||||||
|
|
||||||
protected static final String COMMAND_ERROR = "#?";
|
protected static final String COMMAND_ERROR = "#?";
|
||||||
@ -306,11 +322,21 @@ public abstract class NuvoConnector {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (message.contains(PING)) {
|
if (message.contains(PING)) {
|
||||||
|
try {
|
||||||
|
sendCommand(PING_RESPONSE);
|
||||||
|
} catch (NuvoException e) {
|
||||||
|
logger.debug("Error sending response to PING command");
|
||||||
|
}
|
||||||
dispatchKeyValue(TYPE_PING, BLANK, BLANK);
|
dispatchKeyValue(TYPE_PING, BLANK, BLANK);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (message.contains(VER_STR)) {
|
if (RESTART.equals(message)) {
|
||||||
|
dispatchKeyValue(TYPE_RESTART, BLANK, BLANK);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (message.contains(VER_STR_E6) || message.contains(VER_STR_GC)) {
|
||||||
// example: #VER"NV-E6G FWv2.66 HWv0"
|
// example: #VER"NV-E6G FWv2.66 HWv0"
|
||||||
// split on " and return the version number
|
// split on " and return the version number
|
||||||
dispatchKeyValue(TYPE_VERSION, "", message.split("\"")[1]);
|
dispatchKeyValue(TYPE_VERSION, "", message.split("\"")[1]);
|
||||||
@ -332,16 +358,40 @@ public abstract class NuvoConnector {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Amp controller send a source update ie: #S2DISPINFO,DUR3380,POS3090,STATUS2
|
// 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 source update ie: #S2DISPINFO,DUR3380,POS3090,STATUS2
|
||||||
// or #S2DISPLINE1,"1 of 17"
|
// or #S2DISPLINE1,"1 of 17"
|
||||||
Matcher matcher = SRC_PATTERN.matcher(message);
|
matcher = SRC_PATTERN.matcher(message);
|
||||||
if (matcher.find()) {
|
if (matcher.find()) {
|
||||||
// pull out the source id and the remainder of the message
|
// 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, matcher.group(1), matcher.group(2));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Amp controller send a zone update ie: #Z11,ON,SRC3,VOL63,DND0,LOCK0
|
// Amp controller sent a zone update ie: #Z11,ON,SRC3,VOL63,DND0,LOCK0
|
||||||
matcher = ZONE_PATTERN.matcher(message);
|
matcher = ZONE_PATTERN.matcher(message);
|
||||||
if (matcher.find()) {
|
if (matcher.find()) {
|
||||||
// pull out the zone id and the remainder of the message
|
// pull out the zone id and the remainder of the message
|
||||||
@ -349,7 +399,43 @@ public abstract class NuvoConnector {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Amp controller send a zone button press event ie: #Z11S3PLAYPAUSE
|
// 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]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Amp controller sent a menu request event ie: #Z2S6MENUREQ0x0000000B,1,0,0
|
||||||
|
matcher = ZONE_MENUREQ_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);
|
matcher = ZONE_BUTTON_PATTERN.matcher(message);
|
||||||
if (matcher.find()) {
|
if (matcher.find()) {
|
||||||
// pull out the source id and the remainder of the message, ignore the zone id
|
// pull out the source id and the remainder of the message, ignore the zone id
|
||||||
@ -357,7 +443,7 @@ public abstract class NuvoConnector {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Amp controller send a zone configuration response ie: #ZCFG1,BASS1,TREB-2,BALR2,LOUDCMP1
|
// Amp controller sent a zone configuration response ie: #ZCFG1,BASS1,TREB-2,BALR2,LOUDCMP1
|
||||||
matcher = ZONE_CFG_PATTERN.matcher(message);
|
matcher = ZONE_CFG_PATTERN.matcher(message);
|
||||||
if (matcher.find()) {
|
if (matcher.find()) {
|
||||||
// pull out the zone id and the remainder of the message
|
// pull out the zone id and the remainder of the message
|
||||||
|
@ -0,0 +1,63 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2022 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.nuvo.internal.communication;
|
||||||
|
|
||||||
|
import java.awt.AlphaComposite;
|
||||||
|
import java.awt.Graphics2D;
|
||||||
|
import java.awt.Image;
|
||||||
|
import java.awt.RenderingHints;
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import javax.imageio.ImageIO;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link NuvoImageResizer} class contains methods for re-sizing album art
|
||||||
|
*
|
||||||
|
* @author Michael Lobstein - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class NuvoImageResizer {
|
||||||
|
private static final String EXTENSION_JPG = "jpg";
|
||||||
|
private static final byte[] NO_IMAGE = { 0 };
|
||||||
|
|
||||||
|
public static byte[] resizeImage(byte[] inputImage, int width, int height) {
|
||||||
|
try {
|
||||||
|
BufferedImage image = ImageIO.read(new ByteArrayInputStream(inputImage));
|
||||||
|
Image originalImage = image.getScaledInstance(width, height, Image.SCALE_DEFAULT);
|
||||||
|
|
||||||
|
int type = ((image.getType() == 0) ? BufferedImage.TYPE_INT_ARGB : image.getType());
|
||||||
|
BufferedImage resizedImage = new BufferedImage(width, height, type);
|
||||||
|
|
||||||
|
Graphics2D g2d = resizedImage.createGraphics();
|
||||||
|
|
||||||
|
g2d.setComposite(AlphaComposite.Src);
|
||||||
|
g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
|
||||||
|
g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
|
||||||
|
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
||||||
|
|
||||||
|
g2d.drawImage(originalImage, 0, 0, width, height, null);
|
||||||
|
g2d.dispose();
|
||||||
|
|
||||||
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
|
ImageIO.write(resizedImage, EXTENSION_JPG, baos);
|
||||||
|
return baos.toByteArray();
|
||||||
|
} catch (IOException e) {
|
||||||
|
return NO_IMAGE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -12,7 +12,8 @@
|
|||||||
*/
|
*/
|
||||||
package org.openhab.binding.nuvo.internal.communication;
|
package org.openhab.binding.nuvo.internal.communication;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import static java.util.Map.entry;
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
@ -32,22 +33,21 @@ public class NuvoStatusCodes {
|
|||||||
private static final String ZERO = "0";
|
private static final String ZERO = "0";
|
||||||
|
|
||||||
// map to lookup play mode
|
// map to lookup play mode
|
||||||
public static final Map<String, String> PLAY_MODE = new HashMap<>();
|
public static final Map<String, String> PLAY_MODE = Map.ofEntries(entry("0", "Normal"), entry("1", "Idle"),
|
||||||
static {
|
entry("2", "Playing"), entry("3", "Paused"), entry("4", "Fast Forward"), entry("5", "Rewind"),
|
||||||
PLAY_MODE.put("0", "Normal");
|
entry("6", "Play Shuffle"), entry("7", "Play Repeat"), entry("8", "Play Shuffle Repeat"),
|
||||||
PLAY_MODE.put("1", "Idle");
|
entry("9", "Step Tune"), entry("10", "Seek Tune"), entry("11", "Preset Tune"), entry("12", "unknown-12"));
|
||||||
PLAY_MODE.put("2", "Playing");
|
|
||||||
PLAY_MODE.put("3", "Paused");
|
// map to lookup button action name from NuvoNet button code
|
||||||
PLAY_MODE.put("4", "Fast Forward");
|
public static final Map<String, String> BUTTON_CODE = Map.ofEntries(entry("1", "OK"), entry("2", "PLAYPAUSE"),
|
||||||
PLAY_MODE.put("5", "Rewind");
|
entry("3", "PREV"), entry("4", "NEXT"), entry("5", "POWERMUTE"), // source will not receive this
|
||||||
PLAY_MODE.put("6", "Play Shuffle");
|
entry("6", "UP"), // source will not receive this
|
||||||
PLAY_MODE.put("7", "Play Repeat");
|
entry("7", "DOWN"), // source will not receive this
|
||||||
PLAY_MODE.put("8", "Play Shuffle Repeat");
|
entry("41", "DISCRETEPLAYPAUSE"), entry("42", "DISCRETENEXTTRACK"), entry("43", "DISCRETEPREVIOUSTRACK"),
|
||||||
PLAY_MODE.put("9", "Step Tune");
|
entry("44", "SHUFFLETOGGLE"), entry("45", "REPEATTOGGLE"), entry("46", "TUNEUP"), entry("47", "TUNEDOWN"),
|
||||||
PLAY_MODE.put("10", "Seek Tune");
|
entry("48", "SEEKUP"), entry("49", "SEEKDOWN"), entry("50", "PRESETUP"), entry("51", "PRESETDOWN"),
|
||||||
PLAY_MODE.put("11", "Preset Tune");
|
entry("52", "DIRECTFREQUENCYENTRY"), entry("53", "DIRECTPRESETENTRY"), entry("54", "NEXTBAND"),
|
||||||
PLAY_MODE.put("12", "unknown-12");
|
entry("55", "THUMBSUP"), entry("56", "THUMBSDOWN"));
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* This looks broken because the controller is seriously broken...
|
* This looks broken because the controller is seriously broken...
|
||||||
|
@ -28,4 +28,29 @@ public class NuvoThingConfiguration {
|
|||||||
public @Nullable Integer port;
|
public @Nullable Integer port;
|
||||||
public @Nullable Integer numZones;
|
public @Nullable Integer numZones;
|
||||||
public boolean clockSync;
|
public boolean clockSync;
|
||||||
|
public String favoriteLabels = "";
|
||||||
|
public Integer nuvoNetSrc1 = 0;
|
||||||
|
public Integer nuvoNetSrc2 = 0;
|
||||||
|
public Integer nuvoNetSrc3 = 0;
|
||||||
|
public Integer nuvoNetSrc4 = 0;
|
||||||
|
public Integer nuvoNetSrc5 = 0;
|
||||||
|
public Integer nuvoNetSrc6 = 0;
|
||||||
|
public String favoritesSrc1 = "";
|
||||||
|
public String favoritesSrc2 = "";
|
||||||
|
public String favoritesSrc3 = "";
|
||||||
|
public String favoritesSrc4 = "";
|
||||||
|
public String favoritesSrc5 = "";
|
||||||
|
public String favoritesSrc6 = "";
|
||||||
|
public String favPrefix1 = "";
|
||||||
|
public String favPrefix2 = "";
|
||||||
|
public String favPrefix3 = "";
|
||||||
|
public String favPrefix4 = "";
|
||||||
|
public String favPrefix5 = "";
|
||||||
|
public String favPrefix6 = "";
|
||||||
|
public String menuXmlSrc1 = "";
|
||||||
|
public String menuXmlSrc2 = "";
|
||||||
|
public String menuXmlSrc3 = "";
|
||||||
|
public String menuXmlSrc4 = "";
|
||||||
|
public String menuXmlSrc5 = "";
|
||||||
|
public String menuXmlSrc6 = "";
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,52 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2022 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.nuvo.internal.dto;
|
||||||
|
|
||||||
|
import javax.xml.bind.JAXBContext;
|
||||||
|
import javax.xml.bind.JAXBException;
|
||||||
|
import javax.xml.stream.XMLInputFactory;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implementation for a static use of JAXBContext as singleton instance.
|
||||||
|
*
|
||||||
|
* @author Michael Lobstein - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class JAXBUtils {
|
||||||
|
|
||||||
|
private static final Logger LOGGER = LoggerFactory.getLogger(JAXBUtils.class);
|
||||||
|
|
||||||
|
public static final @Nullable JAXBContext JAXBCONTEXT_NUVO_MENU = initJAXBContextNuvoMenu();
|
||||||
|
public static final XMLInputFactory XMLINPUTFACTORY = initXMLInputFactory();
|
||||||
|
|
||||||
|
private static @Nullable JAXBContext initJAXBContextNuvoMenu() {
|
||||||
|
try {
|
||||||
|
return JAXBContext.newInstance(NuvoMenu.class);
|
||||||
|
} catch (JAXBException e) {
|
||||||
|
LOGGER.error("Exception creating JAXBContext for nuvo menu: {}", e.getLocalizedMessage(), e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static XMLInputFactory initXMLInputFactory() {
|
||||||
|
XMLInputFactory xif = XMLInputFactory.newInstance();
|
||||||
|
xif.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, false);
|
||||||
|
xif.setProperty(XMLInputFactory.SUPPORT_DTD, false);
|
||||||
|
return xif;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,75 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2022 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.nuvo.internal.dto;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import javax.xml.bind.annotation.XmlAccessType;
|
||||||
|
import javax.xml.bind.annotation.XmlAccessorType;
|
||||||
|
import javax.xml.bind.annotation.XmlAttribute;
|
||||||
|
import javax.xml.bind.annotation.XmlElement;
|
||||||
|
import javax.xml.bind.annotation.XmlRootElement;
|
||||||
|
import javax.xml.bind.annotation.XmlType;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a Java object tree that represents the Nuvo keypad menu structure defined by the user
|
||||||
|
*
|
||||||
|
* @author Michael Lobstein - Initial contribution
|
||||||
|
*/
|
||||||
|
|
||||||
|
@NonNullByDefault
|
||||||
|
@XmlAccessorType(XmlAccessType.FIELD)
|
||||||
|
@XmlType(name = "")
|
||||||
|
@XmlRootElement(name = "menu")
|
||||||
|
public class NuvoMenu {
|
||||||
|
|
||||||
|
@XmlElement(required = true)
|
||||||
|
protected List<NuvoMenu.Source> source = new ArrayList<NuvoMenu.Source>();
|
||||||
|
|
||||||
|
public List<NuvoMenu.Source> getSource() {
|
||||||
|
return this.source;
|
||||||
|
}
|
||||||
|
|
||||||
|
@XmlAccessorType(XmlAccessType.FIELD)
|
||||||
|
@XmlType(name = "")
|
||||||
|
public static class Source {
|
||||||
|
protected List<NuvoMenu.Source.TopMenu> topmenu = new ArrayList<NuvoMenu.Source.TopMenu>();
|
||||||
|
|
||||||
|
public List<NuvoMenu.Source.TopMenu> getTopMenu() {
|
||||||
|
return this.topmenu;
|
||||||
|
}
|
||||||
|
|
||||||
|
@XmlAccessorType(XmlAccessType.FIELD)
|
||||||
|
@XmlType(name = "")
|
||||||
|
public static class TopMenu {
|
||||||
|
protected List<String> item = new ArrayList<String>();
|
||||||
|
@XmlAttribute(name = "text", required = true)
|
||||||
|
protected String text = "";
|
||||||
|
|
||||||
|
public List<String> getItems() {
|
||||||
|
return this.item;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getText() {
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setText(String value) {
|
||||||
|
this.text = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -12,20 +12,27 @@
|
|||||||
*/
|
*/
|
||||||
package org.openhab.binding.nuvo.internal.handler;
|
package org.openhab.binding.nuvo.internal.handler;
|
||||||
|
|
||||||
|
import static org.eclipse.jetty.http.HttpMethod.GET;
|
||||||
|
import static org.eclipse.jetty.http.HttpStatus.OK_200;
|
||||||
import static org.openhab.binding.nuvo.internal.NuvoBindingConstants.*;
|
import static org.openhab.binding.nuvo.internal.NuvoBindingConstants.*;
|
||||||
|
|
||||||
|
import java.io.StringReader;
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Base64;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.TreeMap;
|
import java.util.TreeMap;
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
import java.util.concurrent.ScheduledFuture;
|
import java.util.concurrent.ScheduledFuture;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.TimeoutException;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
@ -33,9 +40,16 @@ import java.util.stream.IntStream;
|
|||||||
|
|
||||||
import javax.measure.Unit;
|
import javax.measure.Unit;
|
||||||
import javax.measure.quantity.Time;
|
import javax.measure.quantity.Time;
|
||||||
|
import javax.xml.bind.JAXBContext;
|
||||||
|
import javax.xml.bind.JAXBException;
|
||||||
|
import javax.xml.bind.Unmarshaller;
|
||||||
|
import javax.xml.stream.XMLStreamException;
|
||||||
|
import javax.xml.stream.XMLStreamReader;
|
||||||
|
|
||||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
import org.eclipse.jdt.annotation.Nullable;
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
|
import org.eclipse.jetty.client.HttpClient;
|
||||||
|
import org.eclipse.jetty.client.api.ContentResponse;
|
||||||
import org.openhab.binding.nuvo.internal.NuvoException;
|
import org.openhab.binding.nuvo.internal.NuvoException;
|
||||||
import org.openhab.binding.nuvo.internal.NuvoStateDescriptionOptionProvider;
|
import org.openhab.binding.nuvo.internal.NuvoStateDescriptionOptionProvider;
|
||||||
import org.openhab.binding.nuvo.internal.NuvoThingActions;
|
import org.openhab.binding.nuvo.internal.NuvoThingActions;
|
||||||
@ -43,12 +57,16 @@ import org.openhab.binding.nuvo.internal.communication.NuvoCommand;
|
|||||||
import org.openhab.binding.nuvo.internal.communication.NuvoConnector;
|
import org.openhab.binding.nuvo.internal.communication.NuvoConnector;
|
||||||
import org.openhab.binding.nuvo.internal.communication.NuvoDefaultConnector;
|
import org.openhab.binding.nuvo.internal.communication.NuvoDefaultConnector;
|
||||||
import org.openhab.binding.nuvo.internal.communication.NuvoEnum;
|
import org.openhab.binding.nuvo.internal.communication.NuvoEnum;
|
||||||
|
import org.openhab.binding.nuvo.internal.communication.NuvoImageResizer;
|
||||||
import org.openhab.binding.nuvo.internal.communication.NuvoIpConnector;
|
import org.openhab.binding.nuvo.internal.communication.NuvoIpConnector;
|
||||||
import org.openhab.binding.nuvo.internal.communication.NuvoMessageEvent;
|
import org.openhab.binding.nuvo.internal.communication.NuvoMessageEvent;
|
||||||
import org.openhab.binding.nuvo.internal.communication.NuvoMessageEventListener;
|
import org.openhab.binding.nuvo.internal.communication.NuvoMessageEventListener;
|
||||||
import org.openhab.binding.nuvo.internal.communication.NuvoSerialConnector;
|
import org.openhab.binding.nuvo.internal.communication.NuvoSerialConnector;
|
||||||
import org.openhab.binding.nuvo.internal.communication.NuvoStatusCodes;
|
import org.openhab.binding.nuvo.internal.communication.NuvoStatusCodes;
|
||||||
import org.openhab.binding.nuvo.internal.configuration.NuvoThingConfiguration;
|
import org.openhab.binding.nuvo.internal.configuration.NuvoThingConfiguration;
|
||||||
|
import org.openhab.binding.nuvo.internal.dto.JAXBUtils;
|
||||||
|
import org.openhab.binding.nuvo.internal.dto.NuvoMenu;
|
||||||
|
import org.openhab.binding.nuvo.internal.dto.NuvoMenu.Source.TopMenu;
|
||||||
import org.openhab.core.io.transport.serial.SerialPortManager;
|
import org.openhab.core.io.transport.serial.SerialPortManager;
|
||||||
import org.openhab.core.library.types.DecimalType;
|
import org.openhab.core.library.types.DecimalType;
|
||||||
import org.openhab.core.library.types.NextPreviousType;
|
import org.openhab.core.library.types.NextPreviousType;
|
||||||
@ -108,6 +126,8 @@ public class NuvoHandler extends BaseThingHandler implements NuvoMessageEventLis
|
|||||||
|
|
||||||
private static final int MPS4_PORT = 5006;
|
private static final int MPS4_PORT = 5006;
|
||||||
|
|
||||||
|
private static final byte[] NO_ART = { 0 };
|
||||||
|
|
||||||
private static final Pattern ZONE_PATTERN = Pattern
|
private static final Pattern ZONE_PATTERN = Pattern
|
||||||
.compile("^ON,SRC(\\d{1}),(MUTE|VOL\\d{1,2}),DND([0-1]),LOCK([0-1])$");
|
.compile("^ON,SRC(\\d{1}),(MUTE|VOL\\d{1,2}),DND([0-1]),LOCK([0-1])$");
|
||||||
private static final Pattern DISP_PATTERN = Pattern.compile("^DISPLINE(\\d{1}),\"(.*)\"$");
|
private static final Pattern DISP_PATTERN = Pattern.compile("^DISPLINE(\\d{1}),\"(.*)\"$");
|
||||||
@ -115,11 +135,10 @@ public class NuvoHandler extends BaseThingHandler implements NuvoMessageEventLis
|
|||||||
.compile("^DISPINFO,DUR(\\d{1,6}),POS(\\d{1,6}),STATUS(\\d{1,2})$");
|
.compile("^DISPINFO,DUR(\\d{1,6}),POS(\\d{1,6}),STATUS(\\d{1,2})$");
|
||||||
private static final Pattern ZONE_CFG_PATTERN = Pattern.compile("^BASS(.*),TREB(.*),BAL(.*),LOUDCMP([0-1])$");
|
private static final Pattern ZONE_CFG_PATTERN = Pattern.compile("^BASS(.*),TREB(.*),BAL(.*),LOUDCMP([0-1])$");
|
||||||
|
|
||||||
private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy,MM,dd,HH,mm");
|
|
||||||
|
|
||||||
private final Logger logger = LoggerFactory.getLogger(NuvoHandler.class);
|
private final Logger logger = LoggerFactory.getLogger(NuvoHandler.class);
|
||||||
private final NuvoStateDescriptionOptionProvider stateDescriptionProvider;
|
private final NuvoStateDescriptionOptionProvider stateDescriptionProvider;
|
||||||
private final SerialPortManager serialPortManager;
|
private final SerialPortManager serialPortManager;
|
||||||
|
private final HttpClient httpClient;
|
||||||
|
|
||||||
private @Nullable ScheduledFuture<?> reconnectJob;
|
private @Nullable ScheduledFuture<?> reconnectJob;
|
||||||
private @Nullable ScheduledFuture<?> pollingJob;
|
private @Nullable ScheduledFuture<?> pollingJob;
|
||||||
@ -133,6 +152,16 @@ public class NuvoHandler extends BaseThingHandler implements NuvoMessageEventLis
|
|||||||
private boolean isGConcerto = false;
|
private boolean isGConcerto = false;
|
||||||
private Object sequenceLock = new Object();
|
private Object sequenceLock = new Object();
|
||||||
|
|
||||||
|
private boolean isAnyOhNuvoNet = false;
|
||||||
|
private NuvoMenu nuvoMenus = new NuvoMenu();
|
||||||
|
private HashMap<String, Integer> nuvoNetSrcMap = new HashMap<String, Integer>();
|
||||||
|
private HashMap<String, String> favPrefixMap = new HashMap<String, String>();
|
||||||
|
private HashMap<String, String[]> favoriteMap = new HashMap<String, String[]>();
|
||||||
|
|
||||||
|
private HashMap<String, byte[]> albumArtMap = new HashMap<String, byte[]>();
|
||||||
|
private HashMap<String, Integer> albumArtIds = new HashMap<String, Integer>();
|
||||||
|
private HashMap<String, String> dispInfoCache = new HashMap<String, String>();
|
||||||
|
|
||||||
Set<Integer> activeZones = new HashSet<>(1);
|
Set<Integer> activeZones = new HashSet<>(1);
|
||||||
|
|
||||||
// A tree map that maps the source ids to source labels
|
// A tree map that maps the source ids to source labels
|
||||||
@ -146,10 +175,11 @@ public class NuvoHandler extends BaseThingHandler implements NuvoMessageEventLis
|
|||||||
* Constructor
|
* Constructor
|
||||||
*/
|
*/
|
||||||
public NuvoHandler(Thing thing, NuvoStateDescriptionOptionProvider stateDescriptionProvider,
|
public NuvoHandler(Thing thing, NuvoStateDescriptionOptionProvider stateDescriptionProvider,
|
||||||
SerialPortManager serialPortManager) {
|
SerialPortManager serialPortManager, HttpClient httpClient) {
|
||||||
super(thing);
|
super(thing);
|
||||||
this.stateDescriptionProvider = stateDescriptionProvider;
|
this.stateDescriptionProvider = stateDescriptionProvider;
|
||||||
this.serialPortManager = serialPortManager;
|
this.serialPortManager = serialPortManager;
|
||||||
|
this.httpClient = httpClient;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -187,15 +217,65 @@ public class NuvoHandler extends BaseThingHandler implements NuvoMessageEventLis
|
|||||||
} else if (port != null) {
|
} else if (port != null) {
|
||||||
connector = new NuvoIpConnector(host, port, uid);
|
connector = new NuvoIpConnector(host, port, uid);
|
||||||
this.isMps4 = (port.intValue() == MPS4_PORT);
|
this.isMps4 = (port.intValue() == MPS4_PORT);
|
||||||
if (this.isMps4) {
|
|
||||||
logger.debug("Port set to {} configuring binding for MPS4 compatability", MPS4_PORT);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
||||||
"Either Serial port or Host & Port must be specifed");
|
"Either Serial port or Host & Port must be specifed");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.isMps4) {
|
||||||
|
logger.debug("Port set to {} configuring binding for MPS4 compatability", MPS4_PORT);
|
||||||
|
|
||||||
|
this.isAnyOhNuvoNet = (config.nuvoNetSrc1 == 2 || config.nuvoNetSrc2 == 2 || config.nuvoNetSrc3 == 2
|
||||||
|
|| config.nuvoNetSrc4 == 2 || config.nuvoNetSrc5 == 2 || config.nuvoNetSrc6 == 2);
|
||||||
|
|
||||||
|
if (this.isAnyOhNuvoNet) {
|
||||||
|
logger.debug("At least one source is configured as an openHAB NuvoNet source");
|
||||||
|
loadMenuConfiguration(config);
|
||||||
|
|
||||||
|
nuvoNetSrcMap.put("1", config.nuvoNetSrc1);
|
||||||
|
nuvoNetSrcMap.put("2", config.nuvoNetSrc2);
|
||||||
|
nuvoNetSrcMap.put("3", config.nuvoNetSrc3);
|
||||||
|
nuvoNetSrcMap.put("4", config.nuvoNetSrc4);
|
||||||
|
nuvoNetSrcMap.put("5", config.nuvoNetSrc5);
|
||||||
|
nuvoNetSrcMap.put("6", config.nuvoNetSrc6);
|
||||||
|
|
||||||
|
favoriteMap.put("1",
|
||||||
|
!config.favoritesSrc1.isEmpty() ? config.favoritesSrc1.split(COMMA) : new String[0]);
|
||||||
|
favoriteMap.put("2",
|
||||||
|
!config.favoritesSrc2.isEmpty() ? config.favoritesSrc2.split(COMMA) : new String[0]);
|
||||||
|
favoriteMap.put("3",
|
||||||
|
!config.favoritesSrc3.isEmpty() ? config.favoritesSrc3.split(COMMA) : new String[0]);
|
||||||
|
favoriteMap.put("4",
|
||||||
|
!config.favoritesSrc4.isEmpty() ? config.favoritesSrc4.split(COMMA) : new String[0]);
|
||||||
|
favoriteMap.put("5",
|
||||||
|
!config.favoritesSrc5.isEmpty() ? config.favoritesSrc5.split(COMMA) : new String[0]);
|
||||||
|
favoriteMap.put("6",
|
||||||
|
!config.favoritesSrc6.isEmpty() ? config.favoritesSrc6.split(COMMA) : new String[0]);
|
||||||
|
|
||||||
|
favPrefixMap.put("1", config.favPrefix1);
|
||||||
|
favPrefixMap.put("2", config.favPrefix2);
|
||||||
|
favPrefixMap.put("3", config.favPrefix3);
|
||||||
|
favPrefixMap.put("4", config.favPrefix4);
|
||||||
|
favPrefixMap.put("5", config.favPrefix5);
|
||||||
|
favPrefixMap.put("6", config.favPrefix6);
|
||||||
|
|
||||||
|
albumArtIds.put("S1", 0);
|
||||||
|
albumArtIds.put("S2", 0);
|
||||||
|
albumArtIds.put("S3", 0);
|
||||||
|
albumArtIds.put("S4", 0);
|
||||||
|
albumArtIds.put("S5", 0);
|
||||||
|
albumArtIds.put("S6", 0);
|
||||||
|
|
||||||
|
albumArtMap.put("S1", NO_ART);
|
||||||
|
albumArtMap.put("S2", NO_ART);
|
||||||
|
albumArtMap.put("S3", NO_ART);
|
||||||
|
albumArtMap.put("S4", NO_ART);
|
||||||
|
albumArtMap.put("S5", NO_ART);
|
||||||
|
albumArtMap.put("S6", NO_ART);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (numZones != null) {
|
if (numZones != null) {
|
||||||
this.numZones = numZones;
|
this.numZones = numZones;
|
||||||
}
|
}
|
||||||
@ -213,6 +293,25 @@ public class NuvoHandler extends BaseThingHandler implements NuvoMessageEventLis
|
|||||||
updateThing(editThing().withChannels(channels).build());
|
updateThing(editThing().withChannels(channels).build());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Build a list of State options for the global favorites using user config values (if supplied)
|
||||||
|
String[] favoritesArr = !config.favoriteLabels.isEmpty() ? config.favoriteLabels.split(COMMA) : new String[0];
|
||||||
|
List<StateOption> favoriteLabelsStateOptions = new ArrayList<>();
|
||||||
|
for (int i = 0; i < 12; i++) {
|
||||||
|
if (favoritesArr.length > i) {
|
||||||
|
favoriteLabelsStateOptions.add(new StateOption(String.valueOf(i + 1), favoritesArr[i]));
|
||||||
|
} else if (favoritesArr.length == 0) {
|
||||||
|
favoriteLabelsStateOptions.add(new StateOption(String.valueOf(i + 1), "Favorite " + (i + 1)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Put the global favorites labels on all active zones
|
||||||
|
activeZones.forEach(zoneNum -> {
|
||||||
|
stateDescriptionProvider.setStateOptions(
|
||||||
|
new ChannelUID(getThing().getUID(),
|
||||||
|
ZONE.toLowerCase() + zoneNum + CHANNEL_DELIMIT + CHANNEL_TYPE_FAVORITE),
|
||||||
|
favoriteLabelsStateOptions);
|
||||||
|
});
|
||||||
|
|
||||||
if (config.clockSync) {
|
if (config.clockSync) {
|
||||||
scheduleClockSyncJob();
|
scheduleClockSyncJob();
|
||||||
}
|
}
|
||||||
@ -225,6 +324,37 @@ public class NuvoHandler extends BaseThingHandler implements NuvoMessageEventLis
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void dispose() {
|
public void dispose() {
|
||||||
|
if (this.isAnyOhNuvoNet) {
|
||||||
|
try {
|
||||||
|
// disable NuvoNet for each source that was configured as an openHAB NuvoNet source
|
||||||
|
nuvoNetSrcMap.forEach((srcNum, val) -> {
|
||||||
|
if (val == 2) {
|
||||||
|
try {
|
||||||
|
connector.sendCommand(SRC_KEY + srcNum + "DISPINFOTWO0,0,0,0,0,0,0");
|
||||||
|
Thread.sleep(SLEEP_BETWEEN_CMD_MS);
|
||||||
|
connector.sendCommand(
|
||||||
|
SRC_KEY + srcNum + "DISPLINES0,0,0,\"Source Unavailable\",\"\",\"\",\"\"");
|
||||||
|
Thread.sleep(SLEEP_BETWEEN_CMD_MS);
|
||||||
|
connector.sendCommand("SCFG" + srcNum + "NUVONET0");
|
||||||
|
Thread.sleep(SLEEP_BETWEEN_CMD_MS);
|
||||||
|
} catch (NuvoException | InterruptedException e) {
|
||||||
|
logger.debug("Error sending command to disable NuvoNet source: {}", srcNum);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// need '1' flag for sources configured as an MPS4 NuvoNet source, but disable openHAB NuvoNet sources
|
||||||
|
connector.sendCommand("SNUMBERS" + (nuvoNetSrcMap.get("1") == 1 ? ONE : ZERO) + COMMA
|
||||||
|
+ (nuvoNetSrcMap.get("2") == 1 ? ONE : ZERO) + COMMA
|
||||||
|
+ (nuvoNetSrcMap.get("3") == 1 ? ONE : ZERO) + COMMA
|
||||||
|
+ (nuvoNetSrcMap.get("4") == 1 ? ONE : ZERO) + COMMA
|
||||||
|
+ (nuvoNetSrcMap.get("5") == 1 ? ONE : ZERO) + COMMA
|
||||||
|
+ (nuvoNetSrcMap.get("6") == 1 ? ONE : ZERO));
|
||||||
|
} catch (NuvoException e) {
|
||||||
|
logger.debug("Error sending SNUMBERS command to disable NuvoNet sources");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
cancelReconnectJob();
|
cancelReconnectJob();
|
||||||
cancelPollingJob();
|
cancelPollingJob();
|
||||||
cancelClockSyncJob();
|
cancelClockSyncJob();
|
||||||
@ -249,7 +379,7 @@ public class NuvoHandler extends BaseThingHandler implements NuvoMessageEventLis
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle a command the UI
|
* Handle a command from the UI
|
||||||
*
|
*
|
||||||
* @param channelUID the channel sending the command
|
* @param channelUID the channel sending the command
|
||||||
* @param command the command received
|
* @param command the command received
|
||||||
@ -410,6 +540,54 @@ public class NuvoHandler extends BaseThingHandler implements NuvoMessageEventLis
|
|||||||
connector.sendCommand(command == OnOffType.ON ? NuvoCommand.PAGE_ON : NuvoCommand.PAGE_OFF);
|
connector.sendCommand(command == OnOffType.ON ? NuvoCommand.PAGE_ON : NuvoCommand.PAGE_OFF);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case CHANNEL_TYPE_SENDCMD:
|
||||||
|
if (command instanceof StringType) {
|
||||||
|
String commandStr = command.toString();
|
||||||
|
if (commandStr.contains(DISP_INFO_TWO)) {
|
||||||
|
String sourceKey = commandStr.split(DISP_INFO_TWO)[0];
|
||||||
|
dispInfoCache.put(sourceKey, commandStr);
|
||||||
|
|
||||||
|
// if 'albumartid' is present, substitute it with the albumArtId hex string
|
||||||
|
connector.sendCommand(commandStr.replace(ALBUM_ART_ID,
|
||||||
|
(OFFSET_ZERO + Integer.toHexString(albumArtIds.get(sourceKey)))));
|
||||||
|
} else {
|
||||||
|
connector.sendCommand(commandStr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case CHANNEL_ART_URL:
|
||||||
|
if (command instanceof StringType) {
|
||||||
|
String url = command.toString();
|
||||||
|
if (url.startsWith(HTTP) || url.startsWith(HTTPS)) {
|
||||||
|
try {
|
||||||
|
ContentResponse contentResponse = httpClient.newRequest(url).method(GET)
|
||||||
|
.timeout(10, TimeUnit.SECONDS).send();
|
||||||
|
int httpStatus = contentResponse.getStatus();
|
||||||
|
if (httpStatus == OK_200) {
|
||||||
|
albumArtMap.put(target.getId(),
|
||||||
|
NuvoImageResizer.resizeImage(contentResponse.getContent(), 80, 80));
|
||||||
|
} else {
|
||||||
|
albumArtMap.put(target.getId(), NO_ART);
|
||||||
|
albumArtIds.put(target.getId(), 0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch (InterruptedException | TimeoutException | ExecutionException e) {
|
||||||
|
albumArtMap.put(target.getId(), NO_ART);
|
||||||
|
albumArtIds.put(target.getId(), 0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
albumArtIds.put(target.getId(), Math.abs(url.hashCode()));
|
||||||
|
|
||||||
|
// re-send the cached DISPINFOTWO message, substituting in the new albumArtId
|
||||||
|
if (dispInfoCache.get(target.getId()) != null) {
|
||||||
|
connector.sendCommand(dispInfoCache.get(target.getId()).replace(ALBUM_ART_ID,
|
||||||
|
(OFFSET_ZERO + Integer.toHexString(albumArtIds.get(target.getId())))));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
albumArtMap.put(target.getId(), NO_ART);
|
||||||
|
albumArtIds.put(target.getId(), 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (NuvoException e) {
|
} catch (NuvoException e) {
|
||||||
logger.warn("Command {} from channel {} failed: {}", command, channel, e.getMessage());
|
logger.warn("Command {} from channel {} failed: {}", command, channel, e.getMessage());
|
||||||
@ -477,6 +655,10 @@ public class NuvoHandler extends BaseThingHandler implements NuvoMessageEventLis
|
|||||||
logger.debug("Grand Concerto not detected");
|
logger.debug("Grand Concerto not detected");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case TYPE_RESTART:
|
||||||
|
logger.debug("Restart message received; re-sending initialization messages");
|
||||||
|
enableNuvonet(false);
|
||||||
|
return;
|
||||||
case TYPE_PING:
|
case TYPE_PING:
|
||||||
logger.debug("Ping message received- rescheduling ping timeout");
|
logger.debug("Ping message received- rescheduling ping timeout");
|
||||||
schedulePingTimeoutJob();
|
schedulePingTimeoutJob();
|
||||||
@ -560,6 +742,85 @@ public class NuvoHandler extends BaseThingHandler implements NuvoMessageEventLis
|
|||||||
case TYPE_ZONE_BUTTON:
|
case TYPE_ZONE_BUTTON:
|
||||||
logger.debug("Zone Button pressed: Source: {} - Button: {}", key, updateData);
|
logger.debug("Zone Button pressed: Source: {} - Button: {}", key, updateData);
|
||||||
updateChannelState(NuvoEnum.valueOf(SOURCE + key), CHANNEL_BUTTON_PRESS, updateData);
|
updateChannelState(NuvoEnum.valueOf(SOURCE + key), CHANNEL_BUTTON_PRESS, updateData);
|
||||||
|
break;
|
||||||
|
case TYPE_ZONE_BUTTON2:
|
||||||
|
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);
|
||||||
|
} else {
|
||||||
|
logger.debug("Zone NuvoNet Button pressed: Source: {} - Unknown button code: {}", key, updateData);
|
||||||
|
updateChannelState(NuvoEnum.valueOf(SOURCE + key), CHANNEL_BUTTON_PRESS, updateData);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case TYPE_MENU_ITEM_SELECTED:
|
||||||
|
String[] updateDataSplit = updateData.split(COMMA);
|
||||||
|
String zoneSource = updateDataSplit[0];
|
||||||
|
String menuId = updateDataSplit[1];
|
||||||
|
int menuItemIdx = Integer.parseInt(updateDataSplit[2]) - 1;
|
||||||
|
|
||||||
|
boolean exitMenu = false;
|
||||||
|
if ("0xFFFFFFFF".equals(menuId)) {
|
||||||
|
TopMenu topMenuItem = nuvoMenus.getSource().get(Integer.parseInt(key) - 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());
|
||||||
|
|
||||||
|
List<String> subMenuItems = topMenuItem.getItems();
|
||||||
|
|
||||||
|
if (subMenuItems.isEmpty()) {
|
||||||
|
exitMenu = true;
|
||||||
|
} else {
|
||||||
|
// send submenu (maximum of 20 items)
|
||||||
|
int subMenuSize = subMenuItems.size() < 20 ? subMenuItems.size() : 20;
|
||||||
|
try {
|
||||||
|
connector.sendCommand(zoneSource + "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) + "\"");
|
||||||
|
}
|
||||||
|
} catch (NuvoException | InterruptedException e) {
|
||||||
|
logger.debug("Error sending sub menu for {}", zoneSource);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// a sub menu item was selected
|
||||||
|
TopMenu topMenuItem = nuvoMenus.getSource().get(Integer.parseInt(key) - 1).getTopMenu()
|
||||||
|
.get(Integer.decode(menuId) - 11);
|
||||||
|
String subMenuItem = topMenuItem.getItems().get(menuItemIdx);
|
||||||
|
|
||||||
|
logger.debug("Sub Menu item selected: Source: {} - Menu Item: {}", key,
|
||||||
|
topMenuItem.getText() + "|" + subMenuItem);
|
||||||
|
updateChannelState(NuvoEnum.valueOf(SOURCE + key), CHANNEL_BUTTON_PRESS,
|
||||||
|
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,\"\"");
|
||||||
|
} catch (NuvoException e) {
|
||||||
|
logger.debug("Error sending exit menu command for {}", zoneSource);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case TYPE_ZONE_MENUREQ:
|
||||||
|
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 menu.
|
||||||
|
String[] menuDataSplit = updateData.split(",");
|
||||||
|
if (menuDataSplit.length > 3 && ONE.equals(menuDataSplit[2])) {
|
||||||
|
try {
|
||||||
|
connector.sendCommand(menuDataSplit[0] + "MENU0xFFFFFFFF,0,0,0,0,0,0,\"\"");
|
||||||
|
} catch (NuvoException e) {
|
||||||
|
logger.debug("Error sending main menu command for {}", menuDataSplit[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case TYPE_ZONE_CONFIG:
|
case TYPE_ZONE_CONFIG:
|
||||||
logger.debug("Zone Configuration: Zone: {} - Value: {}", key, updateData);
|
logger.debug("Zone Configuration: Zone: {} - Value: {}", key, updateData);
|
||||||
@ -576,6 +837,58 @@ public class NuvoHandler extends BaseThingHandler implements NuvoMessageEventLis
|
|||||||
logger.debug("no match on message: {}", updateData);
|
logger.debug("no match on message: {}", updateData);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case TYPE_ALBUM_ART_REQ:
|
||||||
|
logger.debug("Album Art Request for Source: {} - Data: {}", key, 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]));
|
||||||
|
|
||||||
|
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);
|
||||||
|
} else {
|
||||||
|
connector.sendCommand(SRC_KEY + key + ALBUM_ART_AVAILABLE + ZERO_COMMA);
|
||||||
|
}
|
||||||
|
} catch (NuvoException e) {
|
||||||
|
logger.debug("Error sending ALBUMARTAVAILABLE command for source: {}", key);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case TYPE_ALBUM_ART_FRAG_REQ:
|
||||||
|
logger.debug("Album Art Fragment Request for Source: {} - Data: {}", key, 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)) {
|
||||||
|
byte[] chunk = new byte[length];
|
||||||
|
byte[] albumArtBytes = albumArtMap.get(SRC_KEY + key);
|
||||||
|
|
||||||
|
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 + COMMA
|
||||||
|
+ frag.length() + COMMA + frag);
|
||||||
|
} catch (NuvoException e) {
|
||||||
|
logger.debug("Error sending ALBUMARTFRAG command for source: {}, artId: {}", key,
|
||||||
|
requestedId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case TYPE_FAVORITE_REQ:
|
||||||
|
logger.debug("Favorite request for source: {} - favoriteId: {}", key, updateData);
|
||||||
|
try {
|
||||||
|
int playlistIdx = Integer.parseInt(updateData, 16) - 1000;
|
||||||
|
updateChannelState(NuvoEnum.valueOf(SOURCE + key), CHANNEL_BUTTON_PRESS,
|
||||||
|
"PLAY_MUSIC_PRESET:" + favoriteMap.get(key)[playlistIdx]);
|
||||||
|
} catch (NumberFormatException nfe) {
|
||||||
|
logger.debug("Unable to parse favoriteId: {}", updateData);
|
||||||
|
}
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
logger.debug("onNewMessageEvent: unhandled key {}", key);
|
logger.debug("onNewMessageEvent: unhandled key {}", key);
|
||||||
// Return here because receiving an unknown message does not indicate that one can poll
|
// Return here because receiving an unknown message does not indicate that one can poll
|
||||||
@ -587,6 +900,138 @@ public class NuvoHandler extends BaseThingHandler implements NuvoMessageEventLis
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void loadMenuConfiguration(NuvoThingConfiguration config) {
|
||||||
|
StringBuilder menuXml = new StringBuilder("<menu>");
|
||||||
|
|
||||||
|
if (!config.menuXmlSrc1.isEmpty()) {
|
||||||
|
menuXml.append("<source>" + config.menuXmlSrc1 + "</source>");
|
||||||
|
} else {
|
||||||
|
menuXml.append("<source/>");
|
||||||
|
}
|
||||||
|
if (!config.menuXmlSrc2.isEmpty()) {
|
||||||
|
menuXml.append("<source>" + config.menuXmlSrc2 + "</source>");
|
||||||
|
} else {
|
||||||
|
menuXml.append("<source/>");
|
||||||
|
}
|
||||||
|
if (!config.menuXmlSrc3.isEmpty()) {
|
||||||
|
menuXml.append("<source>" + config.menuXmlSrc3 + "</source>");
|
||||||
|
} else {
|
||||||
|
menuXml.append("<source/>");
|
||||||
|
}
|
||||||
|
if (!config.menuXmlSrc4.isEmpty()) {
|
||||||
|
menuXml.append("<source>" + config.menuXmlSrc4 + "</source>");
|
||||||
|
} else {
|
||||||
|
menuXml.append("<source/>");
|
||||||
|
}
|
||||||
|
if (!config.menuXmlSrc5.isEmpty()) {
|
||||||
|
menuXml.append("<source>" + config.menuXmlSrc5 + "</source>");
|
||||||
|
} else {
|
||||||
|
menuXml.append("<source/>");
|
||||||
|
}
|
||||||
|
if (!config.menuXmlSrc6.isEmpty()) {
|
||||||
|
menuXml.append("<source>" + config.menuXmlSrc6 + "</source>");
|
||||||
|
} else {
|
||||||
|
menuXml.append("<source/>");
|
||||||
|
}
|
||||||
|
menuXml.append("</menu>");
|
||||||
|
|
||||||
|
try {
|
||||||
|
JAXBContext ctx = JAXBUtils.JAXBCONTEXT_NUVO_MENU;
|
||||||
|
if (ctx != null) {
|
||||||
|
Unmarshaller unmarshaller = ctx.createUnmarshaller();
|
||||||
|
if (unmarshaller != null) {
|
||||||
|
XMLStreamReader xsr = JAXBUtils.XMLINPUTFACTORY
|
||||||
|
.createXMLStreamReader(new StringReader(menuXml.toString()));
|
||||||
|
NuvoMenu menu = (NuvoMenu) unmarshaller.unmarshal(xsr);
|
||||||
|
if (menu != null) {
|
||||||
|
nuvoMenus = menu;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
logger.debug("No JAXBContext available to parse Nuvo Menu XML");
|
||||||
|
} catch (JAXBException | XMLStreamException e) {
|
||||||
|
logger.warn("Error processing Nuvo Menu XML: {}", e.getLocalizedMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void enableNuvonet(boolean showReady) {
|
||||||
|
if (!this.isAnyOhNuvoNet) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// enable NuvoNet for each source configured as an openHAB NuvoNet source
|
||||||
|
nuvoNetSrcMap.forEach((srcNum, val) -> {
|
||||||
|
if (val == 2) {
|
||||||
|
try {
|
||||||
|
connector.sendCommand("SCFG" + srcNum + "NUVONET1");
|
||||||
|
Thread.sleep(SLEEP_BETWEEN_CMD_MS);
|
||||||
|
} catch (NuvoException | InterruptedException e) {
|
||||||
|
logger.debug("Error sending SCFG command for source: {}", srcNum);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
// set '1' flag for each source configured as an MPS4 NuvoNet source or openHAB NuvoNet source
|
||||||
|
connector.sendCommand("SNUMBERS" + (nuvoNetSrcMap.get("1") > 0 ? ONE : ZERO) + COMMA
|
||||||
|
+ (nuvoNetSrcMap.get("2") > 0 ? ONE : ZERO) + COMMA + (nuvoNetSrcMap.get("3") > 0 ? ONE : ZERO)
|
||||||
|
+ COMMA + (nuvoNetSrcMap.get("4") > 0 ? ONE : ZERO) + COMMA
|
||||||
|
+ (nuvoNetSrcMap.get("5") > 0 ? ONE : ZERO) + COMMA + (nuvoNetSrcMap.get("6") > 0 ? ONE : ZERO));
|
||||||
|
Thread.sleep(SLEEP_BETWEEN_CMD_MS);
|
||||||
|
} catch (NuvoException | InterruptedException e) {
|
||||||
|
logger.debug("Error sending SNUMBERS command");
|
||||||
|
}
|
||||||
|
|
||||||
|
// go though each source and if is openHAB NuvoNet then configure menu, favorites, etc.
|
||||||
|
nuvoNetSrcMap.forEach((srcNum, val) -> {
|
||||||
|
if (val == 2) {
|
||||||
|
try {
|
||||||
|
List<TopMenu> topMenuItems = nuvoMenus.getSource().get(Integer.parseInt(srcNum) - 1).getTopMenu();
|
||||||
|
|
||||||
|
if (!topMenuItems.isEmpty()) {
|
||||||
|
connector.sendCommand(
|
||||||
|
SRC_KEY + srcNum + "MENU," + (topMenuItems.size() < 10 ? topMenuItems.size() : 10));
|
||||||
|
Thread.sleep(SLEEP_BETWEEN_CMD_MS);
|
||||||
|
|
||||||
|
for (int i = 0; i < (topMenuItems.size() < 10 ? topMenuItems.size() : 10); i++) {
|
||||||
|
connector.sendCommand(SRC_KEY + srcNum + "MENUITEM" + (i + 1) + ","
|
||||||
|
+ (topMenuItems.get(i).getItems().isEmpty() ? ZERO : ONE) + ",0,\""
|
||||||
|
+ topMenuItems.get(i).getText() + "\"");
|
||||||
|
Thread.sleep(SLEEP_BETWEEN_CMD_MS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String[] favorites = favoriteMap.get(srcNum);
|
||||||
|
if (favorites != null) {
|
||||||
|
connector.sendCommand(SRC_KEY + srcNum + "FAVORITES"
|
||||||
|
+ (favorites.length < 20 ? favorites.length : 20) + COMMA
|
||||||
|
+ ("1".equals(srcNum) ? ONE : ZERO) + COMMA + ("2".equals(srcNum) ? ONE : ZERO) + COMMA
|
||||||
|
+ ("3".equals(srcNum) ? ONE : ZERO) + COMMA + ("4".equals(srcNum) ? ONE : ZERO) + COMMA
|
||||||
|
+ ("5".equals(srcNum) ? ONE : ZERO) + COMMA + ("6".equals(srcNum) ? ONE : ZERO));
|
||||||
|
Thread.sleep(SLEEP_BETWEEN_CMD_MS);
|
||||||
|
|
||||||
|
for (int i = 0; i < (favorites.length < 20 ? favorites.length : 20); i++) {
|
||||||
|
connector.sendCommand(SRC_KEY + srcNum + "FAVORITESITEM" + (i + 1000) + ",0,0,\""
|
||||||
|
+ favPrefixMap.get(srcNum) + favorites[i] + "\"");
|
||||||
|
Thread.sleep(SLEEP_BETWEEN_CMD_MS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (showReady) {
|
||||||
|
connector.sendCommand(SRC_KEY + srcNum + "DISPINFOTWO0,0,0,0,0,0,0");
|
||||||
|
Thread.sleep(SLEEP_BETWEEN_CMD_MS);
|
||||||
|
connector.sendCommand(SRC_KEY + srcNum + "DISPLINES0,0,0,\"Ready\",\"\",\"\",\"\"");
|
||||||
|
Thread.sleep(SLEEP_BETWEEN_CMD_MS);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (NuvoException | InterruptedException e) {
|
||||||
|
logger.debug("Error configuring NuvoNet for source: {}", srcNum);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Schedule the reconnection job
|
* Schedule the reconnection job
|
||||||
*/
|
*/
|
||||||
@ -603,6 +1048,7 @@ public class NuvoHandler extends BaseThingHandler implements NuvoMessageEventLis
|
|||||||
if (!isMps4) {
|
if (!isMps4) {
|
||||||
pollStatus();
|
pollStatus();
|
||||||
}
|
}
|
||||||
|
enableNuvonet(true);
|
||||||
} else {
|
} else {
|
||||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Reconnection failed");
|
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Reconnection failed");
|
||||||
closeConnection();
|
closeConnection();
|
||||||
@ -760,7 +1206,8 @@ public class NuvoHandler extends BaseThingHandler implements NuvoMessageEventLis
|
|||||||
clockSyncJob = scheduler.scheduleWithFixedDelay(() -> {
|
clockSyncJob = scheduler.scheduleWithFixedDelay(() -> {
|
||||||
if (this.isGConcerto) {
|
if (this.isGConcerto) {
|
||||||
try {
|
try {
|
||||||
connector.sendCommand(NuvoCommand.CFGTIME.getValue() + DATE_FORMAT.format(new Date()));
|
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy,MM,dd,HH,mm");
|
||||||
|
connector.sendCommand(NuvoCommand.CFGTIME.getValue() + dateFormat.format(new Date()));
|
||||||
} catch (NuvoException e) {
|
} catch (NuvoException e) {
|
||||||
logger.debug("Error syncing clock: {}", e.getMessage());
|
logger.debug("Error syncing clock: {}", e.getMessage());
|
||||||
}
|
}
|
||||||
|
@ -66,10 +66,78 @@ thing-type.nuvo.amplifier.group.zone20.description = The Controls for Zone 20
|
|||||||
|
|
||||||
thing-type.config.nuvo.amplifier.clockSync.label = Sync Clock On GConcerto
|
thing-type.config.nuvo.amplifier.clockSync.label = Sync Clock On GConcerto
|
||||||
thing-type.config.nuvo.amplifier.clockSync.description = If set to true, the binding will sync the internal clock on the Grand Concerto to match the openHAB host's system clock. The sync job runs at binding startup and once an hour thereafter. The Essentia G has no RTC, so this setting has no effect on that component.
|
thing-type.config.nuvo.amplifier.clockSync.description = If set to true, the binding will sync the internal clock on the Grand Concerto to match the openHAB host's system clock. The sync job runs at binding startup and once an hour thereafter. The Essentia G has no RTC, so this setting has no effect on that component.
|
||||||
|
thing-type.config.nuvo.amplifier.favPrefix1.label = S1 Favorite Prefix
|
||||||
|
thing-type.config.nuvo.amplifier.favPrefix1.description = To quickly locate Source 1's favorites, this prefix will be added to the favorite names
|
||||||
|
thing-type.config.nuvo.amplifier.favPrefix2.label = S2 Favorite Prefix
|
||||||
|
thing-type.config.nuvo.amplifier.favPrefix2.description = To quickly locate Source 2's favorites, this prefix will be added to the favorite names
|
||||||
|
thing-type.config.nuvo.amplifier.favPrefix3.label = S3 Favorite Prefix
|
||||||
|
thing-type.config.nuvo.amplifier.favPrefix3.description = To quickly locate Source 3's favorites, this prefix will be added to the favorite names
|
||||||
|
thing-type.config.nuvo.amplifier.favPrefix4.label = S4 Favorite Prefix
|
||||||
|
thing-type.config.nuvo.amplifier.favPrefix4.description = To quickly locate Source 4's favorites, this prefix will be added to the favorite names
|
||||||
|
thing-type.config.nuvo.amplifier.favPrefix5.label = S5 Favorite Prefix
|
||||||
|
thing-type.config.nuvo.amplifier.favPrefix5.description = To quickly locate Source 5's favorites, this prefix will be added to the favorite names
|
||||||
|
thing-type.config.nuvo.amplifier.favPrefix6.label = S6 Favorite Prefix
|
||||||
|
thing-type.config.nuvo.amplifier.favPrefix6.description = To quickly locate Source 6's favorites, this prefix will be added to the favorite names
|
||||||
|
thing-type.config.nuvo.amplifier.favoriteLabels.label = Favorite Labels
|
||||||
|
thing-type.config.nuvo.amplifier.favoriteLabels.description = A comma separated list of up to 12 label names that are loaded into the 'favorites' channel of each zone
|
||||||
|
thing-type.config.nuvo.amplifier.favoritesSrc1.label = S1 Favorites
|
||||||
|
thing-type.config.nuvo.amplifier.favoritesSrc1.description = A comma separated list of favorite names to load into Source 1
|
||||||
|
thing-type.config.nuvo.amplifier.favoritesSrc2.label = S2 Favorites
|
||||||
|
thing-type.config.nuvo.amplifier.favoritesSrc2.description = A comma separated list of favorite names to load into Source 2
|
||||||
|
thing-type.config.nuvo.amplifier.favoritesSrc3.label = S3 Favorites
|
||||||
|
thing-type.config.nuvo.amplifier.favoritesSrc3.description = A comma separated list of favorite names to load into Source 3
|
||||||
|
thing-type.config.nuvo.amplifier.favoritesSrc4.label = S4 Favorites
|
||||||
|
thing-type.config.nuvo.amplifier.favoritesSrc4.description = A comma separated list of favorite names to load into Source 4
|
||||||
|
thing-type.config.nuvo.amplifier.favoritesSrc5.label = S5 Favorites
|
||||||
|
thing-type.config.nuvo.amplifier.favoritesSrc5.description = A comma separated list of favorite names to load into Source 5
|
||||||
|
thing-type.config.nuvo.amplifier.favoritesSrc6.label = S6 Favorites
|
||||||
|
thing-type.config.nuvo.amplifier.favoritesSrc6.description = A comma separated list of favorite names to load into Source 6
|
||||||
thing-type.config.nuvo.amplifier.host.label = Address
|
thing-type.config.nuvo.amplifier.host.label = Address
|
||||||
thing-type.config.nuvo.amplifier.host.description = Host Name or IP Address of the machine connected to the Nuvo amplifier (Serial over IP)
|
thing-type.config.nuvo.amplifier.host.description = Host Name or IP Address of the machine connected to the Nuvo amplifier (Serial over IP)
|
||||||
|
thing-type.config.nuvo.amplifier.menuXmlSrc1.label = S1 Menu XML
|
||||||
|
thing-type.config.nuvo.amplifier.menuXmlSrc1.description = An XML string representing the menu items to load into the keypad for Source 1, see README
|
||||||
|
thing-type.config.nuvo.amplifier.menuXmlSrc2.label = S2 Menu XML
|
||||||
|
thing-type.config.nuvo.amplifier.menuXmlSrc2.description = An XML string representing the menu items to load into the keypad for Source 2, see README
|
||||||
|
thing-type.config.nuvo.amplifier.menuXmlSrc3.label = S3 Menu XML
|
||||||
|
thing-type.config.nuvo.amplifier.menuXmlSrc3.description = An XML string representing the menu items to load into the keypad for Source 3, see README
|
||||||
|
thing-type.config.nuvo.amplifier.menuXmlSrc4.label = S4 Menu XML
|
||||||
|
thing-type.config.nuvo.amplifier.menuXmlSrc4.description = An XML string representing the menu items to load into the keypad for Source 4, see README
|
||||||
|
thing-type.config.nuvo.amplifier.menuXmlSrc5.label = S5 Menu XML
|
||||||
|
thing-type.config.nuvo.amplifier.menuXmlSrc5.description = An XML string representing the menu items to load into the keypad for Source 5, see README
|
||||||
|
thing-type.config.nuvo.amplifier.menuXmlSrc6.label = S6 Menu XML
|
||||||
|
thing-type.config.nuvo.amplifier.menuXmlSrc6.description = An XML string representing the menu items to load into the keypad for Source 6, see README
|
||||||
thing-type.config.nuvo.amplifier.numZones.label = Number of Zones
|
thing-type.config.nuvo.amplifier.numZones.label = Number of Zones
|
||||||
thing-type.config.nuvo.amplifier.numZones.description = Number of Zones on the amplifier to utilize in the binding (Up to 20 zones when using expansion module)
|
thing-type.config.nuvo.amplifier.numZones.description = Number of Zones on the amplifier to utilize in the binding (Up to 20 zones when using expansion module)
|
||||||
|
thing-type.config.nuvo.amplifier.nuvoNetSrc1.label = S1 is NuvoNet
|
||||||
|
thing-type.config.nuvo.amplifier.nuvoNetSrc1.description = Indicates if Source 1 is configured as a NuvoNet source
|
||||||
|
thing-type.config.nuvo.amplifier.nuvoNetSrc1.option.0 = No
|
||||||
|
thing-type.config.nuvo.amplifier.nuvoNetSrc1.option.1 = MPS4 NuvoNet Source
|
||||||
|
thing-type.config.nuvo.amplifier.nuvoNetSrc1.option.2 = openHAB NuvoNet Source
|
||||||
|
thing-type.config.nuvo.amplifier.nuvoNetSrc2.label = S2 is NuvoNet
|
||||||
|
thing-type.config.nuvo.amplifier.nuvoNetSrc2.description = Indicates if Source 2 is configured as a NuvoNet source
|
||||||
|
thing-type.config.nuvo.amplifier.nuvoNetSrc2.option.0 = No
|
||||||
|
thing-type.config.nuvo.amplifier.nuvoNetSrc2.option.1 = MPS4 NuvoNet Source
|
||||||
|
thing-type.config.nuvo.amplifier.nuvoNetSrc2.option.2 = openHAB NuvoNet Source
|
||||||
|
thing-type.config.nuvo.amplifier.nuvoNetSrc3.label = S3 is NuvoNet
|
||||||
|
thing-type.config.nuvo.amplifier.nuvoNetSrc3.description = Indicates if Source 3 is configured as a NuvoNet source
|
||||||
|
thing-type.config.nuvo.amplifier.nuvoNetSrc3.option.0 = No
|
||||||
|
thing-type.config.nuvo.amplifier.nuvoNetSrc3.option.1 = MPS4 NuvoNet Source
|
||||||
|
thing-type.config.nuvo.amplifier.nuvoNetSrc3.option.2 = openHAB NuvoNet Source
|
||||||
|
thing-type.config.nuvo.amplifier.nuvoNetSrc4.label = S4 is NuvoNet
|
||||||
|
thing-type.config.nuvo.amplifier.nuvoNetSrc4.description = Indicates if Source 4 is configured as a NuvoNet source
|
||||||
|
thing-type.config.nuvo.amplifier.nuvoNetSrc4.option.0 = No
|
||||||
|
thing-type.config.nuvo.amplifier.nuvoNetSrc4.option.1 = MPS4 NuvoNet Source
|
||||||
|
thing-type.config.nuvo.amplifier.nuvoNetSrc4.option.2 = openHAB NuvoNet Source
|
||||||
|
thing-type.config.nuvo.amplifier.nuvoNetSrc5.label = S5 is NuvoNet
|
||||||
|
thing-type.config.nuvo.amplifier.nuvoNetSrc5.description = Indicates if Source 5 is configured as a NuvoNet source
|
||||||
|
thing-type.config.nuvo.amplifier.nuvoNetSrc5.option.0 = No
|
||||||
|
thing-type.config.nuvo.amplifier.nuvoNetSrc5.option.1 = MPS4 NuvoNet Source
|
||||||
|
thing-type.config.nuvo.amplifier.nuvoNetSrc5.option.2 = openHAB NuvoNet Source
|
||||||
|
thing-type.config.nuvo.amplifier.nuvoNetSrc6.label = S6 is NuvoNet
|
||||||
|
thing-type.config.nuvo.amplifier.nuvoNetSrc6.description = Indicates if Source 6 is configured as a NuvoNet source
|
||||||
|
thing-type.config.nuvo.amplifier.nuvoNetSrc6.option.0 = No
|
||||||
|
thing-type.config.nuvo.amplifier.nuvoNetSrc6.option.1 = MPS4 NuvoNet Source
|
||||||
|
thing-type.config.nuvo.amplifier.nuvoNetSrc6.option.2 = openHAB NuvoNet Source
|
||||||
thing-type.config.nuvo.amplifier.port.label = Port
|
thing-type.config.nuvo.amplifier.port.label = Port
|
||||||
thing-type.config.nuvo.amplifier.port.description = Communication Port (serial over IP). For IP connection to the Nuvo amplifier. Use port 5006 with MPS4 server.
|
thing-type.config.nuvo.amplifier.port.description = Communication Port (serial over IP). For IP connection to the Nuvo amplifier. Use port 5006 with MPS4 server.
|
||||||
thing-type.config.nuvo.amplifier.serialPort.label = Serial Port
|
thing-type.config.nuvo.amplifier.serialPort.label = Serial Port
|
||||||
@ -88,6 +156,8 @@ channel-group-type.nuvo.zone.description = The Controls for the Zone
|
|||||||
|
|
||||||
channel-type.nuvo.alloff.label = All Off
|
channel-type.nuvo.alloff.label = All Off
|
||||||
channel-type.nuvo.alloff.description = Turn All Zones Off
|
channel-type.nuvo.alloff.description = Turn All Zones Off
|
||||||
|
channel-type.nuvo.art_url.label = Album Art URL
|
||||||
|
channel-type.nuvo.art_url.description = The URL of the Album Art JPG for this source that is displayed on a CTP-36
|
||||||
channel-type.nuvo.balance.label = Balance Adjustment
|
channel-type.nuvo.balance.label = Balance Adjustment
|
||||||
channel-type.nuvo.balance.description = Adjust the Balance Setting for the Zone
|
channel-type.nuvo.balance.description = Adjust the Balance Setting for the Zone
|
||||||
channel-type.nuvo.bass.label = Bass Adjustment
|
channel-type.nuvo.bass.label = Bass Adjustment
|
||||||
@ -108,18 +178,6 @@ channel-type.nuvo.dnd.label = Do Not Disturb
|
|||||||
channel-type.nuvo.dnd.description = A Switch That Controls If the Zone Should Ignore an Incoming Audio Page
|
channel-type.nuvo.dnd.description = A Switch That Controls If the Zone Should Ignore an Incoming Audio Page
|
||||||
channel-type.nuvo.favorite.label = Favorite
|
channel-type.nuvo.favorite.label = Favorite
|
||||||
channel-type.nuvo.favorite.description = Select a Preset Favorite for the Zone
|
channel-type.nuvo.favorite.description = Select a Preset Favorite for the Zone
|
||||||
channel-type.nuvo.favorite.state.option.1 = Favorite 1
|
|
||||||
channel-type.nuvo.favorite.state.option.2 = Favorite 2
|
|
||||||
channel-type.nuvo.favorite.state.option.3 = Favorite 3
|
|
||||||
channel-type.nuvo.favorite.state.option.4 = Favorite 4
|
|
||||||
channel-type.nuvo.favorite.state.option.5 = Favorite 5
|
|
||||||
channel-type.nuvo.favorite.state.option.6 = Favorite 6
|
|
||||||
channel-type.nuvo.favorite.state.option.7 = Favorite 7
|
|
||||||
channel-type.nuvo.favorite.state.option.8 = Favorite 8
|
|
||||||
channel-type.nuvo.favorite.state.option.9 = Favorite 9
|
|
||||||
channel-type.nuvo.favorite.state.option.10 = Favorite 10
|
|
||||||
channel-type.nuvo.favorite.state.option.11 = Favorite 11
|
|
||||||
channel-type.nuvo.favorite.state.option.12 = Favorite 12
|
|
||||||
channel-type.nuvo.lock.label = Locked
|
channel-type.nuvo.lock.label = Locked
|
||||||
channel-type.nuvo.lock.description = Indicates If This Zone Is Locked
|
channel-type.nuvo.lock.description = Indicates If This Zone Is Locked
|
||||||
channel-type.nuvo.lock.state.option.CLOSED = Unlocked
|
channel-type.nuvo.lock.state.option.CLOSED = Unlocked
|
||||||
@ -132,6 +190,8 @@ channel-type.nuvo.party.label = Party Mode
|
|||||||
channel-type.nuvo.party.description = Activate Party Mode With This Zone as the Host
|
channel-type.nuvo.party.description = Activate Party Mode With This Zone as the Host
|
||||||
channel-type.nuvo.play_mode.label = Play Mode
|
channel-type.nuvo.play_mode.label = Play Mode
|
||||||
channel-type.nuvo.play_mode.description = The Current Playback Mode of the Source
|
channel-type.nuvo.play_mode.description = The Current Playback Mode of the Source
|
||||||
|
channel-type.nuvo.sendcmd.label = Send Command
|
||||||
|
channel-type.nuvo.sendcmd.description = Send a command to the amplifier
|
||||||
channel-type.nuvo.source.label = Source Input
|
channel-type.nuvo.source.label = Source Input
|
||||||
channel-type.nuvo.source.description = Select the Source Input for the Zone
|
channel-type.nuvo.source.description = Select the Source Input for the Zone
|
||||||
channel-type.nuvo.track_length.label = Track Length
|
channel-type.nuvo.track_length.label = Track Length
|
||||||
|
@ -145,6 +145,11 @@
|
|||||||
<description>Number of Zones on the amplifier to utilize in the binding (Up to 20 zones when using expansion module)</description>
|
<description>Number of Zones on the amplifier to utilize in the binding (Up to 20 zones when using expansion module)</description>
|
||||||
<default>6</default>
|
<default>6</default>
|
||||||
</parameter>
|
</parameter>
|
||||||
|
<parameter name="favoriteLabels" type="text" required="false">
|
||||||
|
<label>Favorite Labels</label>
|
||||||
|
<description>A comma separated list of up to 12 label names that are loaded into the 'favorites' channel of each
|
||||||
|
zone</description>
|
||||||
|
</parameter>
|
||||||
<parameter name="clockSync" type="boolean" required="false">
|
<parameter name="clockSync" type="boolean" required="false">
|
||||||
<label>Sync Clock On GConcerto</label>
|
<label>Sync Clock On GConcerto</label>
|
||||||
<description>If set to true, the binding will sync the internal clock on the Grand Concerto to match the openHAB
|
<description>If set to true, the binding will sync the internal clock on the Grand Concerto to match the openHAB
|
||||||
@ -152,6 +157,180 @@
|
|||||||
so this setting has no effect on that component.</description>
|
so this setting has no effect on that component.</description>
|
||||||
<default>false</default>
|
<default>false</default>
|
||||||
</parameter>
|
</parameter>
|
||||||
|
<parameter name="nuvoNetSrc1" type="integer" required="true">
|
||||||
|
<label>S1 is NuvoNet</label>
|
||||||
|
<description>Indicates if Source 1 is configured as a NuvoNet source</description>
|
||||||
|
<limitToOptions>true</limitToOptions>
|
||||||
|
<options>
|
||||||
|
<option value="0">No</option>
|
||||||
|
<option value="1">MPS4 NuvoNet Source</option>
|
||||||
|
<option value="2">openHAB NuvoNet Source</option>
|
||||||
|
</options>
|
||||||
|
<default>0</default>
|
||||||
|
<advanced>true</advanced>
|
||||||
|
</parameter>
|
||||||
|
<parameter name="nuvoNetSrc2" type="integer" required="true">
|
||||||
|
<label>S2 is NuvoNet</label>
|
||||||
|
<description>Indicates if Source 2 is configured as a NuvoNet source</description>
|
||||||
|
<limitToOptions>true</limitToOptions>
|
||||||
|
<options>
|
||||||
|
<option value="0">No</option>
|
||||||
|
<option value="1">MPS4 NuvoNet Source</option>
|
||||||
|
<option value="2">openHAB NuvoNet Source</option>
|
||||||
|
</options>
|
||||||
|
<default>0</default>
|
||||||
|
<advanced>true</advanced>
|
||||||
|
</parameter>
|
||||||
|
<parameter name="nuvoNetSrc3" type="integer" required="true">
|
||||||
|
<label>S3 is NuvoNet</label>
|
||||||
|
<description>Indicates if Source 3 is configured as a NuvoNet source</description>
|
||||||
|
<limitToOptions>true</limitToOptions>
|
||||||
|
<options>
|
||||||
|
<option value="0">No</option>
|
||||||
|
<option value="1">MPS4 NuvoNet Source</option>
|
||||||
|
<option value="2">openHAB NuvoNet Source</option>
|
||||||
|
</options>
|
||||||
|
<default>0</default>
|
||||||
|
<advanced>true</advanced>
|
||||||
|
</parameter>
|
||||||
|
<parameter name="nuvoNetSrc4" type="integer" required="true">
|
||||||
|
<label>S4 is NuvoNet</label>
|
||||||
|
<description>Indicates if Source 4 is configured as a NuvoNet source</description>
|
||||||
|
<limitToOptions>true</limitToOptions>
|
||||||
|
<options>
|
||||||
|
<option value="0">No</option>
|
||||||
|
<option value="1">MPS4 NuvoNet Source</option>
|
||||||
|
<option value="2">openHAB NuvoNet Source</option>
|
||||||
|
</options>
|
||||||
|
<default>0</default>
|
||||||
|
<advanced>true</advanced>
|
||||||
|
</parameter>
|
||||||
|
<parameter name="nuvoNetSrc5" type="integer" required="true">
|
||||||
|
<label>S5 is NuvoNet</label>
|
||||||
|
<description>Indicates if Source 5 is configured as a NuvoNet source</description>
|
||||||
|
<limitToOptions>true</limitToOptions>
|
||||||
|
<options>
|
||||||
|
<option value="0">No</option>
|
||||||
|
<option value="1">MPS4 NuvoNet Source</option>
|
||||||
|
<option value="2">openHAB NuvoNet Source</option>
|
||||||
|
</options>
|
||||||
|
<default>0</default>
|
||||||
|
<advanced>true</advanced>
|
||||||
|
</parameter>
|
||||||
|
<parameter name="nuvoNetSrc6" type="integer" required="true">
|
||||||
|
<label>S6 is NuvoNet</label>
|
||||||
|
<description>Indicates if Source 6 is configured as a NuvoNet source</description>
|
||||||
|
<limitToOptions>true</limitToOptions>
|
||||||
|
<options>
|
||||||
|
<option value="0">No</option>
|
||||||
|
<option value="1">MPS4 NuvoNet Source</option>
|
||||||
|
<option value="2">openHAB NuvoNet Source</option>
|
||||||
|
</options>
|
||||||
|
<default>0</default>
|
||||||
|
<advanced>true</advanced>
|
||||||
|
</parameter>
|
||||||
|
<parameter name="favoritesSrc1" type="text" required="false">
|
||||||
|
<label>S1 Favorites</label>
|
||||||
|
<description>A comma separated list of favorite names to load into Source 1</description>
|
||||||
|
<advanced>true</advanced>
|
||||||
|
</parameter>
|
||||||
|
<parameter name="favoritesSrc2" type="text" required="false">
|
||||||
|
<label>S2 Favorites</label>
|
||||||
|
<description>A comma separated list of favorite names to load into Source 2</description>
|
||||||
|
<advanced>true</advanced>
|
||||||
|
</parameter>
|
||||||
|
<parameter name="favoritesSrc3" type="text" required="false">
|
||||||
|
<label>S3 Favorites</label>
|
||||||
|
<description>A comma separated list of favorite names to load into Source 3</description>
|
||||||
|
<advanced>true</advanced>
|
||||||
|
</parameter>
|
||||||
|
<parameter name="favoritesSrc4" type="text" required="false">
|
||||||
|
<label>S4 Favorites</label>
|
||||||
|
<description>A comma separated list of favorite names to load into Source 4</description>
|
||||||
|
<advanced>true</advanced>
|
||||||
|
</parameter>
|
||||||
|
<parameter name="favoritesSrc5" type="text" required="false">
|
||||||
|
<label>S5 Favorites</label>
|
||||||
|
<description>A comma separated list of favorite names to load into Source 5</description>
|
||||||
|
<advanced>true</advanced>
|
||||||
|
</parameter>
|
||||||
|
<parameter name="favoritesSrc6" type="text" required="false">
|
||||||
|
<label>S6 Favorites</label>
|
||||||
|
<description>A comma separated list of favorite names to load into Source 6</description>
|
||||||
|
<advanced>true</advanced>
|
||||||
|
</parameter>
|
||||||
|
<parameter name="favPrefix1" type="text" required="false">
|
||||||
|
<label>S1 Favorite Prefix</label>
|
||||||
|
<description>To quickly locate Source 1's favorites, this prefix will be added to the favorite
|
||||||
|
names</description>
|
||||||
|
<advanced>true</advanced>
|
||||||
|
</parameter>
|
||||||
|
<parameter name="favPrefix2" type="text" required="false">
|
||||||
|
<label>S2 Favorite Prefix</label>
|
||||||
|
<description>To quickly locate Source 2's favorites, this prefix will be added to the favorite
|
||||||
|
names</description>
|
||||||
|
<advanced>true</advanced>
|
||||||
|
</parameter>
|
||||||
|
<parameter name="favPrefix3" type="text" required="false">
|
||||||
|
<label>S3 Favorite Prefix</label>
|
||||||
|
<description>To quickly locate Source 3's favorites, this prefix will be added to the favorite
|
||||||
|
names</description>
|
||||||
|
<advanced>true</advanced>
|
||||||
|
</parameter>
|
||||||
|
<parameter name="favPrefix4" type="text" required="false">
|
||||||
|
<label>S4 Favorite Prefix</label>
|
||||||
|
<description>To quickly locate Source 4's favorites, this prefix will be added to the favorite
|
||||||
|
names</description>
|
||||||
|
<advanced>true</advanced>
|
||||||
|
</parameter>
|
||||||
|
<parameter name="favPrefix5" type="text" required="false">
|
||||||
|
<label>S5 Favorite Prefix</label>
|
||||||
|
<description>To quickly locate Source 5's favorites, this prefix will be added to the favorite
|
||||||
|
names</description>
|
||||||
|
<advanced>true</advanced>
|
||||||
|
</parameter>
|
||||||
|
<parameter name="favPrefix6" type="text" required="false">
|
||||||
|
<label>S6 Favorite Prefix</label>
|
||||||
|
<description>To quickly locate Source 6's favorites, this prefix will be added to the favorite
|
||||||
|
names</description>
|
||||||
|
<advanced>true</advanced>
|
||||||
|
</parameter>
|
||||||
|
<parameter name="menuXmlSrc1" type="text" required="false">
|
||||||
|
<context>script</context>
|
||||||
|
<label>S1 Menu XML</label>
|
||||||
|
<description>An XML string representing the menu items to load into the keypad for Source 1, see README</description>
|
||||||
|
<advanced>true</advanced>
|
||||||
|
</parameter>
|
||||||
|
<parameter name="menuXmlSrc2" type="text" required="false">
|
||||||
|
<context>script</context>
|
||||||
|
<label>S2 Menu XML</label>
|
||||||
|
<description>An XML string representing the menu items to load into the keypad for Source 2, see README</description>
|
||||||
|
<advanced>true</advanced>
|
||||||
|
</parameter>
|
||||||
|
<parameter name="menuXmlSrc3" type="text" required="false">
|
||||||
|
<context>script</context>
|
||||||
|
<label>S3 Menu XML</label>
|
||||||
|
<description>An XML string representing the menu items to load into the keypad for Source 3, see README</description>
|
||||||
|
<advanced>true</advanced>
|
||||||
|
</parameter>
|
||||||
|
<parameter name="menuXmlSrc4" type="text" required="false">
|
||||||
|
<context>script</context>
|
||||||
|
<label>S4 Menu XML</label>
|
||||||
|
<description>An XML string representing the menu items to load into the keypad for Source 4, see README</description>
|
||||||
|
<advanced>true</advanced>
|
||||||
|
</parameter>
|
||||||
|
<parameter name="menuXmlSrc5" type="text" required="false">
|
||||||
|
<context>script</context>
|
||||||
|
<label>S5 Menu XML</label>
|
||||||
|
<description>An XML string representing the menu items to load into the keypad for Source 5, see README</description>
|
||||||
|
<advanced>true</advanced>
|
||||||
|
</parameter>
|
||||||
|
<parameter name="menuXmlSrc6" type="text" required="false">
|
||||||
|
<context>script</context>
|
||||||
|
<label>S6 Menu XML</label>
|
||||||
|
<description>An XML string representing the menu items to load into the keypad for Source 6, see README</description>
|
||||||
|
<advanced>true</advanced>
|
||||||
|
</parameter>
|
||||||
</config-description>
|
</config-description>
|
||||||
</thing-type>
|
</thing-type>
|
||||||
|
|
||||||
@ -162,6 +341,7 @@
|
|||||||
<channel id="alloff" typeId="alloff"/>
|
<channel id="alloff" typeId="alloff"/>
|
||||||
<channel id="allmute" typeId="system.mute"/>
|
<channel id="allmute" typeId="system.mute"/>
|
||||||
<channel id="page" typeId="page"/>
|
<channel id="page" typeId="page"/>
|
||||||
|
<channel id="sendcmd" typeId="sendcmd"/>
|
||||||
</channels>
|
</channels>
|
||||||
</channel-group-type>
|
</channel-group-type>
|
||||||
|
|
||||||
@ -197,6 +377,7 @@
|
|||||||
<channel id="track_length" typeId="track_length"/>
|
<channel id="track_length" typeId="track_length"/>
|
||||||
<channel id="track_position" typeId="track_position"/>
|
<channel id="track_position" typeId="track_position"/>
|
||||||
<channel id="button_press" typeId="button_press"/>
|
<channel id="button_press" typeId="button_press"/>
|
||||||
|
<channel id="art_url" typeId="art_url"/>
|
||||||
</channels>
|
</channels>
|
||||||
</channel-group-type>
|
</channel-group-type>
|
||||||
|
|
||||||
@ -212,6 +393,12 @@
|
|||||||
<description>Activates the Page Mode for All Zones</description>
|
<description>Activates the Page Mode for All Zones</description>
|
||||||
</channel-type>
|
</channel-type>
|
||||||
|
|
||||||
|
<channel-type id="sendcmd">
|
||||||
|
<item-type>String</item-type>
|
||||||
|
<label>Send Command</label>
|
||||||
|
<description>Send a command to the amplifier</description>
|
||||||
|
</channel-type>
|
||||||
|
|
||||||
<channel-type id="source">
|
<channel-type id="source">
|
||||||
<item-type>Number</item-type>
|
<item-type>Number</item-type>
|
||||||
<label>Source Input</label>
|
<label>Source Input</label>
|
||||||
@ -222,22 +409,6 @@
|
|||||||
<item-type>Number</item-type>
|
<item-type>Number</item-type>
|
||||||
<label>Favorite</label>
|
<label>Favorite</label>
|
||||||
<description>Select a Preset Favorite for the Zone</description>
|
<description>Select a Preset Favorite for the Zone</description>
|
||||||
<state>
|
|
||||||
<options>
|
|
||||||
<option value="1">Favorite 1</option>
|
|
||||||
<option value="2">Favorite 2</option>
|
|
||||||
<option value="3">Favorite 3</option>
|
|
||||||
<option value="4">Favorite 4</option>
|
|
||||||
<option value="5">Favorite 5</option>
|
|
||||||
<option value="6">Favorite 6</option>
|
|
||||||
<option value="7">Favorite 7</option>
|
|
||||||
<option value="8">Favorite 8</option>
|
|
||||||
<option value="9">Favorite 9</option>
|
|
||||||
<option value="10">Favorite 10</option>
|
|
||||||
<option value="11">Favorite 11</option>
|
|
||||||
<option value="12">Favorite 12</option>
|
|
||||||
</options>
|
|
||||||
</state>
|
|
||||||
</channel-type>
|
</channel-type>
|
||||||
|
|
||||||
<channel-type id="control">
|
<channel-type id="control">
|
||||||
@ -350,4 +521,10 @@
|
|||||||
<state readOnly="true"/>
|
<state readOnly="true"/>
|
||||||
</channel-type>
|
</channel-type>
|
||||||
|
|
||||||
|
<channel-type id="art_url">
|
||||||
|
<item-type>String</item-type>
|
||||||
|
<label>Album Art URL</label>
|
||||||
|
<description>The URL of the Album Art JPG for this source that is displayed on a CTP-36</description>
|
||||||
|
</channel-type>
|
||||||
|
|
||||||
</thing:thing-descriptions>
|
</thing:thing-descriptions>
|
||||||
|
Loading…
Reference in New Issue
Block a user