mirror of
https://github.com/openhab/openhab-addons.git
synced 2025-01-10 15:11:59 +01:00
[nuvo] Display album art from MPS4 (#16068)
* Display album art from MPS4 * Display album art from MPS4 --------- Signed-off-by: Michael Lobstein <michael.lobstein@gmail.com> Signed-off-by: Ciprian Pascu <contact@ciprianpascu.ro>
This commit is contained in:
parent
207f5ca038
commit
267063cffe
@ -13,6 +13,7 @@ For users without a serial connector on the server side, you can use a USB to se
|
||||
|
||||
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.
|
||||
If using MCS v5.35 or later on the server, content that is playing on MPS4 sources will display the album art to that source's Image channel.
|
||||
|
||||
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).
|
||||
@ -109,7 +110,7 @@ The following channels are available:
|
||||
| sourceN#track_position (where N= 1-6)| Number:Time | The running time elapsed of the current playing track (ReadOnly) See rules example for updating |
|
||||
| sourceN#button_press (where N= 1-6) | String | Indicates the last button pressed on the keypad for a non NuvoNet source or openHAB NuvoNet source (ReadOnly) |
|
||||
| sourceN#art_url (where N= 1-6) | String | MPS4 Only! The URL of the Album Art JPG for this source that is displayed on a CTP-36. See _very advanced_ rules (SendOnly) |
|
||||
| sourceN#album_art (where N= 1-6) | Image | The Album Art loaded from the art_url channel for display in a UI widget (ReadOnly) |
|
||||
| sourceN#album_art (where N= 1-6) | Image | The Album Art loaded from an MPS4 source or from the art_url channel for display in a UI widget (ReadOnly) |
|
||||
|
||||
## Full Example
|
||||
|
||||
|
@ -12,6 +12,8 @@
|
||||
*/
|
||||
package org.openhab.binding.nuvo.internal;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
|
||||
@ -110,4 +112,12 @@ public class NuvoBindingConstants {
|
||||
public static final String HTTP = "http://";
|
||||
public static final String HTTPS = "https://";
|
||||
public static final String PLAY_MUSIC_PRESET = "PLAY_MUSIC_PRESET:";
|
||||
|
||||
public static final List<String> MPS4_PLAYING_MODES = List.of("2", "6", "7", "8");
|
||||
public static final List<String> MPS4_IDLE_MODES = List.of("0", "1");
|
||||
|
||||
public static final String GET_MCS_INSTANCE = "http://%s/api/Script/MRAD.SetZone%%20Zone_%s/MRAD.GetStatus/?clientId=%s";
|
||||
public static final String GET_MCS_STATUS = "http://%s/api/Script/SetInstance%%20%s/GetStatus?clientId=%s";
|
||||
public static final String GET_MCS_JSON = "http://%s/api/?clientId=%s";
|
||||
public static final String GET_MCS_ART = "http://%s/getArt?guid=%s&instance=%s&h=143&w=143&changed=true&c=1&fmt=jpg";
|
||||
}
|
||||
|
@ -28,6 +28,7 @@ import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
@ -136,6 +137,8 @@ public class NuvoHandler extends BaseThingHandler implements NuvoMessageEventLis
|
||||
private static final Pattern ZONE_CFG_EQ_PATTERN = Pattern.compile("^BASS(.*),TREB(.*),BAL(.*),LOUDCMP([0-1])$");
|
||||
private static final Pattern ZONE_CFG_PATTERN = Pattern.compile(
|
||||
"^ENABLE1,NAME\"(.*)\",SLAVETO(.*),GROUP([0-4]),SOURCES(.*),XSRC(.*),IR(.*),DND(.*),LOCKED(.*),SLAVEEQ(.*)$");
|
||||
private static final Pattern MCS_INSTANCE_PATTERN = Pattern.compile("MCSInstance\",\"value\":\"(.*?)\"");
|
||||
private static final Pattern ART_GUID_PATTERN = Pattern.compile("NowPlayingGuid\",\"value\":\"\\{(.*?)\\}\"\\}");
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(NuvoHandler.class);
|
||||
private final NuvoStateDescriptionOptionProvider stateDescriptionProvider;
|
||||
@ -160,10 +163,13 @@ public class NuvoHandler extends BaseThingHandler implements NuvoMessageEventLis
|
||||
private HashMap<NuvoEnum, Integer> nuvoNetSrcMap = new HashMap<>();
|
||||
private HashMap<NuvoEnum, String> favPrefixMap = new HashMap<>();
|
||||
private HashMap<NuvoEnum, String[]> favoriteMap = new HashMap<>();
|
||||
private HashMap<NuvoEnum, NuvoEnum> sourceZoneMap = new HashMap<>();
|
||||
private HashMap<NuvoEnum, String> sourceInstanceMap = new HashMap<>();
|
||||
|
||||
private HashMap<NuvoEnum, byte[]> albumArtMap = new HashMap<>();
|
||||
private HashMap<NuvoEnum, Integer> albumArtIds = new HashMap<>();
|
||||
private HashMap<NuvoEnum, String> dispInfoCache = new HashMap<>();
|
||||
private HashMap<NuvoEnum, String> mps4ArtGuids = new HashMap<>();
|
||||
|
||||
Set<Integer> activeZones = new HashSet<>(1);
|
||||
|
||||
@ -173,6 +179,7 @@ public class NuvoHandler extends BaseThingHandler implements NuvoMessageEventLis
|
||||
// Indicates if there is a need to poll status because of a disconnection used for MPS4 systems
|
||||
boolean pollStatusNeeded = true;
|
||||
boolean isMps4 = false;
|
||||
String mps4Host = BLANK;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
@ -220,6 +227,7 @@ public class NuvoHandler extends BaseThingHandler implements NuvoMessageEventLis
|
||||
} else if (host != null && port != null) {
|
||||
connector = new NuvoIpConnector(host, port, uid);
|
||||
this.isMps4 = (port.intValue() == MPS4_PORT);
|
||||
mps4Host = host;
|
||||
} else {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
||||
"Either Serial port or Host & Port must be specifed");
|
||||
@ -245,6 +253,13 @@ public class NuvoHandler extends BaseThingHandler implements NuvoMessageEventLis
|
||||
|| config.nuvoNetSrc3.equals(2) || config.nuvoNetSrc4.equals(2) || config.nuvoNetSrc5.equals(2)
|
||||
|| config.nuvoNetSrc6.equals(2));
|
||||
|
||||
mps4ArtGuids.put(NuvoEnum.SOURCE1, BLANK);
|
||||
mps4ArtGuids.put(NuvoEnum.SOURCE2, BLANK);
|
||||
mps4ArtGuids.put(NuvoEnum.SOURCE3, BLANK);
|
||||
mps4ArtGuids.put(NuvoEnum.SOURCE4, BLANK);
|
||||
mps4ArtGuids.put(NuvoEnum.SOURCE5, BLANK);
|
||||
mps4ArtGuids.put(NuvoEnum.SOURCE6, BLANK);
|
||||
|
||||
if (this.isAnyOhNuvoNet) {
|
||||
logger.debug("At least one source is configured as an openHAB NuvoNet source");
|
||||
connector.setAnyOhNuvoNet(true);
|
||||
@ -442,6 +457,7 @@ public class NuvoHandler extends BaseThingHandler implements NuvoMessageEventLis
|
||||
|
||||
// update the other group member's selected source
|
||||
updateSrcForZoneGroup(target, String.valueOf(value));
|
||||
sourceZoneMap.put(NuvoEnum.valueOf(SOURCE + value), target);
|
||||
}
|
||||
}
|
||||
break;
|
||||
@ -747,6 +763,20 @@ public class NuvoHandler extends BaseThingHandler implements NuvoMessageEventLis
|
||||
updateChannelState(source, CHANNEL_TRACK_LENGTH, matcher.group(1));
|
||||
updateChannelState(source, CHANNEL_TRACK_POSITION, matcher.group(2));
|
||||
updateChannelState(source, CHANNEL_PLAY_MODE, matcher.group(3));
|
||||
|
||||
// if this is an MPS4 source, the following retrieves album art when the source is playing
|
||||
if (nuvoNetSrcMap.get(source) == 1
|
||||
&& isLinked(source.name().toLowerCase() + CHANNEL_DELIMIT + CHANNEL_ALBUM_ART)) {
|
||||
if (MPS4_PLAYING_MODES.contains(matcher.group(3))) {
|
||||
logger.debug("DISPINFO update, trying to get album art");
|
||||
getMps4AlbumArt(source);
|
||||
} else if (MPS4_IDLE_MODES.contains(matcher.group(3)) && ZERO.equals(matcher.group(1))) {
|
||||
// clear album art channel for this source
|
||||
logger.debug("DISPINFO update- not playing; clearing art, mode: {}", matcher.group(3));
|
||||
updateChannelState(source, CHANNEL_ALBUM_ART, UNDEF);
|
||||
mps4ArtGuids.put(source, BLANK);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
logger.debug("no match on message: {}", updateData);
|
||||
}
|
||||
@ -770,6 +800,7 @@ public class NuvoHandler extends BaseThingHandler implements NuvoMessageEventLis
|
||||
if (matcher.find()) {
|
||||
updateChannelState(zone, CHANNEL_TYPE_POWER, ON);
|
||||
updateChannelState(zone, CHANNEL_TYPE_SOURCE, matcher.group(1));
|
||||
sourceZoneMap.put(NuvoEnum.valueOf(SOURCE + matcher.group(1)), zone);
|
||||
|
||||
// update the other group member's selected source
|
||||
updateSrcForZoneGroup(zone, matcher.group(1));
|
||||
@ -1434,4 +1465,122 @@ public class NuvoHandler extends BaseThingHandler implements NuvoMessageEventLis
|
||||
logger.warn("Unknown control command: {}", command);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Scrapes the MPS4's json api to retrieve the currently playing media's album art
|
||||
*
|
||||
* @param source the source that should be queried to load the current album art
|
||||
*/
|
||||
private void getMps4AlbumArt(NuvoEnum source) {
|
||||
final String clientId = UUID.randomUUID().toString();
|
||||
|
||||
// try to get cached source instance
|
||||
String instance = sourceInstanceMap.get(source);
|
||||
|
||||
// if not found, need to retrieve from the api, once found these calls will be skipped
|
||||
if (instance == null) {
|
||||
// find which zone is using this source
|
||||
NuvoEnum zone = sourceZoneMap.get(source);
|
||||
|
||||
if (zone == null) {
|
||||
logger.debug("Unable to determine zone that is using source {}", source);
|
||||
return;
|
||||
} else {
|
||||
try {
|
||||
final String json = getMcsJson(String.format(GET_MCS_INSTANCE, mps4Host, zone.getNum(), clientId),
|
||||
clientId);
|
||||
|
||||
Matcher matcher = MCS_INSTANCE_PATTERN.matcher(json);
|
||||
if (matcher.find()) {
|
||||
instance = matcher.group(1);
|
||||
sourceInstanceMap.put(source, instance);
|
||||
logger.debug("Found instance '{}' for source {}", instance, source);
|
||||
} else {
|
||||
logger.debug("No instance match found for json: {}", json);
|
||||
return;
|
||||
}
|
||||
} catch (TimeoutException | ExecutionException e) {
|
||||
logger.debug("Failed getting instance name", e);
|
||||
return;
|
||||
} catch (InterruptedException e) {
|
||||
logger.debug("InterruptedException getting instance name", e);
|
||||
Thread.currentThread().interrupt();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
logger.debug("Using MCS instance '{}' for source {}", instance, source);
|
||||
final String json = getMcsJson(String.format(GET_MCS_STATUS, mps4Host, instance, clientId), clientId);
|
||||
|
||||
if (json.contains("\"name\":\"PlayState\",\"value\":3}")) {
|
||||
Matcher matcher = ART_GUID_PATTERN.matcher(json);
|
||||
if (matcher.find()) {
|
||||
final String nowPlayingGuid = matcher.group(1);
|
||||
|
||||
// If streaming (NowPlayingType=Radio), always retrieve because the same NowPlayingGuid can
|
||||
// get a different image written to it by Gracenote when the track changes
|
||||
if (!mps4ArtGuids.get(source).equals(nowPlayingGuid)
|
||||
|| json.contains("NowPlayingType\",\"value\":\"Radio\"}")) {
|
||||
ContentResponse artResponse = httpClient
|
||||
.newRequest(String.format(GET_MCS_ART, mps4Host, nowPlayingGuid, instance)).method(GET)
|
||||
.timeout(10, TimeUnit.SECONDS).send();
|
||||
|
||||
if (artResponse.getStatus() == OK_200) {
|
||||
logger.debug("Retrieved album art for guid: {}", nowPlayingGuid);
|
||||
updateChannelState(source, CHANNEL_ALBUM_ART, BLANK, artResponse.getContent());
|
||||
mps4ArtGuids.put(source, nowPlayingGuid);
|
||||
}
|
||||
} else {
|
||||
logger.debug("Album art has not changed, guid: {}", nowPlayingGuid);
|
||||
}
|
||||
} else {
|
||||
logger.debug("NowPlayingGuid not found");
|
||||
}
|
||||
} else {
|
||||
logger.debug("PlayState not valid");
|
||||
}
|
||||
} catch (TimeoutException | ExecutionException e) {
|
||||
logger.debug("Failed getting album art", e);
|
||||
updateChannelState(source, CHANNEL_ALBUM_ART, UNDEF);
|
||||
mps4ArtGuids.put(source, BLANK);
|
||||
} catch (InterruptedException e) {
|
||||
logger.debug("InterruptedException getting album art", e);
|
||||
updateChannelState(source, CHANNEL_ALBUM_ART, UNDEF);
|
||||
mps4ArtGuids.put(source, BLANK);
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Used by getMps4AlbumArt to abstract retrieval of status json from MCS
|
||||
*
|
||||
* @param commandUrl the url with the embedded commands to send to MCS
|
||||
* @param clientId the current clientId
|
||||
* @return string json result from the command executed
|
||||
*
|
||||
* @throws InterruptedException
|
||||
* @throws TimeoutException
|
||||
* @throws ExecutionException
|
||||
*/
|
||||
private String getMcsJson(String commandUrl, String clientId)
|
||||
throws InterruptedException, TimeoutException, ExecutionException {
|
||||
ContentResponse commandResp = httpClient.newRequest(commandUrl).method(GET).timeout(10, TimeUnit.SECONDS)
|
||||
.send();
|
||||
|
||||
if (commandResp.getStatus() == OK_200) {
|
||||
Thread.sleep(SLEEP_BETWEEN_CMD_MS);
|
||||
ContentResponse jsonResp = httpClient.newRequest(String.format(GET_MCS_JSON, mps4Host, clientId))
|
||||
.method(GET).timeout(10, TimeUnit.SECONDS).send();
|
||||
if (jsonResp.getStatus() == OK_200) {
|
||||
return jsonResp.getContentAsString();
|
||||
} else {
|
||||
logger.debug("Got error response {} when getting json from MCS", commandResp.getStatus());
|
||||
return BLANK;
|
||||
}
|
||||
}
|
||||
logger.debug("Got error response {} when sending json command url: {}", commandResp.getStatus(), commandUrl);
|
||||
return BLANK;
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user