diff --git a/bundles/org.openhab.binding.nuvo/README.md b/bundles/org.openhab.binding.nuvo/README.md index 2ff5ba64114..031173caa3e 100644 --- a/bundles/org.openhab.binding.nuvo/README.md +++ b/bundles/org.openhab.binding.nuvo/README.md @@ -28,6 +28,15 @@ It has the `amplifier` id. Discovery is not supported. You have to add all things manually. +## Binding Configuration + +The binding has the following configuration parameters: + +| Parameter Label | Parameter ID | Description | Accepted Values | +|--------------------------|--------------- |---------------------------------------------------------------|-----------------------| +| Image Height | imageHeight | Height (in pixels) for album art images loaded from the MPS4. | 1 - 1024; default 150 | +| Image Width | imageWidth | Width (in pixels) for album art images loaded from the MPS4. | 1 - 1024; default 150 | + ## Thing Configuration The thing has the following configuration parameters: diff --git a/bundles/org.openhab.binding.nuvo/src/main/java/org/openhab/binding/nuvo/internal/NuvoBindingConstants.java b/bundles/org.openhab.binding.nuvo/src/main/java/org/openhab/binding/nuvo/internal/NuvoBindingConstants.java index 8057d3b3612..60218c8200f 100644 --- a/bundles/org.openhab.binding.nuvo/src/main/java/org/openhab/binding/nuvo/internal/NuvoBindingConstants.java +++ b/bundles/org.openhab.binding.nuvo/src/main/java/org/openhab/binding/nuvo/internal/NuvoBindingConstants.java @@ -120,5 +120,5 @@ public class NuvoBindingConstants { 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"; + public static final String GET_MCS_ART = "http://%s/getArt?guid=%s&instance=%s&h=%s&w=%s&changed=true&c=1&fmt=jpg"; } diff --git a/bundles/org.openhab.binding.nuvo/src/main/java/org/openhab/binding/nuvo/internal/NuvoHandlerFactory.java b/bundles/org.openhab.binding.nuvo/src/main/java/org/openhab/binding/nuvo/internal/NuvoHandlerFactory.java index e6b8b3b3cba..c58db80dfa4 100644 --- a/bundles/org.openhab.binding.nuvo/src/main/java/org/openhab/binding/nuvo/internal/NuvoHandlerFactory.java +++ b/bundles/org.openhab.binding.nuvo/src/main/java/org/openhab/binding/nuvo/internal/NuvoHandlerFactory.java @@ -14,11 +14,13 @@ package org.openhab.binding.nuvo.internal; import static org.openhab.binding.nuvo.internal.NuvoBindingConstants.*; +import java.util.Map; 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.configuration.NuvoBindingConfiguration; import org.openhab.binding.nuvo.internal.handler.NuvoHandler; import org.openhab.core.io.net.http.HttpClientFactory; import org.openhab.core.io.transport.serial.SerialPortManager; @@ -27,6 +29,7 @@ import org.openhab.core.thing.ThingTypeUID; import org.openhab.core.thing.binding.BaseThingHandlerFactory; import org.openhab.core.thing.binding.ThingHandler; import org.openhab.core.thing.binding.ThingHandlerFactory; +import org.osgi.service.component.ComponentContext; import org.osgi.service.component.annotations.Activate; import org.osgi.service.component.annotations.Component; import org.osgi.service.component.annotations.Reference; @@ -49,6 +52,8 @@ public class NuvoHandlerFactory extends BaseThingHandlerFactory { private final HttpClient httpClient; + private final NuvoBindingConfiguration bindingConf; + @Override public boolean supportsThingType(ThingTypeUID thingTypeUID) { return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID); @@ -56,11 +61,13 @@ public class NuvoHandlerFactory extends BaseThingHandlerFactory { @Activate public NuvoHandlerFactory(final @Reference NuvoStateDescriptionOptionProvider provider, - final @Reference SerialPortManager serialPortManager, - final @Reference HttpClientFactory httpClientFactory) { + final @Reference SerialPortManager serialPortManager, final @Reference HttpClientFactory httpClientFactory, + ComponentContext componentContext, Map config) { + super.activate(componentContext); this.stateDescriptionProvider = provider; this.serialPortManager = serialPortManager; this.httpClient = httpClientFactory.getCommonHttpClient(); + this.bindingConf = new NuvoBindingConfiguration(config); } @Override @@ -68,7 +75,7 @@ public class NuvoHandlerFactory extends BaseThingHandlerFactory { ThingTypeUID thingTypeUID = thing.getThingTypeUID(); if (SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID)) { - return new NuvoHandler(thing, stateDescriptionProvider, serialPortManager, httpClient); + return new NuvoHandler(thing, stateDescriptionProvider, serialPortManager, httpClient, bindingConf); } return null; diff --git a/bundles/org.openhab.binding.nuvo/src/main/java/org/openhab/binding/nuvo/internal/configuration/NuvoBindingConfiguration.java b/bundles/org.openhab.binding.nuvo/src/main/java/org/openhab/binding/nuvo/internal/configuration/NuvoBindingConfiguration.java new file mode 100644 index 00000000000..2a32bb2143d --- /dev/null +++ b/bundles/org.openhab.binding.nuvo/src/main/java/org/openhab/binding/nuvo/internal/configuration/NuvoBindingConfiguration.java @@ -0,0 +1,33 @@ +/** + * Copyright (c) 2010-2024 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.configuration; + +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link NuvoBindingConfiguration} is responsible for holding configuration of the binding itself. + * + * @author Michael Lobstein - Initial contribution + */ +@NonNullByDefault +public class NuvoBindingConfiguration { + public final String imageHeight; + public final String imageWidth; + + public NuvoBindingConfiguration(Map config) { + this.imageHeight = (String) config.getOrDefault("imageHeight", "150"); + this.imageWidth = (String) config.getOrDefault("imageWidth", "150"); + } +} diff --git a/bundles/org.openhab.binding.nuvo/src/main/java/org/openhab/binding/nuvo/internal/handler/NuvoHandler.java b/bundles/org.openhab.binding.nuvo/src/main/java/org/openhab/binding/nuvo/internal/handler/NuvoHandler.java index a7b00609355..f91662a3ba2 100644 --- a/bundles/org.openhab.binding.nuvo/src/main/java/org/openhab/binding/nuvo/internal/handler/NuvoHandler.java +++ b/bundles/org.openhab.binding.nuvo/src/main/java/org/openhab/binding/nuvo/internal/handler/NuvoHandler.java @@ -62,6 +62,7 @@ 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.NuvoBindingConfiguration; import org.openhab.binding.nuvo.internal.configuration.NuvoThingConfiguration; import org.openhab.binding.nuvo.internal.dto.JAXBUtils; import org.openhab.binding.nuvo.internal.dto.NuvoMenu; @@ -140,6 +141,7 @@ public class NuvoHandler extends BaseThingHandler implements NuvoMessageEventLis private static final Pattern ART_GUID_PATTERN = Pattern.compile("NowPlayingGuid\",\"value\":\"\\{(.*?)\\}\"\\}"); private final Logger logger = LoggerFactory.getLogger(NuvoHandler.class); + private final NuvoBindingConfiguration bindingConf; private final NuvoStateDescriptionOptionProvider stateDescriptionProvider; private final SerialPortManager serialPortManager; private final HttpClient httpClient; @@ -184,11 +186,12 @@ public class NuvoHandler extends BaseThingHandler implements NuvoMessageEventLis * Constructor */ public NuvoHandler(Thing thing, NuvoStateDescriptionOptionProvider stateDescriptionProvider, - SerialPortManager serialPortManager, HttpClient httpClient) { + SerialPortManager serialPortManager, HttpClient httpClient, NuvoBindingConfiguration bindingConf) { super(thing); this.stateDescriptionProvider = stateDescriptionProvider; this.serialPortManager = serialPortManager; this.httpClient = httpClient; + this.bindingConf = bindingConf; } @Override @@ -377,12 +380,13 @@ public class NuvoHandler extends BaseThingHandler implements NuvoMessageEventLis }); // need '1' flag for sources configured as an MPS4 NuvoNet source, but disable openHAB NuvoNet sources - connector.sendCommand("SNUMBERS" + (nuvoNetSrcMap.get(NuvoEnum.SOURCE1).equals(1) ? ONE : ZERO) + COMMA - + (nuvoNetSrcMap.get(NuvoEnum.SOURCE2).equals(1) ? ONE : ZERO) + COMMA - + (nuvoNetSrcMap.get(NuvoEnum.SOURCE3).equals(1) ? ONE : ZERO) + COMMA - + (nuvoNetSrcMap.get(NuvoEnum.SOURCE4).equals(1) ? ONE : ZERO) + COMMA - + (nuvoNetSrcMap.get(NuvoEnum.SOURCE5).equals(1) ? ONE : ZERO) + COMMA - + (nuvoNetSrcMap.get(NuvoEnum.SOURCE6).equals(1) ? ONE : ZERO)); + connector.sendCommand( + "SNUMBERS" + (nuvoNetSrcMap.getOrDefault(NuvoEnum.SOURCE1, 0).equals(1) ? ONE : ZERO) + COMMA + + (nuvoNetSrcMap.getOrDefault(NuvoEnum.SOURCE2, 0).equals(1) ? ONE : ZERO) + COMMA + + (nuvoNetSrcMap.getOrDefault(NuvoEnum.SOURCE3, 0).equals(1) ? ONE : ZERO) + COMMA + + (nuvoNetSrcMap.getOrDefault(NuvoEnum.SOURCE4, 0).equals(1) ? ONE : ZERO) + COMMA + + (nuvoNetSrcMap.getOrDefault(NuvoEnum.SOURCE5, 0).equals(1) ? ONE : ZERO) + COMMA + + (nuvoNetSrcMap.getOrDefault(NuvoEnum.SOURCE6, 0).equals(1) ? ONE : ZERO)); } catch (NuvoException e) { logger.debug("Error sending SNUMBERS command to disable NuvoNet sources"); } @@ -467,7 +471,7 @@ public class NuvoHandler extends BaseThingHandler implements NuvoMessageEventLis String sourceNum = String.valueOf(value / 100); NuvoEnum source = NuvoEnum.valueOf(SOURCE + sourceNum); updateChannelState(source, CHANNEL_BUTTON_PRESS, - PLAY_MUSIC_PRESET + favoriteMap.get(source)[value % 100]); + PLAY_MUSIC_PRESET + getFavorite(source, value % 100)); connector.sendCommand(target, NuvoCommand.SOURCE, sourceNum); // if this zone is in a group, update the other group member's selected source @@ -595,7 +599,7 @@ public class NuvoHandler extends BaseThingHandler implements NuvoMessageEventLis // if 'albumartid' is present, substitute it with the albumArtId hex string connector.sendCommand(commandStr.replace(ALBUM_ART_ID, - (OFFSET_ZERO + Integer.toHexString(albumArtIds.get(source))))); + (OFFSET_ZERO + Integer.toHexString(albumArtIds.getOrDefault(source, 0))))); } else { connector.sendCommand(commandStr); } @@ -631,8 +635,9 @@ public class NuvoHandler extends BaseThingHandler implements NuvoMessageEventLis // re-send the cached DISPINFOTWO message, substituting in the new albumArtId if (dispInfoCache.get(target) != null) { - connector.sendCommand(dispInfoCache.get(target).replace(ALBUM_ART_ID, - (OFFSET_ZERO + Integer.toHexString(albumArtIds.get(target))))); + connector.sendCommand(dispInfoCache.getOrDefault(target, BLANK).replace( + ALBUM_ART_ID, + (OFFSET_ZERO + Integer.toHexString(albumArtIds.getOrDefault(target, 0))))); } } else { albumArtMap.put(target, NO_ART); @@ -842,7 +847,7 @@ public class NuvoHandler extends BaseThingHandler implements NuvoMessageEventLis break; case TYPE_NN_MENU_ITEM_SELECTED: // ignore this update unless openHAB is handling this source - if (nuvoNetSrcMap.get(source).equals(2)) { + if (nuvoNetSrcMap.getOrDefault(source, 0).equals(2)) { String[] updateDataSplit = updateData.split(COMMA); String menuId = updateDataSplit[0]; int menuItemIdx = Integer.parseInt(updateDataSplit[1]) - 1; @@ -903,7 +908,7 @@ public class NuvoHandler extends BaseThingHandler implements NuvoMessageEventLis break; case TYPE_NN_MENUREQ: // ignore this update unless openHAB is handling this source - if (nuvoNetSrcMap.get(source).equals(2)) { + if (nuvoNetSrcMap.getOrDefault(source, 0).equals(2)) { logger.debug("Menu Request: Source: {} - Value: {}", source.getNum(), updateData); // For now we only support one level deep menus. If second field is '1', indicates go back to main // menu. @@ -934,7 +939,7 @@ public class NuvoHandler extends BaseThingHandler implements NuvoMessageEventLis // if this zone is a member of a group (1-4), add the zone's enum to the appropriate group map if (!ZERO.equals(matcher.group(3))) { - nuvoGroupMap.get(matcher.group(3)).add(zone); + nuvoGroupMap.getOrDefault(matcher.group(3), new HashSet<>()).add(zone); } } else { logger.debug("no match on message: {}", updateData); @@ -943,16 +948,17 @@ public class NuvoHandler extends BaseThingHandler implements NuvoMessageEventLis break; case TYPE_NN_ALBUM_ART_REQ: // ignore this update unless openHAB is handling this source - if (nuvoNetSrcMap.get(source).equals(2)) { + if (nuvoNetSrcMap.getOrDefault(source, 0).equals(2)) { logger.debug("Album Art Request for Source: {} - Data: {}", source.getNum(), updateData); // 0x620FD879,80,80,2,0x00C0C0C0,0,0,0,0,1 String[] albumArtReq = updateData.split(COMMA); albumArtIds.put(source, Integer.decode(albumArtReq[0])); try { - if (albumArtMap.get(source).length > 1) { - connector.sendCommand(source.getId() + ALBUM_ART_AVAILABLE + albumArtIds.get(source) + COMMA - + albumArtMap.get(source).length); + if (albumArtMap.getOrDefault(source, NO_ART).length > 1) { + connector.sendCommand( + source.getId() + ALBUM_ART_AVAILABLE + albumArtIds.getOrDefault(source, 0) + COMMA + + albumArtMap.getOrDefault(source, NO_ART).length); } else { connector.sendCommand(source.getId() + ALBUM_ART_AVAILABLE + ZERO_COMMA); } @@ -963,7 +969,7 @@ public class NuvoHandler extends BaseThingHandler implements NuvoMessageEventLis break; case TYPE_NN_ALBUM_ART_FRAG_REQ: // ignore this update unless openHAB is handling this source - if (nuvoNetSrcMap.get(source).equals(2)) { + if (nuvoNetSrcMap.getOrDefault(source, 0).equals(2)) { logger.debug("Album Art Fragment Request for Source: {} - Data: {}", source.getNum(), updateData); // 0x620FD879,0,750 (id, requested offset from start of image, byte length requested) String[] albumArtFragReq = updateData.split(COMMA); @@ -991,12 +997,12 @@ public class NuvoHandler extends BaseThingHandler implements NuvoMessageEventLis break; case TYPE_NN_FAVORITE_REQ: // ignore this update unless openHAB is handling this source - if (nuvoNetSrcMap.get(source).equals(2)) { + if (nuvoNetSrcMap.getOrDefault(source, 0).equals(2)) { logger.debug("Favorite request for source: {} - favoriteId: {}", source.getNum(), updateData); try { int playlistIdx = Integer.parseInt(updateData, 16) - 1000; updateChannelState(source, CHANNEL_BUTTON_PRESS, - PLAY_MUSIC_PRESET + favoriteMap.get(source)[playlistIdx]); + PLAY_MUSIC_PRESET + getFavorite(source, playlistIdx)); } catch (NumberFormatException nfe) { logger.debug("Unable to parse favoriteId: {}", updateData); } @@ -1087,12 +1093,12 @@ public class NuvoHandler extends BaseThingHandler implements NuvoMessageEventLis try { // set '1' flag for each source configured as an MPS4 NuvoNet source or openHAB NuvoNet source - connector.sendCommand("SNUMBERS" + nuvoNetSrcMap.get(NuvoEnum.SOURCE1).compareTo(0) + COMMA - + nuvoNetSrcMap.get(NuvoEnum.SOURCE2).compareTo(0) + COMMA - + nuvoNetSrcMap.get(NuvoEnum.SOURCE3).compareTo(0) + COMMA - + nuvoNetSrcMap.get(NuvoEnum.SOURCE4).compareTo(0) + COMMA - + nuvoNetSrcMap.get(NuvoEnum.SOURCE5).compareTo(0) + COMMA - + nuvoNetSrcMap.get(NuvoEnum.SOURCE6).compareTo(0)); + connector.sendCommand("SNUMBERS" + nuvoNetSrcMap.getOrDefault(NuvoEnum.SOURCE1, 0).compareTo(0) + COMMA + + nuvoNetSrcMap.getOrDefault(NuvoEnum.SOURCE2, 0).compareTo(0) + COMMA + + nuvoNetSrcMap.getOrDefault(NuvoEnum.SOURCE3, 0).compareTo(0) + COMMA + + nuvoNetSrcMap.getOrDefault(NuvoEnum.SOURCE4, 0).compareTo(0) + COMMA + + nuvoNetSrcMap.getOrDefault(NuvoEnum.SOURCE5, 0).compareTo(0) + COMMA + + nuvoNetSrcMap.getOrDefault(NuvoEnum.SOURCE6, 0).compareTo(0)); Thread.sleep(SLEEP_BETWEEN_CMD_MS); } catch (NuvoException | InterruptedException e) { logger.debug("Error sending SNUMBERS command"); @@ -1533,13 +1539,15 @@ public class NuvoHandler extends BaseThingHandler implements NuvoMessageEventLis if (matcher.find()) { final String nowPlayingGuid = matcher.group(1); - // If streaming (NowPlayingType=Radio), always retrieve because the same NowPlayingGuid can + // If streaming (not local mp3 or flac) 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\"}")) { + if (!mps4ArtGuids.getOrDefault(source, BLANK).equals(nowPlayingGuid) + || !(json.contains("NowPlayingSrce\",\"value\":\"WMP\"") + || json.contains("NowPlayingSrce\",\"value\":\"Flac\""))) { ContentResponse artResponse = httpClient - .newRequest(String.format(GET_MCS_ART, mps4Host, nowPlayingGuid, instance)).method(GET) - .timeout(10, TimeUnit.SECONDS).send(); + .newRequest(String.format(GET_MCS_ART, mps4Host, nowPlayingGuid, instance, + bindingConf.imageHeight, bindingConf.imageWidth)) + .method(GET).timeout(10, TimeUnit.SECONDS).send(); if (artResponse.getStatus() == OK_200) { logger.debug("Retrieved album art for guid: {}", nowPlayingGuid); @@ -1597,4 +1605,9 @@ public class NuvoHandler extends BaseThingHandler implements NuvoMessageEventLis logger.debug("Got error response {} when sending json command url: {}", commandResp.getStatus(), commandUrl); return BLANK; } + + private String getFavorite(NuvoEnum source, int playlistIdx) { + final String[] favoritesArr = favoriteMap.get(source); + return favoritesArr != null ? favoritesArr[playlistIdx] : BLANK; + } } diff --git a/bundles/org.openhab.binding.nuvo/src/main/resources/OH-INF/addon/addon.xml b/bundles/org.openhab.binding.nuvo/src/main/resources/OH-INF/addon/addon.xml index 3584c983634..58c2798e3dc 100644 --- a/bundles/org.openhab.binding.nuvo/src/main/resources/OH-INF/addon/addon.xml +++ b/bundles/org.openhab.binding.nuvo/src/main/resources/OH-INF/addon/addon.xml @@ -8,6 +8,21 @@ Controls the Nuvo Grand Concerto or Essentia G Whole House Amplifier. local + + + + Height for MPS4 album art images + 150 + true + + + + Width for MPS4 album art images + 150 + true + + + mdns diff --git a/bundles/org.openhab.binding.nuvo/src/main/resources/OH-INF/i18n/nuvo.properties b/bundles/org.openhab.binding.nuvo/src/main/resources/OH-INF/i18n/nuvo.properties index b31fb9cd1d9..361f25f7c02 100644 --- a/bundles/org.openhab.binding.nuvo/src/main/resources/OH-INF/i18n/nuvo.properties +++ b/bundles/org.openhab.binding.nuvo/src/main/resources/OH-INF/i18n/nuvo.properties @@ -3,6 +3,13 @@ addon.nuvo.name = Nuvo Whole House Audio Binding addon.nuvo.description = Controls the Nuvo Grand Concerto or Essentia G Whole House Amplifier. +# add-on config + +addon.config.nuvo.imageHeight.label = Image Height +addon.config.nuvo.imageHeight.description = Height for MPS4 album art images +addon.config.nuvo.imageWidth.label = Image Width +addon.config.nuvo.imageWidth.description = Width for MPS4 album art images + # thing types thing-type.nuvo.amplifier.label = Nuvo Whole House Amplifier