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;
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;
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);
// 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
} 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");
|| 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);
// update the other group member's selected source
updateSrcForZoneGroup(target, String.valueOf(value));
+ sourceZoneMap.put(NuvoEnum.valueOf(SOURCE + value), target);
}
}
break;
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);
}
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));
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;
+ }
}