[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:
@@ -61,6 +61,7 @@ public class NuvoBindingConstants {
|
||||
public static final String CHANNEL_TRACK_LENGTH = "track_length";
|
||||
public static final String CHANNEL_TRACK_POSITION = "track_position";
|
||||
public static final String CHANNEL_BUTTON_PRESS = "button_press";
|
||||
public static final String CHANNEL_ART_URL = "art_url";
|
||||
|
||||
// Message types
|
||||
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_ZONE_UPDATE = "zone_update";
|
||||
public static final String TYPE_ZONE_BUTTON = "zone_button";
|
||||
public static final String TYPE_ZONE_BUTTON2 = "zone_button2";
|
||||
public static final String TYPE_ZONE_MENUREQ = "zone_menureq";
|
||||
public static final String TYPE_MENU_ITEM_SELECTED = "top_menu_button";
|
||||
public static final String TYPE_ZONE_CONFIG = "zone_config";
|
||||
public static final String TYPE_ALBUM_ART_REQ = "album_art_req";
|
||||
public static final String TYPE_ALBUM_ART_FRAG_REQ = "album_art_frag_req";
|
||||
public static final String TYPE_FAVORITE_REQ = "favorite_req";
|
||||
|
||||
// misc
|
||||
public static final String ON = "ON";
|
||||
public static final String OFF = "OFF";
|
||||
public static final String TWO = "2";
|
||||
public static final String ONE = "1";
|
||||
public static final String ZERO = "0";
|
||||
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 DISPINFO = "DISPINFO,"; // yes comma here
|
||||
public static final String DISP_INFO_TWO = "DISPINFOTWO";
|
||||
public static final String NAME_QUOTE = "NAME\"";
|
||||
public static final String MUTE = "MUTE";
|
||||
public static final String VOL = "VOL";
|
||||
public static final String OFFSET_ZERO = "0x";
|
||||
public static final String ZERO_COMMA = "0,0";
|
||||
|
||||
// MPS4
|
||||
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.Nullable;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
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.thing.Thing;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
@@ -46,6 +48,8 @@ public class NuvoHandlerFactory extends BaseThingHandlerFactory {
|
||||
|
||||
private final NuvoStateDescriptionOptionProvider stateDescriptionProvider;
|
||||
|
||||
private final HttpClient httpClient;
|
||||
|
||||
@Override
|
||||
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
|
||||
return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
|
||||
@@ -53,9 +57,11 @@ public class NuvoHandlerFactory extends BaseThingHandlerFactory {
|
||||
|
||||
@Activate
|
||||
public NuvoHandlerFactory(final @Reference NuvoStateDescriptionOptionProvider provider,
|
||||
final @Reference SerialPortManager serialPortManager) {
|
||||
final @Reference SerialPortManager serialPortManager,
|
||||
final @Reference HttpClientFactory httpClientFactory) {
|
||||
this.stateDescriptionProvider = provider;
|
||||
this.serialPortManager = serialPortManager;
|
||||
this.httpClient = httpClientFactory.getCommonHttpClient();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -63,7 +69,7 @@ public class NuvoHandlerFactory extends BaseThingHandlerFactory {
|
||||
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
|
||||
|
||||
if (SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID)) {
|
||||
return new NuvoHandler(thing, stateDescriptionProvider, serialPortManager);
|
||||
return new NuvoHandler(thing, stateDescriptionProvider, serialPortManager, httpClient);
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
@@ -41,19 +41,35 @@ public abstract class NuvoConnector {
|
||||
private static final String BEGIN_CMD = "*";
|
||||
private static final String END_CMD = "\r";
|
||||
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 MUTE = "#MUTE";
|
||||
private static final String PAGE = "#PAGE";
|
||||
private static final String RESTART = "#RESTART\"NuVoNet\"";
|
||||
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 Pattern SRC_PATTERN = Pattern.compile("^#S(\\d{1})(.*)$");
|
||||
private static final Pattern ZONE_PATTERN = Pattern.compile("^#Z(\\d{1,2}),(.*)$");
|
||||
private static final Pattern ZONE_BUTTON_PATTERN = Pattern.compile("^#Z(\\d{1,2})S(\\d{1})(.*)$");
|
||||
private static final Pattern ZONE_MENUREQ_PATTERN = Pattern.compile("^#Z(\\d{1,2})S(\\d{1})MENUREQ(.*)$");
|
||||
private static final Pattern ZONE_BUTTON2_PATTERN = Pattern.compile("^#Z(\\d{1,2})S(\\d{1})BUTTON(.*)$");
|
||||
private static final Pattern ZONE_BUTTONTWO_PATTERN = Pattern.compile("^#Z(\\d{1,2})S(\\d{1})BUTTONTWO(.*)$");
|
||||
|
||||
private static final Pattern ZONE_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);
|
||||
|
||||
protected static final String COMMAND_ERROR = "#?";
|
||||
@@ -306,11 +322,21 @@ public abstract class NuvoConnector {
|
||||
}
|
||||
|
||||
if (message.contains(PING)) {
|
||||
try {
|
||||
sendCommand(PING_RESPONSE);
|
||||
} catch (NuvoException e) {
|
||||
logger.debug("Error sending response to PING command");
|
||||
}
|
||||
dispatchKeyValue(TYPE_PING, BLANK, BLANK);
|
||||
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"
|
||||
// split on " and return the version number
|
||||
dispatchKeyValue(TYPE_VERSION, "", message.split("\"")[1]);
|
||||
@@ -332,16 +358,40 @@ public abstract class NuvoConnector {
|
||||
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"
|
||||
Matcher matcher = SRC_PATTERN.matcher(message);
|
||||
matcher = SRC_PATTERN.matcher(message);
|
||||
if (matcher.find()) {
|
||||
// pull out the source id and the remainder of the message
|
||||
dispatchKeyValue(TYPE_SOURCE_UPDATE, matcher.group(1), matcher.group(2));
|
||||
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);
|
||||
if (matcher.find()) {
|
||||
// pull out the zone id and the remainder of the message
|
||||
@@ -349,7 +399,43 @@ public abstract class NuvoConnector {
|
||||
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);
|
||||
if (matcher.find()) {
|
||||
// pull out the source id and the remainder of the message, ignore the zone id
|
||||
@@ -357,7 +443,7 @@ public abstract class NuvoConnector {
|
||||
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);
|
||||
if (matcher.find()) {
|
||||
// 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;
|
||||
|
||||
import java.util.HashMap;
|
||||
import static java.util.Map.entry;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
@@ -32,22 +33,21 @@ public class NuvoStatusCodes {
|
||||
private static final String ZERO = "0";
|
||||
|
||||
// map to lookup play mode
|
||||
public static final Map<String, String> PLAY_MODE = new HashMap<>();
|
||||
static {
|
||||
PLAY_MODE.put("0", "Normal");
|
||||
PLAY_MODE.put("1", "Idle");
|
||||
PLAY_MODE.put("2", "Playing");
|
||||
PLAY_MODE.put("3", "Paused");
|
||||
PLAY_MODE.put("4", "Fast Forward");
|
||||
PLAY_MODE.put("5", "Rewind");
|
||||
PLAY_MODE.put("6", "Play Shuffle");
|
||||
PLAY_MODE.put("7", "Play Repeat");
|
||||
PLAY_MODE.put("8", "Play Shuffle Repeat");
|
||||
PLAY_MODE.put("9", "Step Tune");
|
||||
PLAY_MODE.put("10", "Seek Tune");
|
||||
PLAY_MODE.put("11", "Preset Tune");
|
||||
PLAY_MODE.put("12", "unknown-12");
|
||||
}
|
||||
public static final Map<String, String> PLAY_MODE = Map.ofEntries(entry("0", "Normal"), entry("1", "Idle"),
|
||||
entry("2", "Playing"), entry("3", "Paused"), entry("4", "Fast Forward"), entry("5", "Rewind"),
|
||||
entry("6", "Play Shuffle"), entry("7", "Play Repeat"), entry("8", "Play Shuffle Repeat"),
|
||||
entry("9", "Step Tune"), entry("10", "Seek Tune"), entry("11", "Preset Tune"), entry("12", "unknown-12"));
|
||||
|
||||
// map to lookup button action name from NuvoNet button code
|
||||
public static final Map<String, String> BUTTON_CODE = Map.ofEntries(entry("1", "OK"), entry("2", "PLAYPAUSE"),
|
||||
entry("3", "PREV"), entry("4", "NEXT"), entry("5", "POWERMUTE"), // source will not receive this
|
||||
entry("6", "UP"), // source will not receive this
|
||||
entry("7", "DOWN"), // source will not receive this
|
||||
entry("41", "DISCRETEPLAYPAUSE"), entry("42", "DISCRETENEXTTRACK"), entry("43", "DISCRETEPREVIOUSTRACK"),
|
||||
entry("44", "SHUFFLETOGGLE"), entry("45", "REPEATTOGGLE"), entry("46", "TUNEUP"), entry("47", "TUNEDOWN"),
|
||||
entry("48", "SEEKUP"), entry("49", "SEEKDOWN"), entry("50", "PRESETUP"), entry("51", "PRESETDOWN"),
|
||||
entry("52", "DIRECTFREQUENCYENTRY"), entry("53", "DIRECTPRESETENTRY"), entry("54", "NEXTBAND"),
|
||||
entry("55", "THUMBSUP"), entry("56", "THUMBSDOWN"));
|
||||
|
||||
/*
|
||||
* This looks broken because the controller is seriously broken...
|
||||
|
||||
@@ -28,4 +28,29 @@ public class NuvoThingConfiguration {
|
||||
public @Nullable Integer port;
|
||||
public @Nullable Integer numZones;
|
||||
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;
|
||||
|
||||
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 java.io.StringReader;
|
||||
import java.math.BigDecimal;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Base64;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
@@ -33,9 +40,16 @@ import java.util.stream.IntStream;
|
||||
|
||||
import javax.measure.Unit;
|
||||
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.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.NuvoStateDescriptionOptionProvider;
|
||||
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.NuvoDefaultConnector;
|
||||
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.NuvoMessageEvent;
|
||||
import org.openhab.binding.nuvo.internal.communication.NuvoMessageEventListener;
|
||||
import org.openhab.binding.nuvo.internal.communication.NuvoSerialConnector;
|
||||
import org.openhab.binding.nuvo.internal.communication.NuvoStatusCodes;
|
||||
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.library.types.DecimalType;
|
||||
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 byte[] NO_ART = { 0 };
|
||||
|
||||
private static final Pattern ZONE_PATTERN = Pattern
|
||||
.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}),\"(.*)\"$");
|
||||
@@ -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})$");
|
||||
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 NuvoStateDescriptionOptionProvider stateDescriptionProvider;
|
||||
private final SerialPortManager serialPortManager;
|
||||
private final HttpClient httpClient;
|
||||
|
||||
private @Nullable ScheduledFuture<?> reconnectJob;
|
||||
private @Nullable ScheduledFuture<?> pollingJob;
|
||||
@@ -133,6 +152,16 @@ public class NuvoHandler extends BaseThingHandler implements NuvoMessageEventLis
|
||||
private boolean isGConcerto = false;
|
||||
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);
|
||||
|
||||
// A tree map that maps the source ids to source labels
|
||||
@@ -146,10 +175,11 @@ public class NuvoHandler extends BaseThingHandler implements NuvoMessageEventLis
|
||||
* Constructor
|
||||
*/
|
||||
public NuvoHandler(Thing thing, NuvoStateDescriptionOptionProvider stateDescriptionProvider,
|
||||
SerialPortManager serialPortManager) {
|
||||
SerialPortManager serialPortManager, HttpClient httpClient) {
|
||||
super(thing);
|
||||
this.stateDescriptionProvider = stateDescriptionProvider;
|
||||
this.serialPortManager = serialPortManager;
|
||||
this.httpClient = httpClient;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -187,15 +217,65 @@ public class NuvoHandler extends BaseThingHandler implements NuvoMessageEventLis
|
||||
} else if (port != null) {
|
||||
connector = new NuvoIpConnector(host, port, uid);
|
||||
this.isMps4 = (port.intValue() == MPS4_PORT);
|
||||
if (this.isMps4) {
|
||||
logger.debug("Port set to {} configuring binding for MPS4 compatability", MPS4_PORT);
|
||||
}
|
||||
} else {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
||||
"Either Serial port or Host & Port must be specifed");
|
||||
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) {
|
||||
this.numZones = numZones;
|
||||
}
|
||||
@@ -213,6 +293,25 @@ public class NuvoHandler extends BaseThingHandler implements NuvoMessageEventLis
|
||||
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) {
|
||||
scheduleClockSyncJob();
|
||||
}
|
||||
@@ -225,6 +324,37 @@ public class NuvoHandler extends BaseThingHandler implements NuvoMessageEventLis
|
||||
|
||||
@Override
|
||||
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();
|
||||
cancelPollingJob();
|
||||
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 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);
|
||||
}
|
||||
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) {
|
||||
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");
|
||||
}
|
||||
break;
|
||||
case TYPE_RESTART:
|
||||
logger.debug("Restart message received; re-sending initialization messages");
|
||||
enableNuvonet(false);
|
||||
return;
|
||||
case TYPE_PING:
|
||||
logger.debug("Ping message received- rescheduling ping timeout");
|
||||
schedulePingTimeoutJob();
|
||||
@@ -560,6 +742,85 @@ public class NuvoHandler extends BaseThingHandler implements NuvoMessageEventLis
|
||||
case TYPE_ZONE_BUTTON:
|
||||
logger.debug("Zone Button pressed: Source: {} - Button: {}", key, 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;
|
||||
case TYPE_ZONE_CONFIG:
|
||||
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);
|
||||
}
|
||||
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:
|
||||
logger.debug("onNewMessageEvent: unhandled key {}", key);
|
||||
// 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
|
||||
*/
|
||||
@@ -603,6 +1048,7 @@ public class NuvoHandler extends BaseThingHandler implements NuvoMessageEventLis
|
||||
if (!isMps4) {
|
||||
pollStatus();
|
||||
}
|
||||
enableNuvonet(true);
|
||||
} else {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Reconnection failed");
|
||||
closeConnection();
|
||||
@@ -760,7 +1206,8 @@ public class NuvoHandler extends BaseThingHandler implements NuvoMessageEventLis
|
||||
clockSyncJob = scheduler.scheduleWithFixedDelay(() -> {
|
||||
if (this.isGConcerto) {
|
||||
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) {
|
||||
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.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.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.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.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
|
||||
@@ -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.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.description = Adjust the Balance Setting for the Zone
|
||||
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.favorite.label = Favorite
|
||||
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.description = Indicates If This Zone Is Locked
|
||||
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.play_mode.label = Play Mode
|
||||
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.description = Select the Source Input for the Zone
|
||||
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>
|
||||
<default>6</default>
|
||||
</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">
|
||||
<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
|
||||
@@ -152,6 +157,180 @@
|
||||
so this setting has no effect on that component.</description>
|
||||
<default>false</default>
|
||||
</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>
|
||||
</thing-type>
|
||||
|
||||
@@ -162,6 +341,7 @@
|
||||
<channel id="alloff" typeId="alloff"/>
|
||||
<channel id="allmute" typeId="system.mute"/>
|
||||
<channel id="page" typeId="page"/>
|
||||
<channel id="sendcmd" typeId="sendcmd"/>
|
||||
</channels>
|
||||
</channel-group-type>
|
||||
|
||||
@@ -197,6 +377,7 @@
|
||||
<channel id="track_length" typeId="track_length"/>
|
||||
<channel id="track_position" typeId="track_position"/>
|
||||
<channel id="button_press" typeId="button_press"/>
|
||||
<channel id="art_url" typeId="art_url"/>
|
||||
</channels>
|
||||
</channel-group-type>
|
||||
|
||||
@@ -212,6 +393,12 @@
|
||||
<description>Activates the Page Mode for All Zones</description>
|
||||
</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">
|
||||
<item-type>Number</item-type>
|
||||
<label>Source Input</label>
|
||||
@@ -222,22 +409,6 @@
|
||||
<item-type>Number</item-type>
|
||||
<label>Favorite</label>
|
||||
<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 id="control">
|
||||
@@ -350,4 +521,10 @@
|
||||
<state readOnly="true"/>
|
||||
</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>
|
||||
|
||||
Reference in New Issue
Block a user