From 533c72db9cfdbdd768b71988ff8e66ddb91ef463 Mon Sep 17 00:00:00 2001 From: Jacob Laursen Date: Mon, 17 Jan 2022 13:41:33 +0100 Subject: [PATCH] [hdpowerview] Refactor dynamic channels (#11853) * Extract dynamic channel creation to separate classes. * Avoid double list allocations. * Add test coverage for scenarios with no channels built. * Extract common builder stuff to super class. * Fix grammar. * Reduce constructor access modifiers. * Removed unneeded this keyword for protected method. * Fix null annotation issues. Signed-off-by: Jacob Laursen Signed-off-by: Andras Uhrin --- .../builders/AutomationChannelBuilder.java | 252 +++++++++++++++++ .../internal/builders/BaseChannelBuilder.java | 53 ++++ .../builders/SceneChannelBuilder.java | 100 +++++++ .../builders/SceneGroupChannelBuilder.java | 102 +++++++ .../handler/HDPowerViewHubHandler.java | 189 +++---------- .../OH-INF/i18n/hdpowerview.properties | 12 +- .../OH-INF/i18n/hdpowerview_da.properties | 12 +- .../resources/OH-INF/thing/thing-types.xml | 5 +- .../AutomationChannelBuilderTest.java | 264 ++++++++++++++++++ .../hdpowerview/LocaleProviderForTests.java | 30 ++ .../hdpowerview/SceneChannelBuilderTest.java | 105 +++++++ .../SceneGroupChannelBuilderTest.java | 105 +++++++ .../TranslationProviderForTests.java | 66 +++++ 13 files changed, 1123 insertions(+), 172 deletions(-) create mode 100644 bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/builders/AutomationChannelBuilder.java create mode 100644 bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/builders/BaseChannelBuilder.java create mode 100644 bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/builders/SceneChannelBuilder.java create mode 100644 bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/builders/SceneGroupChannelBuilder.java create mode 100644 bundles/org.openhab.binding.hdpowerview/src/test/java/org/openhab/binding/hdpowerview/AutomationChannelBuilderTest.java create mode 100644 bundles/org.openhab.binding.hdpowerview/src/test/java/org/openhab/binding/hdpowerview/LocaleProviderForTests.java create mode 100644 bundles/org.openhab.binding.hdpowerview/src/test/java/org/openhab/binding/hdpowerview/SceneChannelBuilderTest.java create mode 100644 bundles/org.openhab.binding.hdpowerview/src/test/java/org/openhab/binding/hdpowerview/SceneGroupChannelBuilderTest.java create mode 100644 bundles/org.openhab.binding.hdpowerview/src/test/java/org/openhab/binding/hdpowerview/TranslationProviderForTests.java diff --git a/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/builders/AutomationChannelBuilder.java b/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/builders/AutomationChannelBuilder.java new file mode 100644 index 0000000000000..10782b22b991c --- /dev/null +++ b/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/builders/AutomationChannelBuilder.java @@ -0,0 +1,252 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.hdpowerview.internal.builders; + +import java.time.DayOfWeek; +import java.time.LocalTime; +import java.time.format.TextStyle; +import java.util.EnumSet; +import java.util.List; +import java.util.Map; +import java.util.StringJoiner; +import java.util.stream.Collectors; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.hdpowerview.internal.HDPowerViewBindingConstants; +import org.openhab.binding.hdpowerview.internal.HDPowerViewTranslationProvider; +import org.openhab.binding.hdpowerview.internal.api.responses.SceneCollections.SceneCollection; +import org.openhab.binding.hdpowerview.internal.api.responses.Scenes.Scene; +import org.openhab.binding.hdpowerview.internal.api.responses.ScheduledEvents; +import org.openhab.binding.hdpowerview.internal.api.responses.ScheduledEvents.ScheduledEvent; +import org.openhab.core.library.CoreItemFactory; +import org.openhab.core.thing.Channel; +import org.openhab.core.thing.ChannelGroupUID; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.binding.builder.ChannelBuilder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link AutomationChannelBuilder} class creates automation channels + * from structured scheduled event data. + * + * @author Jacob Laursen - Initial contribution + */ +@NonNullByDefault +public class AutomationChannelBuilder extends BaseChannelBuilder { + + private final Logger logger = LoggerFactory.getLogger(AutomationChannelBuilder.class); + + @Nullable + private Map scenes; + @Nullable + private Map sceneCollections; + @Nullable + private List scheduledEvents; + + private AutomationChannelBuilder(HDPowerViewTranslationProvider translationProvider, + ChannelGroupUID channelGroupUid) { + super(translationProvider, channelGroupUid, HDPowerViewBindingConstants.CHANNELTYPE_AUTOMATION_ENABLED); + } + + /** + * Creates an {@link AutomationChannelBuilder} for the given {@link HDPowerViewTranslationProvider} and + * {@link ChannelGroupUID}. + * + * @param translationProvider the {@link HDPowerViewTranslationProvider} + * @param channelGroupUid parent {@link ChannelGroupUID} for created channels + * @return channel builder + */ + public static AutomationChannelBuilder create(HDPowerViewTranslationProvider translationProvider, + ChannelGroupUID channelGroupUid) { + return new AutomationChannelBuilder(translationProvider, channelGroupUid); + } + + /** + * Adds created channels to existing list. + * + * @param channels list that channels will be added to + * @return channel builder + */ + public AutomationChannelBuilder withChannels(List channels) { + this.channels = channels; + return this; + } + + /** + * Sets the scenes. + * + * @param scenes the scenes + * @return channel builder + */ + public AutomationChannelBuilder withScenes(List scenes) { + this.scenes = scenes.stream().collect(Collectors.toMap(scene -> scene.id, scene -> scene)); + return this; + } + + /** + * Sets the scene collections. + * + * @param sceneCollections the scene collections + * @return channel builder + */ + public AutomationChannelBuilder withSceneCollections(List sceneCollections) { + this.sceneCollections = sceneCollections.stream() + .collect(Collectors.toMap(sceneCollection -> sceneCollection.id, sceneCollection -> sceneCollection)); + return this; + } + + /** + * Sets the scheduled events. + * + * @param scheduledEvents the sceduled events + * @return channel builder + */ + public AutomationChannelBuilder withScheduledEvents(List scheduledEvents) { + this.scheduledEvents = scheduledEvents; + return this; + } + + /** + * Builds and returns the channels. + * + * @return the {@link Channel} list + */ + public List build() { + List scheduledEvents = this.scheduledEvents; + if (scheduledEvents == null || (scenes == null && sceneCollections == null)) { + return getChannelList(0); + } + List channels = getChannelList(scheduledEvents.size()); + scheduledEvents.stream().forEach(scheduledEvent -> { + Channel channel = createChannel(scheduledEvent); + if (channel != null) { + channels.add(channel); + } + }); + + return channels; + } + + private @Nullable Channel createChannel(ScheduledEvent scheduledEvent) { + String referencedName = getReferencedSceneOrSceneCollectionName(scheduledEvent); + if (referencedName == null) { + return null; + } + ChannelUID channelUid = new ChannelUID(channelGroupUid, Integer.toString(scheduledEvent.id)); + String label = getScheduledEventName(referencedName, scheduledEvent); + String description = translationProvider.getText("dynamic-channel.automation-enabled.description", + referencedName); + Channel channel = ChannelBuilder.create(channelUid, CoreItemFactory.SWITCH).withType(channelTypeUid) + .withLabel(label).withDescription(description).build(); + + return channel; + } + + private @Nullable String getReferencedSceneOrSceneCollectionName(ScheduledEvent scheduledEvent) { + if (scheduledEvent.sceneId > 0) { + Map scenes = this.scenes; + if (scenes == null) { + logger.warn("Scheduled event '{}' references scene '{}', but no scenes are loaded", scheduledEvent.id, + scheduledEvent.sceneId); + return null; + } + Scene scene = scenes.get(scheduledEvent.sceneId); + if (scene != null) { + return scene.getName(); + } + logger.warn("Scene '{}' was not found for scheduled event '{}'", scheduledEvent.sceneId, scheduledEvent.id); + return null; + } else if (scheduledEvent.sceneCollectionId > 0) { + Map sceneCollections = this.sceneCollections; + if (sceneCollections == null) { + logger.warn( + "Scheduled event '{}' references scene collection '{}', but no scene collections are loaded", + scheduledEvent.id, scheduledEvent.sceneCollectionId); + return null; + } + SceneCollection sceneCollection = sceneCollections.get(scheduledEvent.sceneCollectionId); + if (sceneCollection != null) { + return sceneCollection.getName(); + } + logger.warn("Scene collection '{}' was not found for scheduled event '{}'", + scheduledEvent.sceneCollectionId, scheduledEvent.id); + return null; + } else { + logger.warn("Scheduled event '{}'' not related to any scene or scene collection", scheduledEvent.id); + return null; + } + } + + private String getScheduledEventName(String sceneName, ScheduledEvent scheduledEvent) { + String timeString, daysString; + + switch (scheduledEvent.eventType) { + case ScheduledEvents.SCHEDULED_EVENT_TYPE_TIME: + timeString = LocalTime.of(scheduledEvent.hour, scheduledEvent.minute).toString(); + break; + case ScheduledEvents.SCHEDULED_EVENT_TYPE_SUNRISE: + if (scheduledEvent.minute == 0) { + timeString = translationProvider.getText("dynamic-channel.automation.at-sunrise"); + } else if (scheduledEvent.minute < 0) { + timeString = translationProvider.getText("dynamic-channel.automation.before-sunrise", + getFormattedTimeOffset(-scheduledEvent.minute)); + } else { + timeString = translationProvider.getText("dynamic-channel.automation.after-sunrise", + getFormattedTimeOffset(scheduledEvent.minute)); + } + break; + case ScheduledEvents.SCHEDULED_EVENT_TYPE_SUNSET: + if (scheduledEvent.minute == 0) { + timeString = translationProvider.getText("dynamic-channel.automation.at-sunset"); + } else if (scheduledEvent.minute < 0) { + timeString = translationProvider.getText("dynamic-channel.automation.before-sunset", + getFormattedTimeOffset(-scheduledEvent.minute)); + } else { + timeString = translationProvider.getText("dynamic-channel.automation.after-sunset", + getFormattedTimeOffset(scheduledEvent.minute)); + } + break; + default: + return sceneName; + } + + EnumSet days = scheduledEvent.getDays(); + if (EnumSet.allOf(DayOfWeek.class).equals(days)) { + daysString = translationProvider.getText("dynamic-channel.automation.all-days"); + } else if (ScheduledEvents.WEEKDAYS.equals(days)) { + daysString = translationProvider.getText("dynamic-channel.automation.weekdays"); + } else if (ScheduledEvents.WEEKENDS.equals(days)) { + daysString = translationProvider.getText("dynamic-channel.automation.weekends"); + } else { + StringJoiner joiner = new StringJoiner(", "); + days.forEach(day -> joiner.add(day.getDisplayName(TextStyle.SHORT, translationProvider.getLocale()))); + daysString = joiner.toString(); + } + + return translationProvider.getText("dynamic-channel.automation-enabled.label", sceneName, timeString, + daysString); + } + + private String getFormattedTimeOffset(int minutes) { + if (minutes >= 60) { + int remainder = minutes % 60; + if (remainder == 0) { + return translationProvider.getText("dynamic-channel.automation.hour", minutes / 60); + } + return translationProvider.getText("dynamic-channel.automation.hour-minute", minutes / 60, remainder); + } + return translationProvider.getText("dynamic-channel.automation.minute", minutes); + } +} diff --git a/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/builders/BaseChannelBuilder.java b/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/builders/BaseChannelBuilder.java new file mode 100644 index 0000000000000..6ac875c460c90 --- /dev/null +++ b/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/builders/BaseChannelBuilder.java @@ -0,0 +1,53 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.hdpowerview.internal.builders; + +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.hdpowerview.internal.HDPowerViewBindingConstants; +import org.openhab.binding.hdpowerview.internal.HDPowerViewTranslationProvider; +import org.openhab.core.thing.Channel; +import org.openhab.core.thing.ChannelGroupUID; +import org.openhab.core.thing.type.ChannelTypeUID; + +/** + * The {@link BaseChannelBuilder} class is super class for + * channel builders. + * + * @author Jacob Laursen - Initial contribution + */ +@NonNullByDefault +public class BaseChannelBuilder { + + protected final HDPowerViewTranslationProvider translationProvider; + protected final ChannelGroupUID channelGroupUid; + protected final ChannelTypeUID channelTypeUid; + + @Nullable + protected List channels; + + protected BaseChannelBuilder(HDPowerViewTranslationProvider translationProvider, ChannelGroupUID channelGroupUid, + String channelTypeId) { + this.translationProvider = translationProvider; + this.channelGroupUid = channelGroupUid; + this.channelTypeUid = new ChannelTypeUID(HDPowerViewBindingConstants.BINDING_ID, channelTypeId); + } + + protected List getChannelList(int initialCapacity) { + List channels = this.channels; + return channels != null ? channels : new ArrayList<>(initialCapacity); + } +} diff --git a/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/builders/SceneChannelBuilder.java b/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/builders/SceneChannelBuilder.java new file mode 100644 index 0000000000000..ad11985cb4cdc --- /dev/null +++ b/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/builders/SceneChannelBuilder.java @@ -0,0 +1,100 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.hdpowerview.internal.builders; + +import java.util.List; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.hdpowerview.internal.HDPowerViewBindingConstants; +import org.openhab.binding.hdpowerview.internal.HDPowerViewTranslationProvider; +import org.openhab.binding.hdpowerview.internal.api.responses.Scenes.Scene; +import org.openhab.core.library.CoreItemFactory; +import org.openhab.core.thing.Channel; +import org.openhab.core.thing.ChannelGroupUID; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.binding.builder.ChannelBuilder; + +/** + * The {@link SceneChannelBuilder} class creates scene channels + * from structured scene data. + * + * @author Jacob Laursen - Initial contribution + */ +@NonNullByDefault +public class SceneChannelBuilder extends BaseChannelBuilder { + + @Nullable + private List scenes; + + private SceneChannelBuilder(HDPowerViewTranslationProvider translationProvider, ChannelGroupUID channelGroupUid) { + super(translationProvider, channelGroupUid, HDPowerViewBindingConstants.CHANNELTYPE_SCENE_ACTIVATE); + } + + /** + * Creates a {@link SceneChannelBuilder} for the given {@link HDPowerViewTranslationProvider} and + * {@link ChannelGroupUID}. + * + * @param translationProvider the {@link HDPowerViewTranslationProvider} + * @param channelGroupUid parent {@link ChannelGroupUID} for created channels + * @return channel builder + */ + public static SceneChannelBuilder create(HDPowerViewTranslationProvider translationProvider, + ChannelGroupUID channelGroupUid) { + return new SceneChannelBuilder(translationProvider, channelGroupUid); + } + + /** + * Adds created channels to existing list. + * + * @param channels list that channels will be added to + * @return channel builder + */ + public SceneChannelBuilder withChannels(List channels) { + this.channels = channels; + return this; + } + + /** + * Sets the scenes. + * + * @param scenes the scenes + * @return channel builder + */ + public SceneChannelBuilder withScenes(List scenes) { + this.scenes = scenes; + return this; + } + + /** + * Builds and returns the channels. + * + * @return the {@link Channel} list + */ + public List build() { + List scenes = this.scenes; + if (scenes == null) { + return getChannelList(0); + } + List channels = getChannelList(scenes.size()); + scenes.stream().sorted().forEach(scene -> channels.add(createChannel(scene))); + return channels; + } + + private Channel createChannel(Scene scene) { + ChannelUID channelUid = new ChannelUID(channelGroupUid, Integer.toString(scene.id)); + String description = translationProvider.getText("dynamic-channel.scene-activate.description", scene.getName()); + return ChannelBuilder.create(channelUid, CoreItemFactory.SWITCH).withType(channelTypeUid) + .withLabel(scene.getName()).withDescription(description).build(); + } +} diff --git a/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/builders/SceneGroupChannelBuilder.java b/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/builders/SceneGroupChannelBuilder.java new file mode 100644 index 0000000000000..2b70b21d2e6a0 --- /dev/null +++ b/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/builders/SceneGroupChannelBuilder.java @@ -0,0 +1,102 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.hdpowerview.internal.builders; + +import java.util.List; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.hdpowerview.internal.HDPowerViewBindingConstants; +import org.openhab.binding.hdpowerview.internal.HDPowerViewTranslationProvider; +import org.openhab.binding.hdpowerview.internal.api.responses.SceneCollections.SceneCollection; +import org.openhab.core.library.CoreItemFactory; +import org.openhab.core.thing.Channel; +import org.openhab.core.thing.ChannelGroupUID; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.binding.builder.ChannelBuilder; + +/** + * The {@link SceneGroupChannelBuilder} class creates scene group channels + * from structured scene collection data. + * + * @author Jacob Laursen - Initial contribution + */ +@NonNullByDefault +public class SceneGroupChannelBuilder extends BaseChannelBuilder { + + @Nullable + private List sceneCollections; + + private SceneGroupChannelBuilder(HDPowerViewTranslationProvider translationProvider, + ChannelGroupUID channelGroupUid) { + super(translationProvider, channelGroupUid, HDPowerViewBindingConstants.CHANNELTYPE_SCENE_GROUP_ACTIVATE); + } + + /** + * Creates a {@link SceneGroupChannelBuilder} for the given {@link HDPowerViewTranslationProvider} and + * {@link ChannelGroupUID}. + * + * @param translationProvider the {@link HDPowerViewTranslationProvider} + * @param channelGroupUid parent {@link ChannelGroupUID} for created channels + * @return channel builder + */ + public static SceneGroupChannelBuilder create(HDPowerViewTranslationProvider translationProvider, + ChannelGroupUID channelGroupUid) { + return new SceneGroupChannelBuilder(translationProvider, channelGroupUid); + } + + /** + * Adds created channels to existing list. + * + * @param channels list that channels will be added to + * @return channel builder + */ + public SceneGroupChannelBuilder withChannels(List channels) { + this.channels = channels; + return this; + } + + /** + * Sets the scene collections. + * + * @param sceneCollections the scene collections + * @return channel builder + */ + public SceneGroupChannelBuilder withSceneCollections(List sceneCollections) { + this.sceneCollections = sceneCollections; + return this; + } + + /** + * Builds and returns the channels. + * + * @return the {@link Channel} list + */ + public List build() { + List sceneCollections = this.sceneCollections; + if (sceneCollections == null) { + return getChannelList(0); + } + List channels = getChannelList(sceneCollections.size()); + sceneCollections.stream().sorted().forEach(sceneCollection -> channels.add(createChannel(sceneCollection))); + return channels; + } + + private Channel createChannel(SceneCollection sceneCollection) { + ChannelUID channelUid = new ChannelUID(channelGroupUid, Integer.toString(sceneCollection.id)); + String description = translationProvider.getText("dynamic-channel.scene-group-activate.description", + sceneCollection.getName()); + return ChannelBuilder.create(channelUid, CoreItemFactory.SWITCH).withType(channelTypeUid) + .withLabel(sceneCollection.getName()).withDescription(description).build(); + } +} diff --git a/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/handler/HDPowerViewHubHandler.java b/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/handler/HDPowerViewHubHandler.java index 8b3d2c60e844f..b1f4de389ade6 100644 --- a/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/handler/HDPowerViewHubHandler.java +++ b/bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/handler/HDPowerViewHubHandler.java @@ -12,16 +12,11 @@ */ package org.openhab.binding.hdpowerview.internal.handler; -import java.time.DayOfWeek; -import java.time.LocalTime; -import java.time.format.TextStyle; import java.util.ArrayList; -import java.util.EnumSet; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; -import java.util.StringJoiner; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; @@ -44,6 +39,9 @@ import org.openhab.binding.hdpowerview.internal.api.responses.ScheduledEvents.ScheduledEvent; import org.openhab.binding.hdpowerview.internal.api.responses.Shades; import org.openhab.binding.hdpowerview.internal.api.responses.Shades.ShadeData; +import org.openhab.binding.hdpowerview.internal.builders.AutomationChannelBuilder; +import org.openhab.binding.hdpowerview.internal.builders.SceneChannelBuilder; +import org.openhab.binding.hdpowerview.internal.builders.SceneGroupChannelBuilder; import org.openhab.binding.hdpowerview.internal.config.HDPowerViewHubConfiguration; import org.openhab.binding.hdpowerview.internal.config.HDPowerViewShadeConfiguration; import org.openhab.binding.hdpowerview.internal.exceptions.HubException; @@ -261,12 +259,12 @@ private synchronized void poll() { pollShades(); List scenes = updateSceneChannels(); - List sceneCollections = updateSceneCollectionChannels(); - List scheduledEvents = updateScheduledEventChannels(scenes, sceneCollections); + List sceneCollections = updateSceneGroupChannels(); + List scheduledEvents = updateAutomationChannels(scenes, sceneCollections); // Scheduled events should also have their current state updated if event has been // enabled or disabled through app or other integration. - updateScheduledEventStates(scheduledEvents); + updateAutomationStates(scheduledEvents); } catch (HubInvalidResponseException e) { Throwable cause = e.getCause(); if (cause == null) { @@ -383,23 +381,17 @@ private List updateSceneChannels() List allChannels = new ArrayList<>(getThing().getChannels()); allChannels.removeIf(c -> HDPowerViewBindingConstants.CHANNEL_GROUP_SCENES.equals(c.getUID().getGroupId())); - scenes.stream().sorted().forEach(scene -> allChannels.add(createSceneChannel(scene))); - updateThing(editThing().withChannels(allChannels).build()); - createDeprecatedSceneChannels(scenes); + SceneChannelBuilder channelBuilder = SceneChannelBuilder + .create(this.translationProvider, + new ChannelGroupUID(thing.getUID(), HDPowerViewBindingConstants.CHANNEL_GROUP_SCENES)) + .withScenes(scenes).withChannels(allChannels); - return scenes; - } + updateThing(editThing().withChannels(channelBuilder.build()).build()); - private Channel createSceneChannel(Scene scene) { - ChannelGroupUID channelGroupUid = new ChannelGroupUID(thing.getUID(), - HDPowerViewBindingConstants.CHANNEL_GROUP_SCENES); - ChannelUID channelUid = new ChannelUID(channelGroupUid, Integer.toString(scene.id)); - String description = translationProvider.getText("dynamic-channel.scene-activate.description", scene.getName()); - Channel channel = ChannelBuilder.create(channelUid, CoreItemFactory.SWITCH).withType(sceneChannelTypeUID) - .withLabel(scene.getName()).withDescription(description).build(); + createDeprecatedSceneChannels(scenes); - return channel; + return scenes; } /** @@ -460,40 +452,32 @@ private List fetchSceneCollections() return sceneCollectionData; } - private List updateSceneCollectionChannels() + private List updateSceneGroupChannels() throws HubInvalidResponseException, HubProcessingException, HubMaintenanceException { List sceneCollections = fetchSceneCollections(); if (sceneCollections.size() == sceneCollectionCache.size() && sceneCollectionCache.containsAll(sceneCollections)) { // Duplicates are not allowed. Reordering is not supported. - logger.debug("Preserving scene collection channels, no changes detected"); + logger.debug("Preserving scene group channels, no changes detected"); return sceneCollections; } - logger.debug("Updating all scene collection channels, changes detected"); + logger.debug("Updating all scene group channels, changes detected"); sceneCollectionCache = new CopyOnWriteArrayList(sceneCollections); List allChannels = new ArrayList<>(getThing().getChannels()); allChannels .removeIf(c -> HDPowerViewBindingConstants.CHANNEL_GROUP_SCENE_GROUPS.equals(c.getUID().getGroupId())); - sceneCollections.stream().sorted() - .forEach(sceneCollection -> allChannels.add(createSceneCollectionChannel(sceneCollection))); - updateThing(editThing().withChannels(allChannels).build()); - return sceneCollections; - } + SceneGroupChannelBuilder channelBuilder = SceneGroupChannelBuilder + .create(this.translationProvider, + new ChannelGroupUID(thing.getUID(), HDPowerViewBindingConstants.CHANNEL_GROUP_SCENE_GROUPS)) + .withSceneCollections(sceneCollections).withChannels(allChannels); - private Channel createSceneCollectionChannel(SceneCollection sceneCollection) { - ChannelGroupUID channelGroupUid = new ChannelGroupUID(thing.getUID(), - HDPowerViewBindingConstants.CHANNEL_GROUP_SCENE_GROUPS); - ChannelUID channelUid = new ChannelUID(channelGroupUid, Integer.toString(sceneCollection.id)); - String description = translationProvider.getText("dynamic-channel.scene-group-activate.description", - sceneCollection.getName()); - Channel channel = ChannelBuilder.create(channelUid, CoreItemFactory.SWITCH).withType(sceneGroupChannelTypeUID) - .withLabel(sceneCollection.getName()).withDescription(description).build(); - - return channel; + updateThing(editThing().withChannels(channelBuilder.build()).build()); + + return sceneCollections; } private List fetchScheduledEvents() @@ -513,140 +497,33 @@ private List fetchScheduledEvents() return scheduledEventData; } - private List updateScheduledEventChannels(List scenes, - List sceneCollections) + private List updateAutomationChannels(List scenes, List sceneCollections) throws HubInvalidResponseException, HubProcessingException, HubMaintenanceException { List scheduledEvents = fetchScheduledEvents(); if (scheduledEvents.size() == scheduledEventCache.size() && scheduledEventCache.containsAll(scheduledEvents)) { // Duplicates are not allowed. Reordering is not supported. - logger.debug("Preserving scheduled event channels, no changes detected"); + logger.debug("Preserving automation channels, no changes detected"); return scheduledEvents; } - logger.debug("Updating all scheduled event channels, changes detected"); + logger.debug("Updating all automation channels, changes detected"); scheduledEventCache = new CopyOnWriteArrayList(scheduledEvents); List allChannels = new ArrayList<>(getThing().getChannels()); allChannels .removeIf(c -> HDPowerViewBindingConstants.CHANNEL_GROUP_AUTOMATIONS.equals(c.getUID().getGroupId())); - scheduledEvents.stream().forEach(scheduledEvent -> { - Channel channel = createScheduledEventChannel(scheduledEvent, scenes, sceneCollections); - if (channel != null) { - allChannels.add(channel); - } - }); - updateThing(editThing().withChannels(allChannels).build()); + AutomationChannelBuilder channelBuilder = AutomationChannelBuilder + .create(this.translationProvider, + new ChannelGroupUID(thing.getUID(), HDPowerViewBindingConstants.CHANNEL_GROUP_AUTOMATIONS)) + .withScenes(scenes).withSceneCollections(sceneCollections).withScheduledEvents(scheduledEvents) + .withChannels(allChannels); + updateThing(editThing().withChannels(channelBuilder.build()).build()); return scheduledEvents; } - private @Nullable Channel createScheduledEventChannel(ScheduledEvent scheduledEvent, List scenes, - List sceneCollections) { - String referencedName = getReferencedSceneOrSceneCollectionName(scheduledEvent, scenes, sceneCollections); - if (referencedName == null) { - return null; - } - ChannelGroupUID channelGroupUid = new ChannelGroupUID(thing.getUID(), - HDPowerViewBindingConstants.CHANNEL_GROUP_AUTOMATIONS); - ChannelUID channelUid = new ChannelUID(channelGroupUid, Integer.toString(scheduledEvent.id)); - String label = getScheduledEventName(referencedName, scheduledEvent); - String description = translationProvider.getText("dynamic-channel.automation-enabled.description", - referencedName); - Channel channel = ChannelBuilder.create(channelUid, CoreItemFactory.SWITCH).withType(automationChannelTypeUID) - .withLabel(label).withDescription(description).build(); - - return channel; - } - - private @Nullable String getReferencedSceneOrSceneCollectionName(ScheduledEvent scheduledEvent, List scenes, - List sceneCollections) { - if (scheduledEvent.sceneId > 0) { - for (Scene scene : scenes) { - if (scene.id == scheduledEvent.sceneId) { - return scene.getName(); - } - } - logger.error("Scene '{}' was not found for scheduled event '{}'", scheduledEvent.sceneId, - scheduledEvent.id); - return null; - } else if (scheduledEvent.sceneCollectionId > 0) { - for (SceneCollection sceneCollection : sceneCollections) { - if (sceneCollection.id == scheduledEvent.sceneCollectionId) { - return sceneCollection.getName(); - } - } - logger.error("Scene collection '{}' was not found for scheduled event '{}'", - scheduledEvent.sceneCollectionId, scheduledEvent.id); - return null; - } else { - logger.error("Scheduled event '{}'' not related to any scene or scene collection", scheduledEvent.id); - return null; - } - } - - private String getScheduledEventName(String sceneName, ScheduledEvent scheduledEvent) { - String timeString, daysString; - - switch (scheduledEvent.eventType) { - case ScheduledEvents.SCHEDULED_EVENT_TYPE_TIME: - timeString = LocalTime.of(scheduledEvent.hour, scheduledEvent.minute).toString(); - break; - case ScheduledEvents.SCHEDULED_EVENT_TYPE_SUNRISE: - if (scheduledEvent.minute == 0) { - timeString = translationProvider.getText("dynamic-channel.automation.at_sunrise"); - } else if (scheduledEvent.minute < 0) { - timeString = translationProvider.getText("dynamic-channel.automation.before_sunrise", - getFormattedTimeOffset(-scheduledEvent.minute)); - } else { - timeString = translationProvider.getText("dynamic-channel.automation.after_sunrise", - getFormattedTimeOffset(scheduledEvent.minute)); - } - break; - case ScheduledEvents.SCHEDULED_EVENT_TYPE_SUNSET: - if (scheduledEvent.minute == 0) { - timeString = translationProvider.getText("dynamic-channel.automation.at_sunset"); - } else if (scheduledEvent.minute < 0) { - timeString = translationProvider.getText("dynamic-channel.automation.before_sunset", - getFormattedTimeOffset(-scheduledEvent.minute)); - } else { - timeString = translationProvider.getText("dynamic-channel.automation.after_sunset", - getFormattedTimeOffset(scheduledEvent.minute)); - } - break; - default: - return sceneName; - } - - EnumSet days = scheduledEvent.getDays(); - if (EnumSet.allOf(DayOfWeek.class).equals(days)) { - daysString = translationProvider.getText("dynamic-channel.automation.all-days"); - } else if (ScheduledEvents.WEEKDAYS.equals(days)) { - daysString = translationProvider.getText("dynamic-channel.automation.weekdays"); - } else if (ScheduledEvents.WEEKENDS.equals(days)) { - daysString = translationProvider.getText("dynamic-channel.automation.weekends"); - } else { - StringJoiner joiner = new StringJoiner(", "); - days.forEach(day -> joiner.add(day.getDisplayName(TextStyle.SHORT, translationProvider.getLocale()))); - daysString = joiner.toString(); - } - - return translationProvider.getText("dynamic-channel.automation-enabled.label", sceneName, timeString, - daysString); - } - - private String getFormattedTimeOffset(int minutes) { - if (minutes >= 60) { - int remainder = minutes % 60; - if (remainder == 0) { - return translationProvider.getText("dynamic-channel.automation.hour", minutes / 60); - } - return translationProvider.getText("dynamic-channel.automation.hour-minute", minutes / 60, remainder); - } - return translationProvider.getText("dynamic-channel.automation.minute", minutes); - } - - private void updateScheduledEventStates(List scheduledEvents) { + private void updateAutomationStates(List scheduledEvents) { ChannelGroupUID channelGroupUid = new ChannelGroupUID(thing.getUID(), HDPowerViewBindingConstants.CHANNEL_GROUP_AUTOMATIONS); for (ScheduledEvent scheduledEvent : scheduledEvents) { diff --git a/bundles/org.openhab.binding.hdpowerview/src/main/resources/OH-INF/i18n/hdpowerview.properties b/bundles/org.openhab.binding.hdpowerview/src/main/resources/OH-INF/i18n/hdpowerview.properties index 4a6ec1312e3d6..b9b8efc4c9cbc 100644 --- a/bundles/org.openhab.binding.hdpowerview/src/main/resources/OH-INF/i18n/hdpowerview.properties +++ b/bundles/org.openhab.binding.hdpowerview/src/main/resources/OH-INF/i18n/hdpowerview.properties @@ -52,12 +52,12 @@ dynamic-channel.automation-enabled.label = {0}, {1}, {2} dynamic-channel.automation.hour = {0}hr dynamic-channel.automation.minute = {0}m dynamic-channel.automation.hour-minute = {0}hr {1}m -dynamic-channel.automation.at_sunrise = At sunrise -dynamic-channel.automation.before_sunrise = {0} before sunrise -dynamic-channel.automation.after_sunrise = {0} after sunrise -dynamic-channel.automation.at_sunset = At sunset -dynamic-channel.automation.before_sunset = {0} before sunset -dynamic-channel.automation.after_sunset = {0} after sunset +dynamic-channel.automation.at-sunrise = At sunrise +dynamic-channel.automation.before-sunrise = {0} before sunrise +dynamic-channel.automation.after-sunrise = {0} after sunrise +dynamic-channel.automation.at-sunset = At sunset +dynamic-channel.automation.before-sunset = {0} before sunset +dynamic-channel.automation.after-sunset = {0} after sunset dynamic-channel.automation.weekdays = Weekdays dynamic-channel.automation.weekends = Weekends dynamic-channel.automation.all-days = All days diff --git a/bundles/org.openhab.binding.hdpowerview/src/main/resources/OH-INF/i18n/hdpowerview_da.properties b/bundles/org.openhab.binding.hdpowerview/src/main/resources/OH-INF/i18n/hdpowerview_da.properties index 6b4483f261e17..7f3a7fdf0c1f0 100644 --- a/bundles/org.openhab.binding.hdpowerview/src/main/resources/OH-INF/i18n/hdpowerview_da.properties +++ b/bundles/org.openhab.binding.hdpowerview/src/main/resources/OH-INF/i18n/hdpowerview_da.properties @@ -8,12 +8,12 @@ dynamic-channel.automation-enabled.label = {0}, {1}, {2} dynamic-channel.automation.hour = {0}t dynamic-channel.automation.minute = {0}m dynamic-channel.automation.hour-minute = {0}t {1}m -dynamic-channel.automation.at_sunrise = Ved solopgang -dynamic-channel.automation.before_sunrise = {0} før solopgang -dynamic-channel.automation.after_sunrise = {0} efter solopgang -dynamic-channel.automation.at_sunset = Ved solnedgang -dynamic-channel.automation.before_sunset = {0} før solnedgang -dynamic-channel.automation.after_sunset = {0} efter solnedgang +dynamic-channel.automation.at-sunrise = Ved solopgang +dynamic-channel.automation.before-sunrise = {0} før solopgang +dynamic-channel.automation.after-sunrise = {0} efter solopgang +dynamic-channel.automation.at-sunset = Ved solnedgang +dynamic-channel.automation.before-sunset = {0} før solnedgang +dynamic-channel.automation.after-sunset = {0} efter solnedgang dynamic-channel.automation.weekdays = Ugedage dynamic-channel.automation.weekends = Weekend dynamic-channel.automation.all-days = Alle dage diff --git a/bundles/org.openhab.binding.hdpowerview/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.hdpowerview/src/main/resources/OH-INF/thing/thing-types.xml index 80b128b60278b..595392183ac75 100644 --- a/bundles/org.openhab.binding.hdpowerview/src/main/resources/OH-INF/thing/thing-types.xml +++ b/bundles/org.openhab.binding.hdpowerview/src/main/resources/OH-INF/thing/thing-types.xml @@ -18,6 +18,7 @@ Hunter Douglas (Luxaflex) PowerView Hub + host @@ -70,10 +71,6 @@ Hunter Douglas (Luxaflex) PowerView Motorized Shade - - - - id diff --git a/bundles/org.openhab.binding.hdpowerview/src/test/java/org/openhab/binding/hdpowerview/AutomationChannelBuilderTest.java b/bundles/org.openhab.binding.hdpowerview/src/test/java/org/openhab/binding/hdpowerview/AutomationChannelBuilderTest.java new file mode 100644 index 0000000000000..e03b0dd5f0f02 --- /dev/null +++ b/bundles/org.openhab.binding.hdpowerview/src/test/java/org/openhab/binding/hdpowerview/AutomationChannelBuilderTest.java @@ -0,0 +1,264 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.hdpowerview; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.mock; + +import java.util.ArrayList; +import java.util.Base64; +import java.util.List; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.openhab.binding.hdpowerview.internal.HDPowerViewBindingConstants; +import org.openhab.binding.hdpowerview.internal.HDPowerViewTranslationProvider; +import org.openhab.binding.hdpowerview.internal.api.responses.SceneCollections.SceneCollection; +import org.openhab.binding.hdpowerview.internal.api.responses.Scenes.Scene; +import org.openhab.binding.hdpowerview.internal.api.responses.ScheduledEvents; +import org.openhab.binding.hdpowerview.internal.api.responses.ScheduledEvents.ScheduledEvent; +import org.openhab.binding.hdpowerview.internal.builders.AutomationChannelBuilder; +import org.openhab.core.thing.Channel; +import org.openhab.core.thing.ChannelGroupUID; +import org.openhab.core.thing.ThingUID; +import org.osgi.framework.Bundle; + +/** + * Unit tests for {@link AutomationChannelBuilder}. + * + * @author Jacob Laursen - Initial contribution + */ +@NonNullByDefault +public class AutomationChannelBuilderTest { + + private static final ChannelGroupUID CHANNEL_GROUP_UID = new ChannelGroupUID( + new ThingUID(HDPowerViewBindingConstants.BINDING_ID, AutomationChannelBuilderTest.class.getSimpleName()), + HDPowerViewBindingConstants.CHANNELTYPE_AUTOMATION_ENABLED); + + private static final HDPowerViewTranslationProvider translationProvider = new HDPowerViewTranslationProvider( + mock(Bundle.class), new TranslationProviderForTests(), new LocaleProviderForTests()); + private AutomationChannelBuilder builder = AutomationChannelBuilder.create(translationProvider, CHANNEL_GROUP_UID); + private List scenes = new ArrayList<>(); + private List sceneCollections = new ArrayList<>(); + + @BeforeEach + private void setUp() { + builder = AutomationChannelBuilder.create(translationProvider, CHANNEL_GROUP_UID); + + Scene scene = new Scene(); + scene.id = 1; + scene.name = Base64.getEncoder().encodeToString(("TestScene").getBytes()); + scenes = new ArrayList<>(List.of(scene)); + + SceneCollection sceneCollection = new SceneCollection(); + sceneCollection.id = 2; + sceneCollection.name = Base64.getEncoder().encodeToString(("TestSceneCollection").getBytes()); + sceneCollections = new ArrayList<>(List.of(sceneCollection)); + } + + @Test + public void sceneSunriseWeekends() { + ScheduledEvent scheduledEvent = createScheduledEventWithScene(ScheduledEvents.SCHEDULED_EVENT_TYPE_SUNRISE); + scheduledEvent.daySaturday = true; + scheduledEvent.daySunday = true; + + List scheduledEvents = new ArrayList<>(List.of(scheduledEvent)); + List channels = builder.withScenes(scenes).withScheduledEvents(scheduledEvents).build(); + + assertEquals(1, channels.size()); + assertEquals("TestScene, At sunrise, Weekends", channels.get(0).getLabel()); + } + + @Test + public void sceneSunsetWeekdays() { + ScheduledEvent scheduledEvent = createScheduledEventWithScene(ScheduledEvents.SCHEDULED_EVENT_TYPE_SUNSET); + scheduledEvent.dayMonday = true; + scheduledEvent.dayTuesday = true; + scheduledEvent.dayWednesday = true; + scheduledEvent.dayThursday = true; + scheduledEvent.dayFriday = true; + + List scheduledEvents = new ArrayList<>(List.of(scheduledEvent)); + List channels = builder.withScenes(scenes).withScheduledEvents(scheduledEvents).build(); + + assertEquals(1, channels.size()); + assertEquals("TestScene, At sunset, Weekdays", channels.get(0).getLabel()); + } + + @Test + public void sceneTimeAllDays() { + ScheduledEvent scheduledEvent = createScheduledEventWithScene(ScheduledEvents.SCHEDULED_EVENT_TYPE_TIME); + scheduledEvent.dayMonday = true; + scheduledEvent.dayTuesday = true; + scheduledEvent.dayWednesday = true; + scheduledEvent.dayThursday = true; + scheduledEvent.dayFriday = true; + scheduledEvent.daySaturday = true; + scheduledEvent.daySunday = true; + scheduledEvent.hour = 6; + scheduledEvent.minute = 30; + + List scheduledEvents = new ArrayList<>(List.of(scheduledEvent)); + List channels = builder.withScenes(scenes).withScheduledEvents(scheduledEvents).build(); + + assertEquals(1, channels.size()); + assertEquals("TestScene, 06:30, All days", channels.get(0).getLabel()); + } + + @Test + public void sceneMinutesBeforeSunriseMondayTuesday() { + ScheduledEvent scheduledEvent = createScheduledEventWithScene(ScheduledEvents.SCHEDULED_EVENT_TYPE_SUNRISE); + scheduledEvent.dayMonday = true; + scheduledEvent.dayTuesday = true; + scheduledEvent.minute = -15; + + List scheduledEvents = new ArrayList<>(List.of(scheduledEvent)); + List channels = builder.withScenes(scenes).withScheduledEvents(scheduledEvents).build(); + + assertEquals(1, channels.size()); + assertEquals("TestScene, 15m before sunrise, Mon, Tue", channels.get(0).getLabel()); + } + + @Test + public void sceneHoursMinutesAfterSunriseMonday() { + ScheduledEvent scheduledEvent = createScheduledEventWithScene(ScheduledEvents.SCHEDULED_EVENT_TYPE_SUNRISE); + scheduledEvent.dayMonday = true; + scheduledEvent.minute = 61; + + List scheduledEvents = new ArrayList<>(List.of(scheduledEvent)); + List channels = builder.withScenes(scenes).withScheduledEvents(scheduledEvents).build(); + + assertEquals(1, channels.size()); + assertEquals("TestScene, 1hr 1m after sunrise, Mon", channels.get(0).getLabel()); + } + + @Test + public void sceneMinutesBeforeSunsetWednesdayThursdayFriday() { + ScheduledEvent scheduledEvent = createScheduledEventWithScene(ScheduledEvents.SCHEDULED_EVENT_TYPE_SUNSET); + scheduledEvent.dayWednesday = true; + scheduledEvent.dayThursday = true; + scheduledEvent.dayFriday = true; + scheduledEvent.minute = -59; + + List scheduledEvents = new ArrayList<>(List.of(scheduledEvent)); + List channels = builder.withScenes(scenes).withScheduledEvents(scheduledEvents).build(); + + assertEquals(1, channels.size()); + assertEquals("TestScene, 59m before sunset, Wed, Thu, Fri", channels.get(0).getLabel()); + } + + @Test + public void sceneHourAfterSunsetFridaySaturdaySunday() { + ScheduledEvent scheduledEvent = createScheduledEventWithScene(ScheduledEvents.SCHEDULED_EVENT_TYPE_SUNSET); + scheduledEvent.dayFriday = true; + scheduledEvent.daySaturday = true; + scheduledEvent.daySunday = true; + scheduledEvent.minute = 60; + + List scheduledEvents = new ArrayList<>(List.of(scheduledEvent)); + List channels = builder.withScenes(scenes).withScheduledEvents(scheduledEvents).build(); + + assertEquals(1, channels.size()); + assertEquals("TestScene, 1hr after sunset, Fri, Sat, Sun", channels.get(0).getLabel()); + } + + @Test + public void sceneCollection() { + ScheduledEvent scheduledEvent = createScheduledEventWithSceneCollection( + ScheduledEvents.SCHEDULED_EVENT_TYPE_SUNRISE); + + List scheduledEvents = new ArrayList<>(List.of(scheduledEvent)); + List channels = builder.withSceneCollections(sceneCollections).withScheduledEvents(scheduledEvents) + .build(); + + assertEquals(1, channels.size()); + assertEquals("TestSceneCollection, At sunrise, ", channels.get(0).getLabel()); + } + + @Test + public void suppliedListIsUsed() { + ScheduledEvent scheduledEvent = createScheduledEventWithScene(ScheduledEvents.SCHEDULED_EVENT_TYPE_SUNRISE); + List scheduledEvents = new ArrayList<>(List.of(scheduledEvent)); + List existingChannels = new ArrayList<>(0); + List channels = builder.withScenes(scenes).withScheduledEvents(scheduledEvents) + .withChannels(existingChannels).build(); + + assertEquals(existingChannels, channels); + } + + @Test + public void emptyListWhenNoScheduledEvents() { + List channels = builder.build(); + + assertEquals(0, channels.size()); + } + + @Test + public void emptyListWhenNoScenesOrSceneCollections() { + ScheduledEvent scheduledEvent = createScheduledEventWithScene(ScheduledEvents.SCHEDULED_EVENT_TYPE_SUNRISE); + List scheduledEvents = new ArrayList<>(List.of(scheduledEvent)); + List channels = builder.withScheduledEvents(scheduledEvents).build(); + + assertEquals(0, channels.size()); + } + + @Test + public void emptyListWhenNoSceneForScheduledEvent() { + ScheduledEvent scheduledEvent = createScheduledEventWithSceneCollection( + ScheduledEvents.SCHEDULED_EVENT_TYPE_SUNRISE); + List scheduledEvents = new ArrayList<>(List.of(scheduledEvent)); + List channels = builder.withScenes(scenes).withScheduledEvents(scheduledEvents).build(); + + assertEquals(0, channels.size()); + } + + @Test + public void emptyListWhenNoSceneCollectionForScheduledEvent() { + ScheduledEvent scheduledEvent = createScheduledEventWithScene(ScheduledEvents.SCHEDULED_EVENT_TYPE_SUNRISE); + List scheduledEvents = new ArrayList<>(List.of(scheduledEvent)); + + List channels = builder.withSceneCollections(sceneCollections).withScheduledEvents(scheduledEvents) + .build(); + + assertEquals(0, channels.size()); + } + + @Test + public void groupAndIdAreCorrect() { + ScheduledEvent scheduledEvent = createScheduledEventWithScene(ScheduledEvents.SCHEDULED_EVENT_TYPE_SUNRISE); + scheduledEvent.id = 42; + List scheduledEvents = new ArrayList<>(List.of(scheduledEvent)); + List channels = builder.withScenes(scenes).withScheduledEvents(scheduledEvents).build(); + + assertEquals(1, channels.size()); + assertEquals(CHANNEL_GROUP_UID.getId(), channels.get(0).getUID().getGroupId()); + assertEquals(Integer.toString(scheduledEvent.id), channels.get(0).getUID().getIdWithoutGroup()); + } + + private ScheduledEvent createScheduledEventWithScene(int eventType) { + ScheduledEvent scheduledEvent = new ScheduledEvent(); + scheduledEvent.id = 1; + scheduledEvent.sceneId = scenes.get(0).id; + scheduledEvent.eventType = eventType; + return scheduledEvent; + } + + private ScheduledEvent createScheduledEventWithSceneCollection(int eventType) { + ScheduledEvent scheduledEvent = new ScheduledEvent(); + scheduledEvent.id = 1; + scheduledEvent.sceneCollectionId = sceneCollections.get(0).id; + scheduledEvent.eventType = eventType; + return scheduledEvent; + } +} diff --git a/bundles/org.openhab.binding.hdpowerview/src/test/java/org/openhab/binding/hdpowerview/LocaleProviderForTests.java b/bundles/org.openhab.binding.hdpowerview/src/test/java/org/openhab/binding/hdpowerview/LocaleProviderForTests.java new file mode 100644 index 0000000000000..53747462c29e4 --- /dev/null +++ b/bundles/org.openhab.binding.hdpowerview/src/test/java/org/openhab/binding/hdpowerview/LocaleProviderForTests.java @@ -0,0 +1,30 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.hdpowerview; + +import java.util.Locale; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.core.i18n.LocaleProvider; + +/** + * Locale provider for unit tests. + * + * @author Jacob Laursen - Initial contribution + */ +@NonNullByDefault +public class LocaleProviderForTests implements LocaleProvider { + public Locale getLocale() { + return Locale.ENGLISH; + } +} diff --git a/bundles/org.openhab.binding.hdpowerview/src/test/java/org/openhab/binding/hdpowerview/SceneChannelBuilderTest.java b/bundles/org.openhab.binding.hdpowerview/src/test/java/org/openhab/binding/hdpowerview/SceneChannelBuilderTest.java new file mode 100644 index 0000000000000..94065aa07f043 --- /dev/null +++ b/bundles/org.openhab.binding.hdpowerview/src/test/java/org/openhab/binding/hdpowerview/SceneChannelBuilderTest.java @@ -0,0 +1,105 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.hdpowerview; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.mock; + +import java.util.ArrayList; +import java.util.Base64; +import java.util.List; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.openhab.binding.hdpowerview.internal.HDPowerViewBindingConstants; +import org.openhab.binding.hdpowerview.internal.HDPowerViewTranslationProvider; +import org.openhab.binding.hdpowerview.internal.api.responses.Scenes.Scene; +import org.openhab.binding.hdpowerview.internal.builders.SceneChannelBuilder; +import org.openhab.core.thing.Channel; +import org.openhab.core.thing.ChannelGroupUID; +import org.openhab.core.thing.ThingUID; +import org.osgi.framework.Bundle; + +/** + * Unit tests for {@link SceneChannelBuilder}. + * + * @author Jacob Laursen - Initial contribution + */ +@NonNullByDefault +public class SceneChannelBuilderTest { + + private static final ChannelGroupUID CHANNEL_GROUP_UID = new ChannelGroupUID( + new ThingUID(HDPowerViewBindingConstants.BINDING_ID, SceneChannelBuilderTest.class.getSimpleName()), + HDPowerViewBindingConstants.CHANNELTYPE_SCENE_ACTIVATE); + + private static final HDPowerViewTranslationProvider translationProvider = new HDPowerViewTranslationProvider( + mock(Bundle.class), new TranslationProviderForTests(), new LocaleProviderForTests()); + private SceneChannelBuilder builder = SceneChannelBuilder.create(translationProvider, CHANNEL_GROUP_UID); + + @BeforeEach + private void setUp() { + builder = SceneChannelBuilder.create(translationProvider, CHANNEL_GROUP_UID); + } + + @Test + public void labelIsCorrect() { + List scenes = createScenes(); + List channels = builder.withScenes(scenes).build(); + + assertEquals(1, channels.size()); + assertEquals("TestScene", channels.get(0).getLabel()); + } + + @Test + public void descriptionIsCorrect() { + List scenes = createScenes(); + List channels = builder.withScenes(scenes).build(); + + assertEquals(1, channels.size()); + assertEquals("Activates the scene 'TestScene'", channels.get(0).getDescription()); + } + + @Test + public void groupAndIdAreCorrect() { + List scenes = createScenes(); + List channels = builder.withScenes(scenes).build(); + + assertEquals(1, channels.size()); + assertEquals(CHANNEL_GROUP_UID.getId(), channels.get(0).getUID().getGroupId()); + assertEquals(Integer.toString(scenes.get(0).id), channels.get(0).getUID().getIdWithoutGroup()); + } + + @Test + public void suppliedListIsUsed() { + List scenes = createScenes(); + List existingChannels = new ArrayList<>(0); + List channels = builder.withScenes(scenes).withChannels(existingChannels).build(); + + assertEquals(existingChannels, channels); + } + + @Test + public void emptyListWhenNoScenes() { + List channels = builder.build(); + + assertEquals(0, channels.size()); + } + + private List createScenes() { + Scene scene = new Scene(); + scene.id = 1; + scene.name = Base64.getEncoder().encodeToString(("TestScene").getBytes()); + return new ArrayList<>(List.of(scene)); + } +} diff --git a/bundles/org.openhab.binding.hdpowerview/src/test/java/org/openhab/binding/hdpowerview/SceneGroupChannelBuilderTest.java b/bundles/org.openhab.binding.hdpowerview/src/test/java/org/openhab/binding/hdpowerview/SceneGroupChannelBuilderTest.java new file mode 100644 index 0000000000000..16286650d59fe --- /dev/null +++ b/bundles/org.openhab.binding.hdpowerview/src/test/java/org/openhab/binding/hdpowerview/SceneGroupChannelBuilderTest.java @@ -0,0 +1,105 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.hdpowerview; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.mock; + +import java.util.ArrayList; +import java.util.Base64; +import java.util.List; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.openhab.binding.hdpowerview.internal.HDPowerViewBindingConstants; +import org.openhab.binding.hdpowerview.internal.HDPowerViewTranslationProvider; +import org.openhab.binding.hdpowerview.internal.api.responses.SceneCollections.SceneCollection; +import org.openhab.binding.hdpowerview.internal.builders.SceneGroupChannelBuilder; +import org.openhab.core.thing.Channel; +import org.openhab.core.thing.ChannelGroupUID; +import org.openhab.core.thing.ThingUID; +import org.osgi.framework.Bundle; + +/** + * Unit tests for {@link SceneGroupChannelBuilder}. + * + * @author Jacob Laursen - Initial contribution + */ +@NonNullByDefault +public class SceneGroupChannelBuilderTest { + + private static final ChannelGroupUID CHANNEL_GROUP_UID = new ChannelGroupUID( + new ThingUID(HDPowerViewBindingConstants.BINDING_ID, SceneGroupChannelBuilderTest.class.getSimpleName()), + HDPowerViewBindingConstants.CHANNELTYPE_SCENE_GROUP_ACTIVATE); + + private static final HDPowerViewTranslationProvider translationProvider = new HDPowerViewTranslationProvider( + mock(Bundle.class), new TranslationProviderForTests(), new LocaleProviderForTests()); + private SceneGroupChannelBuilder builder = SceneGroupChannelBuilder.create(translationProvider, CHANNEL_GROUP_UID); + + @BeforeEach + private void setUp() { + builder = SceneGroupChannelBuilder.create(translationProvider, CHANNEL_GROUP_UID); + } + + @Test + public void labelIsCorrect() { + List sceneCollections = createSceneCollections(); + List channels = builder.withSceneCollections(sceneCollections).build(); + + assertEquals(1, channels.size()); + assertEquals("TestSceneCollection", channels.get(0).getLabel()); + } + + @Test + public void descriptionIsCorrect() { + List sceneCollections = createSceneCollections(); + List channels = builder.withSceneCollections(sceneCollections).build(); + + assertEquals(1, channels.size()); + assertEquals("Activates the scene group 'TestSceneCollection'", channels.get(0).getDescription()); + } + + @Test + public void groupAndIdAreCorrect() { + List sceneCollections = createSceneCollections(); + List channels = builder.withSceneCollections(sceneCollections).build(); + + assertEquals(1, channels.size()); + assertEquals(CHANNEL_GROUP_UID.getId(), channels.get(0).getUID().getGroupId()); + assertEquals(Integer.toString(sceneCollections.get(0).id), channels.get(0).getUID().getIdWithoutGroup()); + } + + @Test + public void suppliedListIsUsed() { + List sceneCollections = createSceneCollections(); + List existingChannels = new ArrayList<>(0); + List channels = builder.withSceneCollections(sceneCollections).withChannels(existingChannels).build(); + + assertEquals(existingChannels, channels); + } + + @Test + public void emptyListWhenNoSceneCollections() { + List channels = builder.build(); + + assertEquals(0, channels.size()); + } + + private List createSceneCollections() { + SceneCollection sceneCollection = new SceneCollection(); + sceneCollection.id = 1; + sceneCollection.name = Base64.getEncoder().encodeToString(("TestSceneCollection").getBytes()); + return new ArrayList<>(List.of(sceneCollection)); + } +} diff --git a/bundles/org.openhab.binding.hdpowerview/src/test/java/org/openhab/binding/hdpowerview/TranslationProviderForTests.java b/bundles/org.openhab.binding.hdpowerview/src/test/java/org/openhab/binding/hdpowerview/TranslationProviderForTests.java new file mode 100644 index 0000000000000..282c7875d7493 --- /dev/null +++ b/bundles/org.openhab.binding.hdpowerview/src/test/java/org/openhab/binding/hdpowerview/TranslationProviderForTests.java @@ -0,0 +1,66 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.hdpowerview; + +import static java.util.Map.entry; + +import java.text.MessageFormat; +import java.util.Locale; +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.core.i18n.TranslationProvider; +import org.osgi.framework.Bundle; + +/** + * Translation provider for unit tests. + * + * @author Jacob Laursen - Initial contribution + */ +@NonNullByDefault +public class TranslationProviderForTests implements TranslationProvider { + + private final static Map texts = Map.ofEntries( + entry("dynamic-channel.scene-activate.description", "Activates the scene ''{0}''"), + entry("dynamic-channel.scene-group-activate.description", "Activates the scene group ''{0}''"), + entry("dynamic-channel.automation-enabled.description", "Enables/disables the automation ''{0}''"), + entry("dynamic-channel.automation-enabled.label", "{0}, {1}, {2}"), + entry("dynamic-channel.automation.hour", "{0}hr"), entry("dynamic-channel.automation.minute", "{0}m"), + entry("dynamic-channel.automation.hour-minute", "{0}hr {1}m"), + entry("dynamic-channel.automation.at-sunrise", "At sunrise"), + entry("dynamic-channel.automation.before-sunrise", "{0} before sunrise"), + entry("dynamic-channel.automation.after-sunrise", "{0} after sunrise"), + entry("dynamic-channel.automation.at-sunset", "At sunset"), + entry("dynamic-channel.automation.before-sunset", "{0} before sunset"), + entry("dynamic-channel.automation.after-sunset", "{0} after sunset"), + entry("dynamic-channel.automation.weekdays", "Weekdays"), + entry("dynamic-channel.automation.weekends", "Weekends"), + entry("dynamic-channel.automation.all-days", "All days")); + + public TranslationProviderForTests() { + } + + @Nullable + public String getText(@Nullable Bundle bundle, @Nullable String key, @Nullable String defaultText, + @Nullable Locale locale) { + return ""; + } + + @Nullable + public String getText(@Nullable Bundle bundle, @Nullable String key, @Nullable String defaultText, + @Nullable Locale locale, @Nullable Object @Nullable... arguments) { + String text = texts.get(key); + return MessageFormat.format(text != null ? text : key, arguments); + } +}