]> git.basschouten.com Git - openhab-addons.git/commitdiff
[hdpowerview] Refactor dynamic channels (#11853)
authorJacob Laursen <jacob-github@vindvejr.dk>
Mon, 17 Jan 2022 12:41:33 +0000 (13:41 +0100)
committerGitHub <noreply@github.com>
Mon, 17 Jan 2022 12:41:33 +0000 (13:41 +0100)
* 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 <jacob-github@vindvejr.dk>
13 files changed:
bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/builders/AutomationChannelBuilder.java [new file with mode: 0644]
bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/builders/BaseChannelBuilder.java [new file with mode: 0644]
bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/builders/SceneChannelBuilder.java [new file with mode: 0644]
bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/builders/SceneGroupChannelBuilder.java [new file with mode: 0644]
bundles/org.openhab.binding.hdpowerview/src/main/java/org/openhab/binding/hdpowerview/internal/handler/HDPowerViewHubHandler.java
bundles/org.openhab.binding.hdpowerview/src/main/resources/OH-INF/i18n/hdpowerview.properties
bundles/org.openhab.binding.hdpowerview/src/main/resources/OH-INF/i18n/hdpowerview_da.properties
bundles/org.openhab.binding.hdpowerview/src/main/resources/OH-INF/thing/thing-types.xml
bundles/org.openhab.binding.hdpowerview/src/test/java/org/openhab/binding/hdpowerview/AutomationChannelBuilderTest.java [new file with mode: 0644]
bundles/org.openhab.binding.hdpowerview/src/test/java/org/openhab/binding/hdpowerview/LocaleProviderForTests.java [new file with mode: 0644]
bundles/org.openhab.binding.hdpowerview/src/test/java/org/openhab/binding/hdpowerview/SceneChannelBuilderTest.java [new file with mode: 0644]
bundles/org.openhab.binding.hdpowerview/src/test/java/org/openhab/binding/hdpowerview/SceneGroupChannelBuilderTest.java [new file with mode: 0644]
bundles/org.openhab.binding.hdpowerview/src/test/java/org/openhab/binding/hdpowerview/TranslationProviderForTests.java [new file with mode: 0644]

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 (file)
index 0000000..10782b2
--- /dev/null
@@ -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<Integer, Scene> scenes;
+    @Nullable
+    private Map<Integer, SceneCollection> sceneCollections;
+    @Nullable
+    private List<ScheduledEvent> 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<Channel> channels) {
+        this.channels = channels;
+        return this;
+    }
+
+    /**
+     * Sets the scenes.
+     * 
+     * @param scenes the scenes
+     * @return channel builder
+     */
+    public AutomationChannelBuilder withScenes(List<Scene> 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<SceneCollection> 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<ScheduledEvent> scheduledEvents) {
+        this.scheduledEvents = scheduledEvents;
+        return this;
+    }
+
+    /**
+     * Builds and returns the channels.
+     *
+     * @return the {@link Channel} list
+     */
+    public List<Channel> build() {
+        List<ScheduledEvent> scheduledEvents = this.scheduledEvents;
+        if (scheduledEvents == null || (scenes == null && sceneCollections == null)) {
+            return getChannelList(0);
+        }
+        List<Channel> 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<Integer, Scene> 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<Integer, SceneCollection> 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<DayOfWeek> 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 (file)
index 0000000..6ac875c
--- /dev/null
@@ -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<Channel> 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<Channel> getChannelList(int initialCapacity) {
+        List<Channel> 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 (file)
index 0000000..ad11985
--- /dev/null
@@ -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<Scene> 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<Channel> channels) {
+        this.channels = channels;
+        return this;
+    }
+
+    /**
+     * Sets the scenes.
+     * 
+     * @param scenes the scenes
+     * @return channel builder
+     */
+    public SceneChannelBuilder withScenes(List<Scene> scenes) {
+        this.scenes = scenes;
+        return this;
+    }
+
+    /**
+     * Builds and returns the channels.
+     *
+     * @return the {@link Channel} list
+     */
+    public List<Channel> build() {
+        List<Scene> scenes = this.scenes;
+        if (scenes == null) {
+            return getChannelList(0);
+        }
+        List<Channel> 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 (file)
index 0000000..2b70b21
--- /dev/null
@@ -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<SceneCollection> 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<Channel> channels) {
+        this.channels = channels;
+        return this;
+    }
+
+    /**
+     * Sets the scene collections.
+     * 
+     * @param sceneCollections the scene collections
+     * @return channel builder
+     */
+    public SceneGroupChannelBuilder withSceneCollections(List<SceneCollection> sceneCollections) {
+        this.sceneCollections = sceneCollections;
+        return this;
+    }
+
+    /**
+     * Builds and returns the channels.
+     *
+     * @return the {@link Channel} list
+     */
+    public List<Channel> build() {
+        List<SceneCollection> sceneCollections = this.sceneCollections;
+        if (sceneCollections == null) {
+            return getChannelList(0);
+        }
+        List<Channel> 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();
+    }
+}
index 8b3d2c60e844f1c3341cb157d914a5796e9ebe0f..b1f4de389ade6069fbc87f3f13803b21b685da83 100644 (file)
  */
 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;
 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 @@ public class HDPowerViewHubHandler extends BaseBridgeHandler {
             pollShades();
 
             List<Scene> scenes = updateSceneChannels();
-            List<SceneCollection> sceneCollections = updateSceneCollectionChannels();
-            List<ScheduledEvent> scheduledEvents = updateScheduledEventChannels(scenes, sceneCollections);
+            List<SceneCollection> sceneCollections = updateSceneGroupChannels();
+            List<ScheduledEvent> 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 @@ public class HDPowerViewHubHandler extends BaseBridgeHandler {
 
         List<Channel> 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 @@ public class HDPowerViewHubHandler extends BaseBridgeHandler {
         return sceneCollectionData;
     }
 
-    private List<SceneCollection> updateSceneCollectionChannels()
+    private List<SceneCollection> updateSceneGroupChannels()
             throws HubInvalidResponseException, HubProcessingException, HubMaintenanceException {
         List<SceneCollection> 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<SceneCollection>(sceneCollections);
 
         List<Channel> 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<ScheduledEvent> fetchScheduledEvents()
@@ -513,140 +497,33 @@ public class HDPowerViewHubHandler extends BaseBridgeHandler {
         return scheduledEventData;
     }
 
-    private List<ScheduledEvent> updateScheduledEventChannels(List<Scene> scenes,
-            List<SceneCollection> sceneCollections)
+    private List<ScheduledEvent> updateAutomationChannels(List<Scene> scenes, List<SceneCollection> sceneCollections)
             throws HubInvalidResponseException, HubProcessingException, HubMaintenanceException {
         List<ScheduledEvent> 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<ScheduledEvent>(scheduledEvents);
 
         List<Channel> 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<Scene> scenes,
-            List<SceneCollection> 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<Scene> scenes,
-            List<SceneCollection> 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<DayOfWeek> 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<ScheduledEvent> scheduledEvents) {
+    private void updateAutomationStates(List<ScheduledEvent> scheduledEvents) {
         ChannelGroupUID channelGroupUid = new ChannelGroupUID(thing.getUID(),
                 HDPowerViewBindingConstants.CHANNEL_GROUP_AUTOMATIONS);
         for (ScheduledEvent scheduledEvent : scheduledEvents) {
index 4a6ec1312e3d61249c75b9c8dbd88ee41263512c..b9b8efc4c9cbc86657314b873ae3ce8eb12f0614 100644 (file)
@@ -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
index 6b4483f261e17146687e88fa96ad35d47e1a7ebc..7f3a7fdf0c1f0d97b87d49ea341c348e51b52b7e 100644 (file)
@@ -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
index 80b128b60278bcfd7542437667ec692957e68da4..595392183ac75f4b9acee6a9c194a802ddf4e750 100644 (file)
@@ -18,6 +18,7 @@
                        <property name="vendor">Hunter Douglas (Luxaflex)</property>
                        <property name="modelId">PowerView Hub</property>
                </properties>
+
                <representation-property>host</representation-property>
 
                <config-description>
                <properties>
                        <property name="vendor">Hunter Douglas (Luxaflex)</property>
                        <property name="modelId">PowerView Motorized Shade</property>
-                       <property name="type"></property>
-                       <property name="capabilities"></property>
-                       <property name="secondaryRailDetected"></property>
-                       <property name="tiltAnywhereDetected"></property>
                </properties>
                <representation-property>id</representation-property>
 
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 (file)
index 0000000..e03b0dd
--- /dev/null
@@ -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<Scene> scenes = new ArrayList<>();
+    private List<SceneCollection> 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<ScheduledEvent> scheduledEvents = new ArrayList<>(List.of(scheduledEvent));
+        List<Channel> 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<ScheduledEvent> scheduledEvents = new ArrayList<>(List.of(scheduledEvent));
+        List<Channel> 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<ScheduledEvent> scheduledEvents = new ArrayList<>(List.of(scheduledEvent));
+        List<Channel> 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<ScheduledEvent> scheduledEvents = new ArrayList<>(List.of(scheduledEvent));
+        List<Channel> 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<ScheduledEvent> scheduledEvents = new ArrayList<>(List.of(scheduledEvent));
+        List<Channel> 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<ScheduledEvent> scheduledEvents = new ArrayList<>(List.of(scheduledEvent));
+        List<Channel> 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<ScheduledEvent> scheduledEvents = new ArrayList<>(List.of(scheduledEvent));
+        List<Channel> 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<ScheduledEvent> scheduledEvents = new ArrayList<>(List.of(scheduledEvent));
+        List<Channel> 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<ScheduledEvent> scheduledEvents = new ArrayList<>(List.of(scheduledEvent));
+        List<Channel> existingChannels = new ArrayList<>(0);
+        List<Channel> channels = builder.withScenes(scenes).withScheduledEvents(scheduledEvents)
+                .withChannels(existingChannels).build();
+
+        assertEquals(existingChannels, channels);
+    }
+
+    @Test
+    public void emptyListWhenNoScheduledEvents() {
+        List<Channel> channels = builder.build();
+
+        assertEquals(0, channels.size());
+    }
+
+    @Test
+    public void emptyListWhenNoScenesOrSceneCollections() {
+        ScheduledEvent scheduledEvent = createScheduledEventWithScene(ScheduledEvents.SCHEDULED_EVENT_TYPE_SUNRISE);
+        List<ScheduledEvent> scheduledEvents = new ArrayList<>(List.of(scheduledEvent));
+        List<Channel> channels = builder.withScheduledEvents(scheduledEvents).build();
+
+        assertEquals(0, channels.size());
+    }
+
+    @Test
+    public void emptyListWhenNoSceneForScheduledEvent() {
+        ScheduledEvent scheduledEvent = createScheduledEventWithSceneCollection(
+                ScheduledEvents.SCHEDULED_EVENT_TYPE_SUNRISE);
+        List<ScheduledEvent> scheduledEvents = new ArrayList<>(List.of(scheduledEvent));
+        List<Channel> channels = builder.withScenes(scenes).withScheduledEvents(scheduledEvents).build();
+
+        assertEquals(0, channels.size());
+    }
+
+    @Test
+    public void emptyListWhenNoSceneCollectionForScheduledEvent() {
+        ScheduledEvent scheduledEvent = createScheduledEventWithScene(ScheduledEvents.SCHEDULED_EVENT_TYPE_SUNRISE);
+        List<ScheduledEvent> scheduledEvents = new ArrayList<>(List.of(scheduledEvent));
+
+        List<Channel> 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<ScheduledEvent> scheduledEvents = new ArrayList<>(List.of(scheduledEvent));
+        List<Channel> 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 (file)
index 0000000..5374746
--- /dev/null
@@ -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 (file)
index 0000000..94065aa
--- /dev/null
@@ -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<Scene> scenes = createScenes();
+        List<Channel> channels = builder.withScenes(scenes).build();
+
+        assertEquals(1, channels.size());
+        assertEquals("TestScene", channels.get(0).getLabel());
+    }
+
+    @Test
+    public void descriptionIsCorrect() {
+        List<Scene> scenes = createScenes();
+        List<Channel> channels = builder.withScenes(scenes).build();
+
+        assertEquals(1, channels.size());
+        assertEquals("Activates the scene 'TestScene'", channels.get(0).getDescription());
+    }
+
+    @Test
+    public void groupAndIdAreCorrect() {
+        List<Scene> scenes = createScenes();
+        List<Channel> 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<Scene> scenes = createScenes();
+        List<Channel> existingChannels = new ArrayList<>(0);
+        List<Channel> channels = builder.withScenes(scenes).withChannels(existingChannels).build();
+
+        assertEquals(existingChannels, channels);
+    }
+
+    @Test
+    public void emptyListWhenNoScenes() {
+        List<Channel> channels = builder.build();
+
+        assertEquals(0, channels.size());
+    }
+
+    private List<Scene> 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 (file)
index 0000000..1628665
--- /dev/null
@@ -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<SceneCollection> sceneCollections = createSceneCollections();
+        List<Channel> channels = builder.withSceneCollections(sceneCollections).build();
+
+        assertEquals(1, channels.size());
+        assertEquals("TestSceneCollection", channels.get(0).getLabel());
+    }
+
+    @Test
+    public void descriptionIsCorrect() {
+        List<SceneCollection> sceneCollections = createSceneCollections();
+        List<Channel> channels = builder.withSceneCollections(sceneCollections).build();
+
+        assertEquals(1, channels.size());
+        assertEquals("Activates the scene group 'TestSceneCollection'", channels.get(0).getDescription());
+    }
+
+    @Test
+    public void groupAndIdAreCorrect() {
+        List<SceneCollection> sceneCollections = createSceneCollections();
+        List<Channel> 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<SceneCollection> sceneCollections = createSceneCollections();
+        List<Channel> existingChannels = new ArrayList<>(0);
+        List<Channel> channels = builder.withSceneCollections(sceneCollections).withChannels(existingChannels).build();
+
+        assertEquals(existingChannels, channels);
+    }
+
+    @Test
+    public void emptyListWhenNoSceneCollections() {
+        List<Channel> channels = builder.build();
+
+        assertEquals(0, channels.size());
+    }
+
+    private List<SceneCollection> 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 (file)
index 0000000..282c787
--- /dev/null
@@ -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<String, String> 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);
+    }
+}