From 2eaa6400f716e26f3416b3cd73a6b5b278f3f02a Mon Sep 17 00:00:00 2001 From: Espen Fossen Date: Sat, 28 Sep 2024 15:28:35 +0200 Subject: [PATCH] [emotiva] Fix missing data in source channels (#17365) * [emotiva] Fixes issue with missing data in source channels. Signed-off-by: Espen Fossen --- .../internal/EmotivaBindingConstants.java | 3 + .../internal/EmotivaCommandHelper.java | 8 +- .../internal/EmotivaProcessorHandler.java | 213 ++++++++---------- .../internal/EmotivaProcessorState.java | 149 ++++++++++++ .../internal/EmotivaUdpReceivingService.java | 11 +- .../internal/EmotivaUdpSendingService.java | 15 +- .../internal/InputStateOptionProvider.java | 2 +- .../dto/EmotivaSubscriptionRequest.java | 21 +- .../internal/dto/EmotivaUnsubscribeDTO.java | 14 +- .../protocol/EmotivaControlRequest.java | 33 ++- .../protocol/EmotivaSubscriptionTags.java | 16 +- .../internal/EmotivaCommandHelperTest.java | 6 +- .../internal/EmotivaProcessorStateTest.java | 127 +++++++++++ .../dto/EmotivaSubscriptionRequestTest.java | 10 +- .../dto/EmotivaUnsubscriptionTest.java | 7 +- .../protocol/EmotivaControlRequestTest.java | 17 +- 16 files changed, 427 insertions(+), 225 deletions(-) create mode 100644 bundles/org.openhab.binding.emotiva/src/main/java/org/openhab/binding/emotiva/internal/EmotivaProcessorState.java create mode 100644 bundles/org.openhab.binding.emotiva/src/test/java/org/openhab/binding/emotiva/internal/EmotivaProcessorStateTest.java diff --git a/bundles/org.openhab.binding.emotiva/src/main/java/org/openhab/binding/emotiva/internal/EmotivaBindingConstants.java b/bundles/org.openhab.binding.emotiva/src/main/java/org/openhab/binding/emotiva/internal/EmotivaBindingConstants.java index 33d9fd0596eb3..316e941606aa8 100644 --- a/bundles/org.openhab.binding.emotiva/src/main/java/org/openhab/binding/emotiva/internal/EmotivaBindingConstants.java +++ b/bundles/org.openhab.binding.emotiva/src/main/java/org/openhab/binding/emotiva/internal/EmotivaBindingConstants.java @@ -86,6 +86,9 @@ public class EmotivaBindingConstants { public static final int DEFAULT_TRIM_MAX_DECIBEL = 12; public static final String MAP_SOURCES_MAIN_ZONE = "sources"; public static final String MAP_SOURCES_ZONE_2 = "zone2-sources"; + public static final String MAP_TUNER_CHANNELS = "tuner-channel"; + public static final String MAP_TUNER_BANDS = "tuner-bands"; + public static final String MAP_MODES = "modes"; /** Miscellaneous Constants **/ public static final int PROTOCOL_V3_LEVEL_MULTIPLIER = 2; diff --git a/bundles/org.openhab.binding.emotiva/src/main/java/org/openhab/binding/emotiva/internal/EmotivaCommandHelper.java b/bundles/org.openhab.binding.emotiva/src/main/java/org/openhab/binding/emotiva/internal/EmotivaCommandHelper.java index ec79969e85310..ab3e870c11444 100644 --- a/bundles/org.openhab.binding.emotiva/src/main/java/org/openhab/binding/emotiva/internal/EmotivaCommandHelper.java +++ b/bundles/org.openhab.binding.emotiva/src/main/java/org/openhab/binding/emotiva/internal/EmotivaCommandHelper.java @@ -14,8 +14,6 @@ import static org.openhab.binding.emotiva.internal.EmotivaBindingConstants.*; -import java.util.Map; - import org.eclipse.jdt.annotation.NonNullByDefault; import org.openhab.binding.emotiva.internal.protocol.EmotivaControlCommands; import org.openhab.binding.emotiva.internal.protocol.EmotivaControlRequest; @@ -69,11 +67,11 @@ private static int clamp(int volumeInPercentage, int min, int max) { return Math.min(Math.max(Double.valueOf(volumeInPercentage).intValue(), min), max); } - public static EmotivaControlRequest channelToControlRequest(String id, - Map> commandMaps, EmotivaProtocolVersion protocolVersion) { + public static EmotivaControlRequest channelToControlRequest(String id, EmotivaProcessorState state, + EmotivaProtocolVersion protocolVersion) { EmotivaSubscriptionTags channelSubscription = EmotivaSubscriptionTags.fromChannelUID(id); EmotivaControlCommands channelFromCommand = OHChannelToEmotivaCommand.fromChannelUID(id); - return new EmotivaControlRequest(id, channelSubscription, channelFromCommand, commandMaps, protocolVersion); + return new EmotivaControlRequest(id, channelSubscription, channelFromCommand, state, protocolVersion); } public static String getMenuPanelRowLabel(int row) { diff --git a/bundles/org.openhab.binding.emotiva/src/main/java/org/openhab/binding/emotiva/internal/EmotivaProcessorHandler.java b/bundles/org.openhab.binding.emotiva/src/main/java/org/openhab/binding/emotiva/internal/EmotivaProcessorHandler.java index 9b1d07becf04d..191a96f135000 100644 --- a/bundles/org.openhab.binding.emotiva/src/main/java/org/openhab/binding/emotiva/internal/EmotivaProcessorHandler.java +++ b/bundles/org.openhab.binding.emotiva/src/main/java/org/openhab/binding/emotiva/internal/EmotivaProcessorHandler.java @@ -20,17 +20,12 @@ import static org.openhab.binding.emotiva.internal.EmotivaCommandHelper.updateProgress; import static org.openhab.binding.emotiva.internal.EmotivaCommandHelper.volumeDecibelToPercentage; import static org.openhab.binding.emotiva.internal.EmotivaCommandHelper.volumePercentageToDecibel; -import static org.openhab.binding.emotiva.internal.protocol.EmotivaControlCommands.band_am; -import static org.openhab.binding.emotiva.internal.protocol.EmotivaControlCommands.band_fm; -import static org.openhab.binding.emotiva.internal.protocol.EmotivaControlCommands.channel_1; import static org.openhab.binding.emotiva.internal.protocol.EmotivaControlCommands.none; import static org.openhab.binding.emotiva.internal.protocol.EmotivaControlCommands.power_on; import static org.openhab.binding.emotiva.internal.protocol.EmotivaDataType.STRING; import static org.openhab.binding.emotiva.internal.protocol.EmotivaPropertyStatus.NOT_VALID; import static org.openhab.binding.emotiva.internal.protocol.EmotivaProtocolVersion.protocolFromConfig; import static org.openhab.binding.emotiva.internal.protocol.EmotivaSubscriptionTags.noSubscriptionToChannel; -import static org.openhab.binding.emotiva.internal.protocol.EmotivaSubscriptionTags.tuner_band; -import static org.openhab.binding.emotiva.internal.protocol.EmotivaSubscriptionTags.tuner_channel; import java.io.IOException; import java.io.InterruptedIOException; @@ -40,13 +35,10 @@ import java.time.Instant; import java.time.temporal.ChronoUnit; import java.util.Collection; -import java.util.Collections; import java.util.EnumMap; -import java.util.HashMap; import java.util.List; -import java.util.Map; +import java.util.Optional; import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; @@ -74,6 +66,7 @@ import org.openhab.binding.emotiva.internal.protocol.EmotivaUdpResponse; import org.openhab.binding.emotiva.internal.protocol.EmotivaXmlUtils; import org.openhab.core.common.NamedThreadFactory; +import org.openhab.core.library.types.DecimalType; import org.openhab.core.library.types.OnOffType; import org.openhab.core.library.types.PercentType; import org.openhab.core.library.types.QuantityType; @@ -102,21 +95,17 @@ public class EmotivaProcessorHandler extends BaseThingHandler { private final Logger logger = LoggerFactory.getLogger(EmotivaProcessorHandler.class); - private final Map stateMap = Collections.synchronizedMap(new HashMap<>()); - private final EmotivaConfiguration config; /** * Emotiva devices have trouble with too many subscriptions in same request, so subscriptions are dividing into - * those general group channels, and the rest. + * groups. */ - private final EmotivaSubscriptionTags[] generalSubscription = EmotivaSubscriptionTags.generalChannels(); - private final EmotivaSubscriptionTags[] nonGeneralSubscriptions = EmotivaSubscriptionTags.nonGeneralChannels(); + private final List generalSubscription = EmotivaSubscriptionTags.channels("general"); + private final List mainZoneSubscriptions = EmotivaSubscriptionTags.channels("main-zone"); + private final List zone2Subscriptions = EmotivaSubscriptionTags.channels("zone2"); - private final EnumMap sourcesMainZone; - private final EnumMap sourcesZone2; - private final EnumMap modes; - private final Map> commandMaps = new ConcurrentHashMap<>(); + private final EmotivaProcessorState state = new EmotivaProcessorState(); private final EmotivaTranslationProvider i18nProvider; private @Nullable ScheduledFuture pollingJob; @@ -141,41 +130,6 @@ public EmotivaProcessorHandler(Thing thing, EmotivaTranslationProvider i18nProvi this.i18nProvider = i18nProvider; this.config = getConfigAs(EmotivaConfiguration.class); this.retryConnectInMinutes = config.retryConnectInMinutes; - - sourcesMainZone = new EnumMap<>(EmotivaControlCommands.class); - commandMaps.put(MAP_SOURCES_MAIN_ZONE, sourcesMainZone); - - sourcesZone2 = new EnumMap<>(EmotivaControlCommands.class); - commandMaps.put(MAP_SOURCES_ZONE_2, sourcesZone2); - - EnumMap channels = new EnumMap<>( - Map.ofEntries(Map.entry(channel_1, channel_1.getLabel()), - Map.entry(EmotivaControlCommands.channel_2, EmotivaControlCommands.channel_2.getLabel()), - Map.entry(EmotivaControlCommands.channel_3, EmotivaControlCommands.channel_3.getLabel()), - Map.entry(EmotivaControlCommands.channel_4, EmotivaControlCommands.channel_4.getLabel()), - Map.entry(EmotivaControlCommands.channel_5, EmotivaControlCommands.channel_5.getLabel()), - Map.entry(EmotivaControlCommands.channel_6, EmotivaControlCommands.channel_6.getLabel()), - Map.entry(EmotivaControlCommands.channel_7, EmotivaControlCommands.channel_7.getLabel()), - Map.entry(EmotivaControlCommands.channel_8, EmotivaControlCommands.channel_8.getLabel()), - Map.entry(EmotivaControlCommands.channel_9, EmotivaControlCommands.channel_9.getLabel()), - Map.entry(EmotivaControlCommands.channel_10, EmotivaControlCommands.channel_10.getLabel()), - Map.entry(EmotivaControlCommands.channel_11, EmotivaControlCommands.channel_11.getLabel()), - Map.entry(EmotivaControlCommands.channel_12, EmotivaControlCommands.channel_12.getLabel()), - Map.entry(EmotivaControlCommands.channel_13, EmotivaControlCommands.channel_13.getLabel()), - Map.entry(EmotivaControlCommands.channel_14, EmotivaControlCommands.channel_14.getLabel()), - Map.entry(EmotivaControlCommands.channel_15, EmotivaControlCommands.channel_15.getLabel()), - Map.entry(EmotivaControlCommands.channel_16, EmotivaControlCommands.channel_16.getLabel()), - Map.entry(EmotivaControlCommands.channel_17, EmotivaControlCommands.channel_17.getLabel()), - Map.entry(EmotivaControlCommands.channel_18, EmotivaControlCommands.channel_18.getLabel()), - Map.entry(EmotivaControlCommands.channel_19, EmotivaControlCommands.channel_19.getLabel()), - Map.entry(EmotivaControlCommands.channel_20, EmotivaControlCommands.channel_20.getLabel()))); - commandMaps.put(tuner_channel.getEmotivaName(), channels); - - EnumMap bands = new EnumMap<>( - Map.of(band_am, band_am.getLabel(), band_fm, band_fm.getLabel())); - commandMaps.put(tuner_band.getEmotivaName(), bands); - - modes = new EnumMap<>(EmotivaSubscriptionTags.class); } @Override @@ -222,7 +176,8 @@ private synchronized void connect() { try { logger.debug("Connection attempt '{}'", attempt); sendConnector.sendSubscription(generalSubscription, config); - sendConnector.sendSubscription(nonGeneralSubscriptions, config); + sendConnector.sendSubscription(mainZoneSubscriptions, config); + sendConnector.sendSubscription(zone2Subscriptions, config); } catch (IOException e) { // network or socket failure, also wait 2 sec and try again } @@ -264,44 +219,49 @@ private void scheduleConnectRetry(long waitMinutes) { } /** - * Starts a polling job for connection to th device, adds the + * Starts a polling job for connection to the device, adds the * {@link EmotivaBindingConstants#DEFAULT_KEEP_ALIVE_IN_MILLISECONDS} as a time buffer for checking, to avoid * flapping state or minor network issues. */ private void startPollingKeepAlive() { final ScheduledFuture localRefreshJob = this.pollingJob; if (localRefreshJob == null || localRefreshJob.isCancelled()) { - logger.debug("Start polling"); - - int delay = stateMap.get(EmotivaSubscriptionTags.keepAlive.name()) != null - && stateMap.get(EmotivaSubscriptionTags.keepAlive.name()) instanceof Number keepAlive - ? keepAlive.intValue() - : config.keepAlive; - pollingJob = scheduler.scheduleWithFixedDelay(this::checkKeepAliveTimestamp, - delay + DEFAULT_KEEP_ALIVE_IN_MILLISECONDS, delay + DEFAULT_KEEP_ALIVE_IN_MILLISECONDS, + + Number keepAliveConfig = state.getChannel(EmotivaSubscriptionTags.keepAlive) + .filter(channel -> channel instanceof Number).map(keepAlive -> (Number) keepAlive) + .orElse(new DecimalType(config.keepAlive)); + + // noinspection ConstantConditions + long delay = keepAliveConfig == null + ? DEFAULT_KEEP_ALIVE_IN_MILLISECONDS + DEFAULT_KEEP_ALIVE_IN_MILLISECONDS + : keepAliveConfig.longValue() + DEFAULT_KEEP_ALIVE_IN_MILLISECONDS; + pollingJob = scheduler.scheduleWithFixedDelay(this::checkKeepAliveTimestamp, delay, delay, TimeUnit.MILLISECONDS); + logger.debug("Started scheduled job to check connection to device, with an {}ms internal", delay); } } private void checkKeepAliveTimestamp() { if (ThingStatus.ONLINE.equals(getThing().getStatusInfo().getStatus())) { - State state = stateMap.get(LAST_SEEN_STATE_NAME); - if (state instanceof Number value) { - Instant lastKeepAliveMessageTimestamp = Instant.ofEpochSecond(value.longValue()); - Instant deviceGoneGracePeriod = Instant.now().minus(config.keepAlive, ChronoUnit.MILLIS) - .minus(DEFAULT_KEEP_ALIVE_CONSIDERED_LOST_IN_MILLISECONDS, ChronoUnit.MILLIS); - if (lastKeepAliveMessageTimestamp.isBefore(deviceGoneGracePeriod)) { - logger.debug( - "Last KeepAlive message received '{}', over grace-period by '{}', consider '{}' gone, setting OFFLINE and disposing", - lastKeepAliveMessageTimestamp, - Duration.between(lastKeepAliveMessageTimestamp, deviceGoneGracePeriod), - thing.getThingTypeUID()); - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, - "@text/message.processor.connection.error.keep-alive"); - // Connection lost, avoid sending unsubscription messages - udpSenderActive = false; - disconnect(); - scheduleConnectRetry(retryConnectInMinutes); + Optional lastSeenState = state.getChannel(LAST_SEEN_STATE_NAME); + if (lastSeenState.isPresent()) { + if (lastSeenState.get() instanceof Number value) { + Instant lastKeepAliveMessageTimestamp = Instant.ofEpochSecond(value.longValue()); + Instant deviceGoneGracePeriod = Instant.now().minus(config.keepAlive, ChronoUnit.MILLIS) + .minus(DEFAULT_KEEP_ALIVE_CONSIDERED_LOST_IN_MILLISECONDS, ChronoUnit.MILLIS); + if (lastKeepAliveMessageTimestamp.isBefore(deviceGoneGracePeriod)) { + logger.debug( + "Last KeepAlive message received '{}', over grace-period by '{}', consider '{}' gone, setting OFFLINE and disposing", + lastKeepAliveMessageTimestamp, + Duration.between(lastKeepAliveMessageTimestamp, deviceGoneGracePeriod), + thing.getThingTypeUID()); + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, + "@text/message.processor.connection.error.keep-alive"); + // Connection lost, avoid sending unsubscription messages + udpSenderActive = false; + disconnect(); + scheduleConnectRetry(retryConnectInMinutes); + } } } } else if (ThingStatus.OFFLINE.equals(getThing().getStatusInfo().getStatus())) { @@ -325,9 +285,10 @@ private void handleStatusUpdate(EmotivaUdpResponse emotivaUdpResponse) { return; } - if (object instanceof EmotivaAckDTO answerDto) { + if (object instanceof EmotivaAckDTO) { // Currently not supported to revert a failed command update, just used for logging for now. - logger.trace("Processing received '{}' with '{}'", EmotivaAckDTO.class.getSimpleName(), answerDto); + logger.trace("Processing received '{}' with '{}'", EmotivaAckDTO.class.getSimpleName(), + emotivaUdpResponse.answer()); } else if (object instanceof EmotivaBarNotifyWrapper answerDto) { logger.trace("Processing received '{}' with '{}'", EmotivaBarNotifyWrapper.class.getSimpleName(), emotivaUdpResponse.answer()); @@ -364,13 +325,15 @@ private void handleStatusUpdate(EmotivaUdpResponse emotivaUdpResponse) { logger.trace("Processing received '{}' with '{}'", EmotivaSubscriptionResponse.class.getSimpleName(), emotivaUdpResponse.answer()); // Populates static input sources, except input - sourcesMainZone.putAll(EmotivaControlCommands.getCommandsFromType(EmotivaCommandType.SOURCE_MAIN_ZONE)); - sourcesMainZone.remove(EmotivaControlCommands.input); - commandMaps.put(MAP_SOURCES_MAIN_ZONE, sourcesMainZone); + EnumMap sourceMainZone = EmotivaControlCommands + .getCommandsFromType(EmotivaCommandType.SOURCE_MAIN_ZONE); + sourceMainZone.remove(EmotivaControlCommands.input); + state.setSourcesMainZone(sourceMainZone); - sourcesZone2.putAll(EmotivaControlCommands.getCommandsFromType(EmotivaCommandType.SOURCE_ZONE2)); - sourcesZone2.remove(EmotivaControlCommands.zone2_input); - commandMaps.put(MAP_SOURCES_ZONE_2, sourcesZone2); + EnumMap sourcesZone2 = EmotivaControlCommands + .getCommandsFromType(EmotivaCommandType.SOURCE_ZONE2); + sourcesZone2.remove(EmotivaControlCommands.input); + state.setSourcesZone2(sourcesZone2); if (answerDto.getProperties() == null) { for (EmotivaNotifyDTO dto : xmlUtils.unmarshallToNotification(answerDto.getTags())) { @@ -528,11 +491,9 @@ private void handleChannelUpdate(String emotivaSubscriptionName, String value, S // Add/Update user assigned name for inputs if (subscriptionTag.getChannel().startsWith(CHANNEL_INPUT1.substring(0, CHANNEL_INPUT1.indexOf("-") + 1)) && "true".equals(visible)) { - logger.debug("Adding '{}' to dynamic source input list", trimmedValue); - sourcesMainZone.put(EmotivaControlCommands.matchToInput(subscriptionTag.name()), trimmedValue); - commandMaps.put(MAP_SOURCES_MAIN_ZONE, sourcesMainZone); - - logger.debug("sources list is now {}", sourcesMainZone.size()); + state.updateSourcesMainZone(EmotivaControlCommands.matchToInput(subscriptionTag.name()), trimmedValue); + logger.debug("Adding '{}' to dynamic source input list, map is now {}", trimmedValue, + state.getSourcesMainZone()); } // Add/Update audio modes @@ -541,7 +502,7 @@ private void handleChannelUpdate(String emotivaSubscriptionName, String value, S + subscriptionTag.getChannel().substring(subscriptionTag.getChannel().indexOf("_") + 1)); logger.debug("Adding '{} ({})' from channel '{}' to dynamic mode list", trimmedValue, modeName, subscriptionTag.getChannel()); - modes.put(EmotivaSubscriptionTags.fromChannelUID(subscriptionTag.getChannel()), trimmedValue); + state.updateModes(EmotivaSubscriptionTags.fromChannelUID(subscriptionTag.getChannel()), trimmedValue); } findChannelDatatypeAndUpdateChannel(subscriptionTag.getChannel(), trimmedValue, @@ -630,10 +591,10 @@ private void findChannelDatatypeAndUpdateChannel(String channelName, String valu } } - private void updateChannelState(String channelID, State state) { - stateMap.put(channelID, state); - logger.trace("Updating channel '{}' with '{}'", channelID, state); - updateState(channelID, state); + private void updateChannelState(String channelID, State channelState) { + state.updateChannel(channelID, channelState); + logger.trace("Updating channel '{}' with '{}'", channelID, channelState); + updateState(channelID, channelState); } private void updateVolumeChannels(String value, String muteChannel, String volumeChannel, String volumeDbChannel) { @@ -651,10 +612,10 @@ public void handleCommand(ChannelUID channelUID, Command ohCommand) { EmotivaUdpSendingService localSendingService = sendingService; if (localSendingService != null) { - EmotivaControlRequest emotivaRequest = channelToControlRequest(channelUID.getId(), commandMaps, + EmotivaControlRequest emotivaRequest = channelToControlRequest(channelUID.getId(), state, protocolFromConfig(config.protocolVersion)); if (ohCommand instanceof RefreshType) { - stateMap.remove(channelUID.getId()); + state.removeChannel(channelUID.getId()); if (emotivaRequest.getDefaultCommand().equals(none)) { logger.debug("Found controlCommand 'none' for request '{}' from channel '{}' with RefreshType", @@ -665,26 +626,29 @@ public void handleCommand(ChannelUID channelUID, Command ohCommand) { } } else { try { - EmotivaControlDTO dto = emotivaRequest.createDTO(ohCommand, stateMap.get(channelUID.getId())); - localSendingService.send(dto); - - if (emotivaRequest.getName().equals(EmotivaControlCommands.volume.name())) { - if (ohCommand instanceof PercentType value) { - updateChannelState(CHANNEL_MAIN_VOLUME_DB, - QuantityType.valueOf(volumePercentageToDecibel(value.intValue()), Units.DECIBEL)); - } else if (ohCommand instanceof QuantityType value) { - updateChannelState(CHANNEL_MAIN_VOLUME, volumeDecibelToPercentage(value.toString())); - } - } else if (emotivaRequest.getName().equals(EmotivaControlCommands.zone2_volume.name())) { - if (ohCommand instanceof PercentType value) { - updateChannelState(CHANNEL_ZONE2_VOLUME_DB, - QuantityType.valueOf(volumePercentageToDecibel(value.intValue()), Units.DECIBEL)); - } else if (ohCommand instanceof QuantityType value) { - updateChannelState(CHANNEL_ZONE2_VOLUME, volumeDecibelToPercentage(value.toString())); - } - } else if (ohCommand instanceof OnOffType value) { - if (value.equals(OnOffType.ON) && emotivaRequest.getOnCommand().equals(power_on)) { - localSendingService.sendUpdate(EmotivaSubscriptionTags.speakerChannels(), config); + Optional channel = state.getChannel(channelUID.getId()); + if (channel.isPresent()) { + EmotivaControlDTO dto = emotivaRequest.createDTO(ohCommand, channel.get()); + localSendingService.send(dto); + + if (emotivaRequest.getName().equals(EmotivaControlCommands.volume.name())) { + if (ohCommand instanceof PercentType value) { + updateChannelState(CHANNEL_MAIN_VOLUME_DB, QuantityType + .valueOf(volumePercentageToDecibel(value.intValue()), Units.DECIBEL)); + } else if (ohCommand instanceof QuantityType value) { + updateChannelState(CHANNEL_MAIN_VOLUME, volumeDecibelToPercentage(value.toString())); + } + } else if (emotivaRequest.getName().equals(EmotivaControlCommands.zone2_volume.name())) { + if (ohCommand instanceof PercentType value) { + updateChannelState(CHANNEL_ZONE2_VOLUME_DB, QuantityType + .valueOf(volumePercentageToDecibel(value.intValue()), Units.DECIBEL)); + } else if (ohCommand instanceof QuantityType value) { + updateChannelState(CHANNEL_ZONE2_VOLUME, volumeDecibelToPercentage(value.toString())); + } + } else if (ohCommand instanceof OnOffType value) { + if (value.equals(OnOffType.ON) && emotivaRequest.getOnCommand().equals(power_on)) { + localSendingService.sendUpdate(EmotivaSubscriptionTags.speakerChannels(), config); + } } } } catch (InterruptedIOException e) { @@ -714,7 +678,8 @@ private synchronized void disconnect() { try { // Unsubscribe before disconnect localSendingService.sendUnsubscribe(generalSubscription); - localSendingService.sendUnsubscribe(nonGeneralSubscriptions); + localSendingService.sendUnsubscribe(mainZoneSubscriptions); + localSendingService.sendUnsubscribe(zone2Subscriptions); } catch (IOException e) { logger.debug("Failed to unsubscribe for '{}'", config.ipAddress, e); } @@ -772,14 +737,14 @@ public Collection> getServices() { } public EnumMap getSourcesMainZone() { - return sourcesMainZone; + return state.getSourcesMainZone(); } public EnumMap getSourcesZone2() { - return sourcesZone2; + return state.getSourcesZone2(); } public EnumMap getModes() { - return modes; + return state.getModes(); } } diff --git a/bundles/org.openhab.binding.emotiva/src/main/java/org/openhab/binding/emotiva/internal/EmotivaProcessorState.java b/bundles/org.openhab.binding.emotiva/src/main/java/org/openhab/binding/emotiva/internal/EmotivaProcessorState.java new file mode 100644 index 0000000000000..6d9f48aee81a4 --- /dev/null +++ b/bundles/org.openhab.binding.emotiva/src/main/java/org/openhab/binding/emotiva/internal/EmotivaProcessorState.java @@ -0,0 +1,149 @@ +/** + * 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.emotiva.internal; + +import static org.openhab.binding.emotiva.internal.EmotivaBindingConstants.MAP_SOURCES_MAIN_ZONE; +import static org.openhab.binding.emotiva.internal.EmotivaBindingConstants.MAP_SOURCES_ZONE_2; +import static org.openhab.binding.emotiva.internal.EmotivaBindingConstants.MAP_TUNER_BANDS; +import static org.openhab.binding.emotiva.internal.EmotivaBindingConstants.MAP_TUNER_CHANNELS; +import static org.openhab.binding.emotiva.internal.protocol.EmotivaControlCommands.band_am; +import static org.openhab.binding.emotiva.internal.protocol.EmotivaControlCommands.band_fm; +import static org.openhab.binding.emotiva.internal.protocol.EmotivaControlCommands.channel_1; + +import java.util.Collections; +import java.util.EnumMap; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.emotiva.internal.protocol.EmotivaControlCommands; +import org.openhab.binding.emotiva.internal.protocol.EmotivaSubscriptionTags; +import org.openhab.core.types.State; + +/** + * Holds state for Emotiva Processor. + * + * @author Espen Fossen - Initial contribution + */ +@NonNullByDefault +public class EmotivaProcessorState { + + private final Map channelStateMap; + + private EnumMap sourcesMainZone; + private EnumMap sourcesZone2; + private final EnumMap modes; + + private EnumMap tunerChannels = new EnumMap<>( + Map.ofEntries(Map.entry(channel_1, channel_1.getLabel()), + Map.entry(EmotivaControlCommands.channel_2, EmotivaControlCommands.channel_2.getLabel()), + Map.entry(EmotivaControlCommands.channel_3, EmotivaControlCommands.channel_3.getLabel()), + Map.entry(EmotivaControlCommands.channel_4, EmotivaControlCommands.channel_4.getLabel()), + Map.entry(EmotivaControlCommands.channel_5, EmotivaControlCommands.channel_5.getLabel()), + Map.entry(EmotivaControlCommands.channel_6, EmotivaControlCommands.channel_6.getLabel()), + Map.entry(EmotivaControlCommands.channel_7, EmotivaControlCommands.channel_7.getLabel()), + Map.entry(EmotivaControlCommands.channel_8, EmotivaControlCommands.channel_8.getLabel()), + Map.entry(EmotivaControlCommands.channel_9, EmotivaControlCommands.channel_9.getLabel()), + Map.entry(EmotivaControlCommands.channel_10, EmotivaControlCommands.channel_10.getLabel()), + Map.entry(EmotivaControlCommands.channel_11, EmotivaControlCommands.channel_11.getLabel()), + Map.entry(EmotivaControlCommands.channel_12, EmotivaControlCommands.channel_12.getLabel()), + Map.entry(EmotivaControlCommands.channel_13, EmotivaControlCommands.channel_13.getLabel()), + Map.entry(EmotivaControlCommands.channel_14, EmotivaControlCommands.channel_14.getLabel()), + Map.entry(EmotivaControlCommands.channel_15, EmotivaControlCommands.channel_15.getLabel()), + Map.entry(EmotivaControlCommands.channel_16, EmotivaControlCommands.channel_16.getLabel()), + Map.entry(EmotivaControlCommands.channel_17, EmotivaControlCommands.channel_17.getLabel()), + Map.entry(EmotivaControlCommands.channel_18, EmotivaControlCommands.channel_18.getLabel()), + Map.entry(EmotivaControlCommands.channel_19, EmotivaControlCommands.channel_19.getLabel()), + Map.entry(EmotivaControlCommands.channel_20, EmotivaControlCommands.channel_20.getLabel()))); + + private EnumMap tunerBands = new EnumMap<>( + Map.of(band_am, band_am.getLabel(), band_fm, band_fm.getLabel())); + + public EmotivaProcessorState() { + channelStateMap = Collections.synchronizedMap(new HashMap<>()); + sourcesMainZone = new EnumMap<>(EmotivaControlCommands.class); + sourcesZone2 = new EnumMap<>(EmotivaControlCommands.class); + modes = new EnumMap<>(EmotivaSubscriptionTags.class); + } + + public Optional getChannel(String channelName) { + if (channelStateMap.containsKey(channelName)) { + return Optional.ofNullable(channelStateMap.get(channelName)); + } else { + return Optional.empty(); + } + } + + public Optional getChannel(EmotivaSubscriptionTags channelTagName) { + if (channelStateMap.containsKey(channelTagName.name())) { + return Optional.ofNullable(channelStateMap.get(channelTagName.name())); + } else { + return Optional.empty(); + } + } + + public Map getCommandMap(String mapName) { + return switch (mapName) { + case MAP_SOURCES_MAIN_ZONE -> sourcesMainZone; + case MAP_SOURCES_ZONE_2 -> sourcesZone2; + case MAP_TUNER_CHANNELS -> tunerChannels; + case MAP_TUNER_BANDS -> tunerBands; + default -> new EnumMap<>(EmotivaControlCommands.class); + }; + } + + public EnumMap getSourcesMainZone() { + return sourcesMainZone; + } + + public EnumMap getSourcesZone2() { + return sourcesZone2; + } + + public EnumMap getModes() { + return modes; + } + + public void setChannels(EnumMap map) { + tunerChannels = map; + } + + public void setSourcesMainZone(EnumMap map) { + sourcesMainZone = map; + } + + public void setSourcesZone2(EnumMap map) { + sourcesZone2 = map; + } + + public void setTunerBands(EnumMap map) { + tunerBands = map; + } + + public void updateChannel(String channel, State state) { + channelStateMap.put(channel, state); + } + + public void updateSourcesMainZone(EmotivaControlCommands command, String value) { + sourcesMainZone.put(command, value); + } + + public void updateModes(EmotivaSubscriptionTags tag, String value) { + modes.put(tag, value); + } + + public void removeChannel(String channel) { + channelStateMap.remove(channel); + } +} diff --git a/bundles/org.openhab.binding.emotiva/src/main/java/org/openhab/binding/emotiva/internal/EmotivaUdpReceivingService.java b/bundles/org.openhab.binding.emotiva/src/main/java/org/openhab/binding/emotiva/internal/EmotivaUdpReceivingService.java index 954af61a2f38e..adec7e6db88b0 100644 --- a/bundles/org.openhab.binding.emotiva/src/main/java/org/openhab/binding/emotiva/internal/EmotivaUdpReceivingService.java +++ b/bundles/org.openhab.binding.emotiva/src/main/java/org/openhab/binding/emotiva/internal/EmotivaUdpReceivingService.java @@ -156,9 +156,7 @@ private void listenUnhandledInterruption() throws InterruptedException { localReceivingSocket.receive(answer); // receive packet (blocking call) listenerNotifyActive = false; - final byte[] receivedData = Arrays.copyOfRange(answer.getData(), 0, answer.getLength() - 1); - - if (receivedData.length == 0) { + if (Arrays.copyOfRange(answer.getData(), 0, answer.getLength() - 1).length == 0) { if (isConnected()) { logger.debug("Nothing received, this may happen during shutdown or some unknown error"); } @@ -166,7 +164,7 @@ private void listenUnhandledInterruption() throws InterruptedException { } receiveNotifyFailures = 0; // message successfully received, unset failure counter - handleReceivedData(answer, receivedData, localListener); + handleReceivedData(answer, localListener); } catch (Exception e) { listenerNotifyActive = false; @@ -190,12 +188,11 @@ private void listenUnhandledInterruption() throws InterruptedException { } } - private void handleReceivedData(DatagramPacket answer, byte[] receivedData, - Consumer localListener) { + private void handleReceivedData(DatagramPacket answer, Consumer localListener) { // log & notify listener in new thread (so that listener loop continues immediately) executorService.execute(() -> { if (answer.getAddress() != null && answer.getLength() > 0) { - logger.trace("Received data on port '{}': {}", answer.getPort(), receivedData); + logger.trace("Received data on port '{}'", answer.getPort()); EmotivaUdpResponse emotivaUdpResponse = new EmotivaUdpResponse( new String(answer.getData(), 0, answer.getLength()), answer.getAddress().getHostAddress()); localListener.accept(emotivaUdpResponse); diff --git a/bundles/org.openhab.binding.emotiva/src/main/java/org/openhab/binding/emotiva/internal/EmotivaUdpSendingService.java b/bundles/org.openhab.binding.emotiva/src/main/java/org/openhab/binding/emotiva/internal/EmotivaUdpSendingService.java index 5b91a6896f200..216d503580370 100644 --- a/bundles/org.openhab.binding.emotiva/src/main/java/org/openhab/binding/emotiva/internal/EmotivaUdpSendingService.java +++ b/bundles/org.openhab.binding.emotiva/src/main/java/org/openhab/binding/emotiva/internal/EmotivaUdpSendingService.java @@ -21,6 +21,7 @@ import java.net.SocketException; import java.nio.charset.Charset; import java.util.Arrays; +import java.util.List; import java.util.Objects; import java.util.concurrent.ExecutorService; import java.util.function.Consumer; @@ -122,12 +123,11 @@ public void connect(Consumer listener, boolean logNotThrowEx } } - private void handleReceivedData(DatagramPacket answer, byte[] receivedData, - Consumer localListener) { + private void handleReceivedData(DatagramPacket answer, Consumer localListener) { // log & notify listener in new thread (so that listener loop continues immediately) executorService.execute(() -> { if (answer.getAddress() != null && answer.getLength() > 0) { - logger.trace("Received data on port '{}': {}", answer.getPort(), receivedData); + logger.trace("Received data on port '{}'", answer.getPort()); EmotivaUdpResponse emotivaUdpResponse = new EmotivaUdpResponse( new String(answer.getData(), 0, answer.getLength()), answer.getAddress().getHostAddress()); localListener.accept(emotivaUdpResponse); @@ -158,7 +158,7 @@ public void send(EmotivaControlDTO dto) throws IOException { send(emotivaXmlUtils.marshallJAXBElementObjects(dto)); } - public void sendSubscription(EmotivaSubscriptionTags[] tags, EmotivaConfiguration config) throws IOException { + public void sendSubscription(List tags, EmotivaConfiguration config) throws IOException { send(emotivaXmlUtils.marshallJAXBElementObjects(new EmotivaSubscriptionRequest(tags, config.protocolVersion))); } @@ -171,7 +171,7 @@ public void sendUpdate(EmotivaSubscriptionTags[] tags, EmotivaConfiguration conf send(emotivaXmlUtils.marshallJAXBElementObjects(new EmotivaUpdateRequest(tags, config.protocolVersion))); } - public void sendUnsubscribe(EmotivaSubscriptionTags[] defaultCommand) throws IOException { + public void sendUnsubscribe(List defaultCommand) throws IOException { send(emotivaXmlUtils.marshallJAXBElementObjects(new EmotivaUnsubscribeDTO(defaultCommand))); } @@ -196,14 +196,13 @@ public void send(String msg) throws IOException { logger.debug("Sending successful"); localDatagramSocket.receive(answer); - final byte[] receivedData = Arrays.copyOfRange(answer.getData(), 0, answer.getLength() - 1); - if (receivedData.length == 0) { + if (Arrays.copyOfRange(answer.getData(), 0, answer.getLength() - 1).length == 0) { logger.debug("Nothing received, this may happen during shutdown or some unknown error"); } if (localListener != null) { - handleReceivedData(answer, receivedData, localListener); + handleReceivedData(answer, localListener); } } else { throw new SocketException("Datagram Socket closed or not initialized"); diff --git a/bundles/org.openhab.binding.emotiva/src/main/java/org/openhab/binding/emotiva/internal/InputStateOptionProvider.java b/bundles/org.openhab.binding.emotiva/src/main/java/org/openhab/binding/emotiva/internal/InputStateOptionProvider.java index c20b2c6f5814e..dce836a5c5853 100644 --- a/bundles/org.openhab.binding.emotiva/src/main/java/org/openhab/binding/emotiva/internal/InputStateOptionProvider.java +++ b/bundles/org.openhab.binding.emotiva/src/main/java/org/openhab/binding/emotiva/internal/InputStateOptionProvider.java @@ -65,7 +65,7 @@ public void setThingHandler(ThingHandler handler) { public @Nullable StateDescription getStateDescription(Channel channel, @Nullable StateDescription original, @Nullable Locale locale) { ChannelTypeUID typeUID = channel.getChannelTypeUID(); - if (typeUID == null || !BINDING_ID.equals(typeUID.getBindingId()) || original == null) { + if (typeUID == null || !BINDING_ID.equals(typeUID.getBindingId())) { return null; } diff --git a/bundles/org.openhab.binding.emotiva/src/main/java/org/openhab/binding/emotiva/internal/dto/EmotivaSubscriptionRequest.java b/bundles/org.openhab.binding.emotiva/src/main/java/org/openhab/binding/emotiva/internal/dto/EmotivaSubscriptionRequest.java index faff31332029f..5f1480bae7a19 100644 --- a/bundles/org.openhab.binding.emotiva/src/main/java/org/openhab/binding/emotiva/internal/dto/EmotivaSubscriptionRequest.java +++ b/bundles/org.openhab.binding.emotiva/src/main/java/org/openhab/binding/emotiva/internal/dto/EmotivaSubscriptionRequest.java @@ -14,13 +14,12 @@ import static org.openhab.binding.emotiva.internal.protocol.EmotivaProtocolVersion.PROTOCOL_V2; -import java.util.ArrayList; import java.util.List; +import java.util.stream.Collectors; import javax.xml.bind.annotation.XmlAttribute; import javax.xml.bind.annotation.XmlRootElement; -import org.openhab.binding.emotiva.internal.protocol.EmotivaControlCommands; import org.openhab.binding.emotiva.internal.protocol.EmotivaSubscriptionTags; /** @@ -38,26 +37,18 @@ public class EmotivaSubscriptionRequest extends AbstractJAXBElementDTO { public EmotivaSubscriptionRequest() { } - public EmotivaSubscriptionRequest(List commands, String protocol) { + public EmotivaSubscriptionRequest(List emotivaCommandTypes, String protocol) { this.protocol = protocol; - this.commands = commands; - } - - public EmotivaSubscriptionRequest(EmotivaSubscriptionTags[] emotivaCommandTypes, String protocol) { - this.protocol = protocol; - List list = new ArrayList<>(); - for (EmotivaSubscriptionTags commandType : emotivaCommandTypes) { - list.add(EmotivaCommandDTO.fromTypeWithAck(commandType)); - } - this.commands = list; + this.commands = emotivaCommandTypes.stream().map(EmotivaCommandDTO::fromTypeWithAck) + .collect(Collectors.toList()); } public EmotivaSubscriptionRequest(EmotivaSubscriptionTags tag) { this.commands = List.of(EmotivaCommandDTO.fromTypeWithAck(tag)); } - public EmotivaSubscriptionRequest(EmotivaControlCommands commandType, String protocol) { + public EmotivaSubscriptionRequest(EmotivaCommandDTO commandType, String protocol) { this.protocol = protocol; - this.commands = List.of(EmotivaCommandDTO.fromTypeWithAck(commandType)); + this.commands = List.of(commandType); } } diff --git a/bundles/org.openhab.binding.emotiva/src/main/java/org/openhab/binding/emotiva/internal/dto/EmotivaUnsubscribeDTO.java b/bundles/org.openhab.binding.emotiva/src/main/java/org/openhab/binding/emotiva/internal/dto/EmotivaUnsubscribeDTO.java index 6a97e53a364f0..621bcf25537d8 100644 --- a/bundles/org.openhab.binding.emotiva/src/main/java/org/openhab/binding/emotiva/internal/dto/EmotivaUnsubscribeDTO.java +++ b/bundles/org.openhab.binding.emotiva/src/main/java/org/openhab/binding/emotiva/internal/dto/EmotivaUnsubscribeDTO.java @@ -14,10 +14,10 @@ import java.util.ArrayList; import java.util.List; +import java.util.stream.Collectors; import javax.xml.bind.annotation.XmlRootElement; -import org.openhab.binding.emotiva.internal.protocol.EmotivaControlCommands; import org.openhab.binding.emotiva.internal.protocol.EmotivaSubscriptionTags; /** @@ -34,10 +34,6 @@ public class EmotivaUnsubscribeDTO extends AbstractJAXBElementDTO { public EmotivaUnsubscribeDTO() { } - public EmotivaUnsubscribeDTO(List commands) { - this.commands = commands; - } - public EmotivaUnsubscribeDTO(EmotivaSubscriptionTags[] emotivaCommandTypes) { List list = new ArrayList<>(); for (EmotivaSubscriptionTags commandType : emotivaCommandTypes) { @@ -50,7 +46,11 @@ public EmotivaUnsubscribeDTO(EmotivaSubscriptionTags tag) { this.commands = List.of(EmotivaCommandDTO.fromType(tag)); } - public EmotivaUnsubscribeDTO(EmotivaControlCommands commandType) { - this.commands = List.of(EmotivaCommandDTO.fromType(commandType)); + public EmotivaUnsubscribeDTO(EmotivaCommandDTO commandType) { + this.commands = List.of(commandType); + } + + public EmotivaUnsubscribeDTO(List commandType) { + this.commands = commandType.stream().map(EmotivaCommandDTO::fromTypeWithAck).collect(Collectors.toList()); } } diff --git a/bundles/org.openhab.binding.emotiva/src/main/java/org/openhab/binding/emotiva/internal/protocol/EmotivaControlRequest.java b/bundles/org.openhab.binding.emotiva/src/main/java/org/openhab/binding/emotiva/internal/protocol/EmotivaControlRequest.java index ab529c742b464..035b0cd5ad3b6 100644 --- a/bundles/org.openhab.binding.emotiva/src/main/java/org/openhab/binding/emotiva/internal/protocol/EmotivaControlRequest.java +++ b/bundles/org.openhab.binding.emotiva/src/main/java/org/openhab/binding/emotiva/internal/protocol/EmotivaControlRequest.java @@ -17,13 +17,12 @@ import static org.openhab.binding.emotiva.internal.EmotivaCommandHelper.volumePercentageToDecibel; import static org.openhab.binding.emotiva.internal.protocol.EmotivaCommandType.*; import static org.openhab.binding.emotiva.internal.protocol.EmotivaDataType.FREQUENCY_HERTZ; -import static org.openhab.binding.emotiva.internal.protocol.EmotivaSubscriptionTags.tuner_band; -import static org.openhab.binding.emotiva.internal.protocol.EmotivaSubscriptionTags.tuner_channel; import java.util.Map; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.emotiva.internal.EmotivaProcessorState; import org.openhab.binding.emotiva.internal.dto.EmotivaControlDTO; import org.openhab.core.library.types.OnOffType; import org.openhab.core.library.types.PercentType; @@ -53,11 +52,11 @@ public class EmotivaControlRequest { private final EmotivaControlCommands downCommand; private double maxValue; private double minValue; - private final Map> commandMaps; + private final EmotivaProcessorState state; private final EmotivaProtocolVersion protocolVersion; public EmotivaControlRequest(String channel, EmotivaSubscriptionTags channelSubscription, - EmotivaControlCommands controlCommand, Map> commandMaps, + EmotivaControlCommands controlCommand, EmotivaProcessorState state, EmotivaProtocolVersion protocolVersion) { if (channelSubscription.equals(EmotivaSubscriptionTags.unknown)) { if (controlCommand.equals(EmotivaControlCommands.none)) { @@ -94,7 +93,7 @@ public EmotivaControlRequest(String channel, EmotivaSubscriptionTags channelSubs this.name = defaultCommand.name(); this.dataType = defaultCommand.getDataType(); this.channel = channel; - this.commandMaps = commandMaps; + this.state = state; this.protocolVersion = protocolVersion; if (name.equals(EmotivaControlCommands.volume.name()) || name.equals(EmotivaControlCommands.zone2_volume.name())) { @@ -155,10 +154,10 @@ public EmotivaControlDTO createDTO(Command ohCommand, @Nullable State previousSt case NONE -> { switch (channel) { case CHANNEL_TUNER_BAND -> { - return matchToCommandMap(ohCommand, tuner_band.getEmotivaName()); + return matchToCommandMap(ohCommand, MAP_TUNER_BANDS); } case CHANNEL_TUNER_CHANNEL_SELECT -> { - return matchToCommandMap(ohCommand, tuner_channel.getEmotivaName()); + return matchToCommandMap(ohCommand, MAP_TUNER_CHANNELS); } case CHANNEL_SOURCE -> { return matchToCommandMap(ohCommand, MAP_SOURCES_MAIN_ZONE); @@ -284,15 +283,13 @@ public EmotivaControlDTO createDTO(Command ohCommand, @Nullable State previousSt private EmotivaControlDTO matchToCommandMap(Command ohCommand, String mapName) { if (ohCommand instanceof StringType value) { - Map commandMap = commandMaps.get(mapName); - if (commandMap != null) { - for (EmotivaControlCommands command : commandMap.keySet()) { - String map = commandMap.get(command); - if (map != null && map.equals(value.toString())) { - return EmotivaControlDTO.create(EmotivaControlCommands.matchToInput(command.toString())); - } else if (command.name().equalsIgnoreCase(value.toString())) { - return EmotivaControlDTO.create(command); - } + Map commandMap = state.getCommandMap(mapName); + for (EmotivaControlCommands command : commandMap.keySet()) { + String map = commandMap.get(command); + if (map != null && map.equals(value.toString())) { + return EmotivaControlDTO.create(EmotivaControlCommands.matchToInput(command.toString())); + } else if (command.name().equalsIgnoreCase(value.toString())) { + return EmotivaControlDTO.create(command); } } } @@ -469,7 +466,7 @@ public String toString() { return "EmotivaControlRequest{" + "name='" + name + '\'' + ", dataType=" + dataType + ", channel='" + channel + '\'' + ", defaultCommand=" + defaultCommand + ", setCommand=" + setCommand + ", onCommand=" + onCommand + ", offCommand=" + offCommand + ", upCommand=" + upCommand + ", downCommand=" + downCommand - + ", maxValue=" + maxValue + ", minValue=" + minValue + ", commandMaps=" + commandMaps - + ", protocolVersion=" + protocolVersion + '}'; + + ", maxValue=" + maxValue + ", minValue=" + minValue + ", state=" + state + ", protocolVersion=" + + protocolVersion + '}'; } } diff --git a/bundles/org.openhab.binding.emotiva/src/main/java/org/openhab/binding/emotiva/internal/protocol/EmotivaSubscriptionTags.java b/bundles/org.openhab.binding.emotiva/src/main/java/org/openhab/binding/emotiva/internal/protocol/EmotivaSubscriptionTags.java index 2e2d64809d083..b4c5dce435e0b 100644 --- a/bundles/org.openhab.binding.emotiva/src/main/java/org/openhab/binding/emotiva/internal/protocol/EmotivaSubscriptionTags.java +++ b/bundles/org.openhab.binding.emotiva/src/main/java/org/openhab/binding/emotiva/internal/protocol/EmotivaSubscriptionTags.java @@ -132,24 +132,14 @@ public static EmotivaSubscriptionTags fromChannelUID(String id) { return EmotivaSubscriptionTags.unknown; } - public static EmotivaSubscriptionTags[] generalChannels() { + public static List channels(String zonePrefix) { List tags = new ArrayList<>(); for (EmotivaSubscriptionTags value : values()) { - if (value.channel.startsWith("general")) { + if (value.channel.startsWith(zonePrefix)) { tags.add(value); } } - return tags.toArray(new EmotivaSubscriptionTags[0]); - } - - public static EmotivaSubscriptionTags[] nonGeneralChannels() { - List tags = new ArrayList<>(); - for (EmotivaSubscriptionTags value : values()) { - if (!value.channel.startsWith("general")) { - tags.add(value); - } - } - return tags.toArray(new EmotivaSubscriptionTags[0]); + return tags; } public static EmotivaSubscriptionTags[] speakerChannels() { diff --git a/bundles/org.openhab.binding.emotiva/src/test/java/org/openhab/binding/emotiva/internal/EmotivaCommandHelperTest.java b/bundles/org.openhab.binding.emotiva/src/test/java/org/openhab/binding/emotiva/internal/EmotivaCommandHelperTest.java index b495509cb16d5..2c4417851f241 100644 --- a/bundles/org.openhab.binding.emotiva/src/test/java/org/openhab/binding/emotiva/internal/EmotivaCommandHelperTest.java +++ b/bundles/org.openhab.binding.emotiva/src/test/java/org/openhab/binding/emotiva/internal/EmotivaCommandHelperTest.java @@ -32,8 +32,6 @@ import static org.openhab.binding.emotiva.internal.protocol.EmotivaProtocolVersion.PROTOCOL_V2; import static org.openhab.binding.emotiva.internal.protocol.EmotivaProtocolVersion.PROTOCOL_V3; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Stream; import org.eclipse.jdt.annotation.NonNullByDefault; @@ -90,9 +88,9 @@ private static Stream channelToControlRequest() { void testChannelToControlRequest(String channel, String name, EmotivaDataType emotivaDataType, EmotivaControlCommands defaultCommand, EmotivaControlCommands onCommand, EmotivaControlCommands offCommand, EmotivaControlCommands setCommand, EmotivaProtocolVersion version, double min, double max) { - final Map> commandMaps = new ConcurrentHashMap<>(); + EmotivaProcessorState state = new EmotivaProcessorState(); - EmotivaControlRequest surround = EmotivaCommandHelper.channelToControlRequest(channel, commandMaps, version); + EmotivaControlRequest surround = EmotivaCommandHelper.channelToControlRequest(channel, state, version); assertThat(surround.getName(), is(name)); assertThat(surround.getChannel(), is(channel)); assertThat(surround.getDataType(), is(emotivaDataType)); diff --git a/bundles/org.openhab.binding.emotiva/src/test/java/org/openhab/binding/emotiva/internal/EmotivaProcessorStateTest.java b/bundles/org.openhab.binding.emotiva/src/test/java/org/openhab/binding/emotiva/internal/EmotivaProcessorStateTest.java new file mode 100644 index 0000000000000..29e904a82a5b1 --- /dev/null +++ b/bundles/org.openhab.binding.emotiva/src/test/java/org/openhab/binding/emotiva/internal/EmotivaProcessorStateTest.java @@ -0,0 +1,127 @@ +/** + * 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.emotiva.internal; + +import static org.hamcrest.CoreMatchers.*; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.openhab.binding.emotiva.internal.EmotivaBindingConstants.MAP_MODES; +import static org.openhab.binding.emotiva.internal.EmotivaBindingConstants.MAP_SOURCES_MAIN_ZONE; +import static org.openhab.binding.emotiva.internal.EmotivaBindingConstants.MAP_SOURCES_ZONE_2; +import static org.openhab.binding.emotiva.internal.EmotivaBindingConstants.MAP_TUNER_BANDS; +import static org.openhab.binding.emotiva.internal.EmotivaBindingConstants.MAP_TUNER_CHANNELS; + +import java.util.EnumMap; +import java.util.Optional; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.junit.jupiter.api.Test; +import org.openhab.binding.emotiva.internal.protocol.EmotivaControlCommands; +import org.openhab.binding.emotiva.internal.protocol.EmotivaSubscriptionTags; +import org.openhab.core.library.types.DecimalType; + +/** + * Unit tests for the EmotivaProcessorHandlerState. + * + * @author Espen Fossen - Initial contribution + */ +@NonNullByDefault +class EmotivaProcessorStateTest { + + @Test + void initialState() { + var state = new EmotivaProcessorState(); + + assertThat(state.getSourcesMainZone(), not(nullValue())); + assertThat(state.getSourcesMainZone().size(), is(0)); + + assertThat(state.getSourcesZone2(), not(nullValue())); + assertThat(state.getSourcesZone2().size(), is(0)); + + assertThat(state.getModes(), not(nullValue())); + assertThat(state.getModes().size(), is(0)); + + assertThat(state.getChannel(EmotivaSubscriptionTags.keepAlive), is(Optional.empty())); + + assertThat(state.getCommandMap(MAP_SOURCES_MAIN_ZONE).size(), is(0)); + assertThat(state.getCommandMap(MAP_SOURCES_ZONE_2).size(), is(0)); + assertThat(state.getCommandMap(MAP_TUNER_CHANNELS).size(), is(20)); + assertThat(state.getCommandMap(MAP_TUNER_BANDS).size(), is(2)); + assertThat(state.getCommandMap(MAP_MODES).size(), is(0)); + } + + @Test + void updateAndRemoveChannel() { + var state = new EmotivaProcessorState(); + + assertThat(state.getChannel(EmotivaSubscriptionTags.keepAlive.getChannel()), is(Optional.empty())); + + state.updateChannel(EmotivaSubscriptionTags.keepAlive.getChannel(), new DecimalType(10)); + assertThat(state.getChannel(EmotivaSubscriptionTags.keepAlive.getChannel()), + is(Optional.of(new DecimalType(10)))); + + state.removeChannel(EmotivaSubscriptionTags.keepAlive.getChannel()); + assertThat(state.getChannel(EmotivaSubscriptionTags.keepAlive.getChannel()), is(Optional.empty())); + } + + @Test + void replaceSourcesMap() { + var state = new EmotivaProcessorState(); + + assertThat(state.getSourcesMainZone(), not(nullValue())); + assertThat(state.getSourcesMainZone().size(), is(0)); + + EnumMap sourcesMap = new EnumMap<>(EmotivaControlCommands.class); + sourcesMap.put(EmotivaControlCommands.source_1, "HDMI1"); + state.setSourcesMainZone(sourcesMap); + + assertThat(state.getSourcesMainZone(), not(nullValue())); + assertThat(state.getSourcesMainZone().size(), is(1)); + assertThat(state.getSourcesMainZone().get(EmotivaControlCommands.source_1), is("HDMI1")); + } + + @Test + void updateModes() { + var state = new EmotivaProcessorState(); + + state.updateModes(EmotivaSubscriptionTags.mode_auto, "Auto"); + + assertThat(state.getModes(), not(nullValue())); + assertThat(state.getModes().size(), is(1)); + assertThat(state.getModes().get(EmotivaSubscriptionTags.mode_auto), is("Auto")); + + state.updateModes(EmotivaSubscriptionTags.mode_auto, "Custom Label"); + + assertThat(state.getModes(), not(nullValue())); + assertThat(state.getModes().size(), is(1)); + assertThat(state.getModes().get(EmotivaSubscriptionTags.mode_auto), is("Custom Label")); + } + + @Test + void updateSourcesMap() { + var state = new EmotivaProcessorState(); + + EnumMap sourcesMap = new EnumMap<>(EmotivaControlCommands.class); + sourcesMap.put(EmotivaControlCommands.source_1, "HDMI1"); + state.setSourcesMainZone(sourcesMap); + + assertThat(state.getSourcesMainZone(), not(nullValue())); + assertThat(state.getSourcesMainZone().size(), is(1)); + assertThat(state.getSourcesMainZone().get(EmotivaControlCommands.source_1), is("HDMI1")); + + state.updateSourcesMainZone(EmotivaControlCommands.source_1, "SHIELD"); + + assertThat(state.getSourcesMainZone(), not(nullValue())); + assertThat(state.getSourcesMainZone().size(), is(1)); + assertThat(state.getSourcesMainZone().get(EmotivaControlCommands.source_1), is("SHIELD")); + } +} diff --git a/bundles/org.openhab.binding.emotiva/src/test/java/org/openhab/binding/emotiva/internal/dto/EmotivaSubscriptionRequestTest.java b/bundles/org.openhab.binding.emotiva/src/test/java/org/openhab/binding/emotiva/internal/dto/EmotivaSubscriptionRequestTest.java index 8f3c9a8b78157..8315eec64c00b 100644 --- a/bundles/org.openhab.binding.emotiva/src/test/java/org/openhab/binding/emotiva/internal/dto/EmotivaSubscriptionRequestTest.java +++ b/bundles/org.openhab.binding.emotiva/src/test/java/org/openhab/binding/emotiva/internal/dto/EmotivaSubscriptionRequestTest.java @@ -49,20 +49,16 @@ void marshalFromChannelUID() { } @Test - void marshallWithTwoSubscriptionsNoAck() { - EmotivaCommandDTO command1 = new EmotivaCommandDTO(EmotivaControlCommands.volume, "10", "yes"); - EmotivaCommandDTO command2 = new EmotivaCommandDTO(EmotivaControlCommands.power_off); + void marshallWithSubscriptionNoAck() { + EmotivaCommandDTO command = new EmotivaCommandDTO(EmotivaControlCommands.volume, "10", "yes"); - EmotivaSubscriptionRequest dto = new EmotivaSubscriptionRequest(List.of(command1, command2), - PROTOCOL_V2.value()); + EmotivaSubscriptionRequest dto = new EmotivaSubscriptionRequest(command, PROTOCOL_V2.value()); String xmlString = xmlUtils.marshallJAXBElementObjects(dto); assertThat(xmlString, containsString("")); assertThat(xmlString, containsString("")); - assertThat(xmlString, containsString("")); assertThat(xmlString, containsString("")); assertThat(xmlString, not(containsString(""))); - assertThat(xmlString, not(containsString(""))); } @Test diff --git a/bundles/org.openhab.binding.emotiva/src/test/java/org/openhab/binding/emotiva/internal/dto/EmotivaUnsubscriptionTest.java b/bundles/org.openhab.binding.emotiva/src/test/java/org/openhab/binding/emotiva/internal/dto/EmotivaUnsubscriptionTest.java index 95f4101e65eca..e12c5b7570837 100644 --- a/bundles/org.openhab.binding.emotiva/src/test/java/org/openhab/binding/emotiva/internal/dto/EmotivaUnsubscriptionTest.java +++ b/bundles/org.openhab.binding.emotiva/src/test/java/org/openhab/binding/emotiva/internal/dto/EmotivaUnsubscriptionTest.java @@ -16,8 +16,6 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.openhab.binding.emotiva.internal.EmotivaBindingConstants.CHANNEL_TUNER_RDS; -import java.util.List; - import javax.xml.bind.JAXBException; import org.eclipse.jdt.annotation.NonNullByDefault; @@ -50,16 +48,13 @@ void marshalFromChannelUID() { @Test void marshallWithTwoUnsubscriptions() { EmotivaCommandDTO command1 = new EmotivaCommandDTO(EmotivaControlCommands.volume); - EmotivaCommandDTO command2 = new EmotivaCommandDTO(EmotivaControlCommands.power_off); - EmotivaUnsubscribeDTO dto = new EmotivaUnsubscribeDTO(List.of(command1, command2)); + EmotivaUnsubscribeDTO dto = new EmotivaUnsubscribeDTO(command1); String xmlString = xmlUtils.marshallJAXBElementObjects(dto); assertThat(xmlString, containsString("")); assertThat(xmlString, containsString("")); - assertThat(xmlString, containsString("")); assertThat(xmlString, containsString("")); assertThat(xmlString, not(containsString(""))); - assertThat(xmlString, not(containsString(""))); } } diff --git a/bundles/org.openhab.binding.emotiva/src/test/java/org/openhab/binding/emotiva/internal/protocol/EmotivaControlRequestTest.java b/bundles/org.openhab.binding.emotiva/src/test/java/org/openhab/binding/emotiva/internal/protocol/EmotivaControlRequestTest.java index 754f18a1cd64a..51f85fb247ac1 100644 --- a/bundles/org.openhab.binding.emotiva/src/test/java/org/openhab/binding/emotiva/internal/protocol/EmotivaControlRequestTest.java +++ b/bundles/org.openhab.binding.emotiva/src/test/java/org/openhab/binding/emotiva/internal/protocol/EmotivaControlRequestTest.java @@ -17,15 +17,12 @@ import static org.openhab.binding.emotiva.internal.EmotivaBindingConstants.*; import static org.openhab.binding.emotiva.internal.protocol.EmotivaControlCommands.*; import static org.openhab.binding.emotiva.internal.protocol.EmotivaProtocolVersion.*; -import static org.openhab.binding.emotiva.internal.protocol.EmotivaSubscriptionTags.tuner_band; -import static org.openhab.binding.emotiva.internal.protocol.EmotivaSubscriptionTags.tuner_channel; import static org.openhab.core.types.RefreshType.REFRESH; import java.util.Collections; import java.util.EnumMap; import java.util.HashMap; import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Stream; import org.eclipse.jdt.annotation.NonNullByDefault; @@ -33,8 +30,8 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; -import org.openhab.binding.emotiva.internal.EmotivaBindingConstants; import org.openhab.binding.emotiva.internal.EmotivaCommandHelper; +import org.openhab.binding.emotiva.internal.EmotivaProcessorState; import org.openhab.binding.emotiva.internal.dto.EmotivaControlDTO; import org.openhab.core.library.types.DecimalType; import org.openhab.core.library.types.OnOffType; @@ -220,7 +217,7 @@ private static Stream channelToDTOs() { private static final EnumMap RADIO_BAND_MAP = new EnumMap<>( EmotivaControlCommands.class); private static final Map STATE_MAP = Collections.synchronizedMap(new HashMap<>()); - private static final Map> COMMAND_MAPS = new ConcurrentHashMap<>(); + private static final EmotivaProcessorState state = new EmotivaProcessorState(); @BeforeAll static void beforeAll() { @@ -228,7 +225,7 @@ static void beforeAll() { MAP_SOURCES_MAIN_ZONE.put(source_2, "SHIELD"); MAP_SOURCES_MAIN_ZONE.put(hdmi1, "HDMI1"); MAP_SOURCES_MAIN_ZONE.put(coax1, "Coax 1"); - COMMAND_MAPS.put(EmotivaBindingConstants.MAP_SOURCES_MAIN_ZONE, MAP_SOURCES_MAIN_ZONE); + state.setSourcesMainZone(MAP_SOURCES_MAIN_ZONE); MAP_SOURCES_ZONE_2.put(source_1, "HDMI 1"); MAP_SOURCES_ZONE_2.put(source_2, "SHIELD"); @@ -236,16 +233,16 @@ static void beforeAll() { MAP_SOURCES_ZONE_2.put(zone2_coax1, "Coax 1"); MAP_SOURCES_ZONE_2.put(zone2_ARC, "Audio Return Channel"); MAP_SOURCES_ZONE_2.put(zone2_follow_main, "Follow Main"); - COMMAND_MAPS.put(EmotivaBindingConstants.MAP_SOURCES_ZONE_2, MAP_SOURCES_ZONE_2); + state.setSourcesZone2(MAP_SOURCES_ZONE_2); CHANNEL_MAP.put(channel_1, "Channel 1"); CHANNEL_MAP.put(channel_2, "Channel 2"); CHANNEL_MAP.put(channel_3, "My Radio Channel"); - COMMAND_MAPS.put(tuner_channel.getEmotivaName(), CHANNEL_MAP); + state.setChannels(CHANNEL_MAP); RADIO_BAND_MAP.put(band_am, "AM"); RADIO_BAND_MAP.put(band_fm, "FM"); - COMMAND_MAPS.put(tuner_band.getEmotivaName(), RADIO_BAND_MAP); + state.setTunerBands(RADIO_BAND_MAP); STATE_MAP.put(CHANNEL_TREBLE, new DecimalType(-3)); STATE_MAP.put(CHANNEL_TUNER_CHANNEL, new StringType("FM 87.50MHz")); @@ -256,7 +253,7 @@ static void beforeAll() { @MethodSource("channelToDTOs") void createDTO(String channel, Command ohValue, EmotivaControlCommands controlCommand, EmotivaProtocolVersion protocolVersion, String requestValue) { - EmotivaControlRequest controlRequest = EmotivaCommandHelper.channelToControlRequest(channel, COMMAND_MAPS, + EmotivaControlRequest controlRequest = EmotivaCommandHelper.channelToControlRequest(channel, state, protocolVersion); EmotivaControlDTO dto = controlRequest.createDTO(ohValue, STATE_MAP.get(channel));