2 * Copyright (c) 2010-2023 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
7 * This program and the accompanying materials are made available under the
8 * terms of the Eclipse Public License 2.0 which is available at
9 * http://www.eclipse.org/legal/epl-2.0
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.hdpowerview.internal.builders;
15 import java.time.DayOfWeek;
16 import java.time.LocalTime;
17 import java.time.format.TextStyle;
18 import java.util.EnumSet;
19 import java.util.List;
21 import java.util.StringJoiner;
22 import java.util.stream.Collectors;
24 import org.eclipse.jdt.annotation.NonNullByDefault;
25 import org.eclipse.jdt.annotation.Nullable;
26 import org.openhab.binding.hdpowerview.internal.HDPowerViewBindingConstants;
27 import org.openhab.binding.hdpowerview.internal.HDPowerViewTranslationProvider;
28 import org.openhab.binding.hdpowerview.internal.dto.Scene;
29 import org.openhab.binding.hdpowerview.internal.dto.SceneCollection;
30 import org.openhab.binding.hdpowerview.internal.dto.ScheduledEvent;
31 import org.openhab.core.library.CoreItemFactory;
32 import org.openhab.core.thing.Channel;
33 import org.openhab.core.thing.ChannelGroupUID;
34 import org.openhab.core.thing.ChannelUID;
35 import org.openhab.core.thing.binding.builder.ChannelBuilder;
36 import org.slf4j.Logger;
37 import org.slf4j.LoggerFactory;
40 * The {@link AutomationChannelBuilder} class creates automation channels
41 * from structured scheduled event data.
43 * @author Jacob Laursen - Initial contribution
46 public class AutomationChannelBuilder extends BaseChannelBuilder {
48 private final Logger logger = LoggerFactory.getLogger(AutomationChannelBuilder.class);
51 private Map<Integer, Scene> scenes;
53 private Map<Integer, SceneCollection> sceneCollections;
55 private List<ScheduledEvent> scheduledEvents;
57 private AutomationChannelBuilder(HDPowerViewTranslationProvider translationProvider,
58 ChannelGroupUID channelGroupUid) {
59 super(translationProvider, channelGroupUid, HDPowerViewBindingConstants.CHANNELTYPE_AUTOMATION_ENABLED);
63 * Creates an {@link AutomationChannelBuilder} for the given {@link HDPowerViewTranslationProvider} and
64 * {@link ChannelGroupUID}.
66 * @param translationProvider the {@link HDPowerViewTranslationProvider}
67 * @param channelGroupUid parent {@link ChannelGroupUID} for created channels
68 * @return channel builder
70 public static AutomationChannelBuilder create(HDPowerViewTranslationProvider translationProvider,
71 ChannelGroupUID channelGroupUid) {
72 return new AutomationChannelBuilder(translationProvider, channelGroupUid);
76 * Adds created channels to existing list.
78 * @param channels list that channels will be added to
79 * @return channel builder
81 public AutomationChannelBuilder withChannels(List<Channel> channels) {
82 this.channels = channels;
89 * @param scenes the scenes
90 * @return channel builder
92 public AutomationChannelBuilder withScenes(List<Scene> scenes) {
93 this.scenes = scenes.stream().collect(Collectors.toMap(scene -> scene.id, scene -> scene));
98 * Sets the scene collections.
100 * @param sceneCollections the scene collections
101 * @return channel builder
103 public AutomationChannelBuilder withSceneCollections(List<SceneCollection> sceneCollections) {
104 this.sceneCollections = sceneCollections.stream()
105 .collect(Collectors.toMap(sceneCollection -> sceneCollection.id, sceneCollection -> sceneCollection));
110 * Sets the scheduled events.
112 * @param scheduledEvents the sceduled events
113 * @return channel builder
115 public AutomationChannelBuilder withScheduledEvents(List<ScheduledEvent> scheduledEvents) {
116 this.scheduledEvents = scheduledEvents;
121 * Builds and returns the channels.
123 * @return the {@link Channel} list
125 public List<Channel> build() {
126 List<ScheduledEvent> scheduledEvents = this.scheduledEvents;
127 if (scheduledEvents == null || (scenes == null && sceneCollections == null)) {
128 return getChannelList(0);
130 List<Channel> channels = getChannelList(scheduledEvents.size());
131 scheduledEvents.stream().forEach(scheduledEvent -> {
132 Channel channel = createChannel(scheduledEvent);
133 if (channel != null) {
134 channels.add(channel);
141 private @Nullable Channel createChannel(ScheduledEvent scheduledEvent) {
142 String referencedName = getReferencedSceneOrSceneCollectionName(scheduledEvent);
143 if (referencedName == null) {
146 ChannelUID channelUid = new ChannelUID(channelGroupUid, Integer.toString(scheduledEvent.id));
147 String label = getScheduledEventName(referencedName, scheduledEvent);
148 String description = translationProvider.getText("dynamic-channel.automation-enabled.description",
150 Channel channel = ChannelBuilder.create(channelUid, CoreItemFactory.SWITCH).withType(channelTypeUid)
151 .withLabel(label).withDescription(description).build();
156 private @Nullable String getReferencedSceneOrSceneCollectionName(ScheduledEvent scheduledEvent) {
157 if (scheduledEvent.sceneId > 0) {
158 Map<Integer, Scene> scenes = this.scenes;
159 if (scenes == null) {
160 logger.warn("Scheduled event '{}' references scene '{}', but no scenes are loaded", scheduledEvent.id,
161 scheduledEvent.sceneId);
164 Scene scene = scenes.get(scheduledEvent.sceneId);
166 return scene.getName();
168 logger.warn("Scene '{}' was not found for scheduled event '{}'", scheduledEvent.sceneId, scheduledEvent.id);
170 } else if (scheduledEvent.sceneCollectionId > 0) {
171 Map<Integer, SceneCollection> sceneCollections = this.sceneCollections;
172 if (sceneCollections == null) {
174 "Scheduled event '{}' references scene collection '{}', but no scene collections are loaded",
175 scheduledEvent.id, scheduledEvent.sceneCollectionId);
178 SceneCollection sceneCollection = sceneCollections.get(scheduledEvent.sceneCollectionId);
179 if (sceneCollection != null) {
180 return sceneCollection.getName();
182 logger.warn("Scene collection '{}' was not found for scheduled event '{}'",
183 scheduledEvent.sceneCollectionId, scheduledEvent.id);
186 logger.warn("Scheduled event '{}'' not related to any scene or scene collection", scheduledEvent.id);
191 private String getScheduledEventName(String sceneName, ScheduledEvent scheduledEvent) {
192 String timeString, daysString;
194 switch (scheduledEvent.eventType) {
195 case ScheduledEvent.SCHEDULED_EVENT_TYPE_TIME:
196 timeString = LocalTime.of(scheduledEvent.hour, scheduledEvent.minute).toString();
198 case ScheduledEvent.SCHEDULED_EVENT_TYPE_SUNRISE:
199 if (scheduledEvent.minute == 0) {
200 timeString = translationProvider.getText("dynamic-channel.automation.at-sunrise");
201 } else if (scheduledEvent.minute < 0) {
202 timeString = translationProvider.getText("dynamic-channel.automation.before-sunrise",
203 getFormattedTimeOffset(-scheduledEvent.minute));
205 timeString = translationProvider.getText("dynamic-channel.automation.after-sunrise",
206 getFormattedTimeOffset(scheduledEvent.minute));
209 case ScheduledEvent.SCHEDULED_EVENT_TYPE_SUNSET:
210 if (scheduledEvent.minute == 0) {
211 timeString = translationProvider.getText("dynamic-channel.automation.at-sunset");
212 } else if (scheduledEvent.minute < 0) {
213 timeString = translationProvider.getText("dynamic-channel.automation.before-sunset",
214 getFormattedTimeOffset(-scheduledEvent.minute));
216 timeString = translationProvider.getText("dynamic-channel.automation.after-sunset",
217 getFormattedTimeOffset(scheduledEvent.minute));
224 EnumSet<DayOfWeek> days = scheduledEvent.getDays();
225 if (EnumSet.allOf(DayOfWeek.class).equals(days)) {
226 daysString = translationProvider.getText("dynamic-channel.automation.all-days");
227 } else if (ScheduledEvent.WEEKDAYS.equals(days)) {
228 daysString = translationProvider.getText("dynamic-channel.automation.weekdays");
229 } else if (ScheduledEvent.WEEKENDS.equals(days)) {
230 daysString = translationProvider.getText("dynamic-channel.automation.weekends");
232 StringJoiner joiner = new StringJoiner(", ");
233 days.forEach(day -> joiner.add(day.getDisplayName(TextStyle.SHORT, translationProvider.getLocale())));
234 daysString = joiner.toString();
237 return translationProvider.getText("dynamic-channel.automation-enabled.label", sceneName, timeString,
241 private String getFormattedTimeOffset(int minutes) {
243 int remainder = minutes % 60;
244 if (remainder == 0) {
245 return translationProvider.getText("dynamic-channel.automation.hour", minutes / 60);
247 return translationProvider.getText("dynamic-channel.automation.hour-minute", minutes / 60, remainder);
249 return translationProvider.getText("dynamic-channel.automation.minute", minutes);