2 * Copyright (c) 2010-2024 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.transform.basicprofiles.internal.factory;
15 import static org.openhab.transform.basicprofiles.internal.BasicProfilesConstants.SCOPE;
17 import java.util.Collection;
18 import java.util.Locale;
21 import java.util.concurrent.ConcurrentHashMap;
23 import org.eclipse.jdt.annotation.NonNullByDefault;
24 import org.eclipse.jdt.annotation.Nullable;
25 import org.openhab.core.i18n.LocalizedKey;
26 import org.openhab.core.i18n.TimeZoneProvider;
27 import org.openhab.core.items.ItemRegistry;
28 import org.openhab.core.library.CoreItemFactory;
29 import org.openhab.core.thing.Channel;
30 import org.openhab.core.thing.DefaultSystemChannelTypeProvider;
31 import org.openhab.core.thing.profiles.Profile;
32 import org.openhab.core.thing.profiles.ProfileAdvisor;
33 import org.openhab.core.thing.profiles.ProfileCallback;
34 import org.openhab.core.thing.profiles.ProfileContext;
35 import org.openhab.core.thing.profiles.ProfileFactory;
36 import org.openhab.core.thing.profiles.ProfileType;
37 import org.openhab.core.thing.profiles.ProfileTypeBuilder;
38 import org.openhab.core.thing.profiles.ProfileTypeProvider;
39 import org.openhab.core.thing.profiles.ProfileTypeUID;
40 import org.openhab.core.thing.profiles.i18n.ProfileTypeI18nLocalizationService;
41 import org.openhab.core.thing.type.ChannelType;
42 import org.openhab.core.util.BundleResolver;
43 import org.openhab.transform.basicprofiles.internal.profiles.DebounceCountingStateProfile;
44 import org.openhab.transform.basicprofiles.internal.profiles.DebounceTimeStateProfile;
45 import org.openhab.transform.basicprofiles.internal.profiles.GenericCommandTriggerProfile;
46 import org.openhab.transform.basicprofiles.internal.profiles.GenericToggleSwitchTriggerProfile;
47 import org.openhab.transform.basicprofiles.internal.profiles.InvertStateProfile;
48 import org.openhab.transform.basicprofiles.internal.profiles.RoundStateProfile;
49 import org.openhab.transform.basicprofiles.internal.profiles.StateFilterProfile;
50 import org.openhab.transform.basicprofiles.internal.profiles.ThresholdStateProfile;
51 import org.openhab.transform.basicprofiles.internal.profiles.TimeRangeCommandProfile;
52 import org.osgi.framework.Bundle;
53 import org.osgi.service.component.annotations.Activate;
54 import org.osgi.service.component.annotations.Component;
55 import org.osgi.service.component.annotations.Reference;
58 * The {@link BasicProfilesFactory} is responsible for creating profiles.
60 * @author Christoph Weitkamp - Initial contribution
62 @Component(service = { ProfileFactory.class, ProfileTypeProvider.class })
64 public class BasicProfilesFactory implements ProfileFactory, ProfileTypeProvider, ProfileAdvisor {
66 public static final ProfileTypeUID GENERIC_COMMAND_UID = new ProfileTypeUID(SCOPE, "generic-command");
67 public static final ProfileTypeUID GENERIC_TOGGLE_SWITCH_UID = new ProfileTypeUID(SCOPE, "toggle-switch");
68 public static final ProfileTypeUID DEBOUNCE_COUNTING_UID = new ProfileTypeUID(SCOPE, "debounce-counting");
69 public static final ProfileTypeUID DEBOUNCE_TIME_UID = new ProfileTypeUID(SCOPE, "debounce-time");
70 public static final ProfileTypeUID INVERT_UID = new ProfileTypeUID(SCOPE, "invert");
71 public static final ProfileTypeUID ROUND_UID = new ProfileTypeUID(SCOPE, "round");
72 public static final ProfileTypeUID THRESHOLD_UID = new ProfileTypeUID(SCOPE, "threshold");
73 public static final ProfileTypeUID TIME_RANGE_COMMAND_UID = new ProfileTypeUID(SCOPE, "time-range-command");
74 public static final ProfileTypeUID STATE_FILTER_UID = new ProfileTypeUID(SCOPE, "state-filter");
76 private static final ProfileType PROFILE_TYPE_GENERIC_COMMAND = ProfileTypeBuilder
77 .newTrigger(GENERIC_COMMAND_UID, "Generic Command") //
78 .withSupportedItemTypes(CoreItemFactory.DIMMER, CoreItemFactory.NUMBER, CoreItemFactory.PLAYER,
79 CoreItemFactory.ROLLERSHUTTER, CoreItemFactory.SWITCH) //
81 private static final ProfileType PROFILE_TYPE_GENERIC_TOGGLE_SWITCH = ProfileTypeBuilder
82 .newTrigger(GENERIC_TOGGLE_SWITCH_UID, "Generic Toggle Switch") //
83 .withSupportedItemTypes(CoreItemFactory.COLOR, CoreItemFactory.DIMMER, CoreItemFactory.SWITCH) //
85 private static final ProfileType PROFILE_TYPE_DEBOUNCE_COUNTING = ProfileTypeBuilder
86 .newState(DEBOUNCE_COUNTING_UID, "Debounce (Counting)").build();
87 private static final ProfileType PROFILE_TYPE_DEBOUNCE_TIME = ProfileTypeBuilder
88 .newState(DEBOUNCE_TIME_UID, "Debounce (Time)").build();
89 private static final ProfileType PROFILE_TYPE_INVERT = ProfileTypeBuilder.newState(INVERT_UID, "Invert / Negate")
90 .withSupportedItemTypes(CoreItemFactory.CONTACT, CoreItemFactory.DIMMER, CoreItemFactory.NUMBER,
91 CoreItemFactory.PLAYER, CoreItemFactory.ROLLERSHUTTER, CoreItemFactory.SWITCH) //
92 .withSupportedItemTypesOfChannel(CoreItemFactory.CONTACT, CoreItemFactory.DIMMER, CoreItemFactory.NUMBER,
93 CoreItemFactory.PLAYER, CoreItemFactory.ROLLERSHUTTER, CoreItemFactory.SWITCH) //
95 private static final ProfileType PROFILE_TYPE_ROUND = ProfileTypeBuilder.newState(ROUND_UID, "Round")
96 .withSupportedItemTypes(CoreItemFactory.NUMBER) //
97 .withSupportedItemTypesOfChannel(CoreItemFactory.NUMBER) //
99 private static final ProfileType PROFILE_TYPE_THRESHOLD = ProfileTypeBuilder.newState(THRESHOLD_UID, "Threshold") //
100 .withSupportedItemTypesOfChannel(CoreItemFactory.DIMMER, CoreItemFactory.NUMBER) //
101 .withSupportedItemTypes(CoreItemFactory.SWITCH) //
103 private static final ProfileType PROFILE_TYPE_TIME_RANGE_COMMAND = ProfileTypeBuilder
104 .newState(TIME_RANGE_COMMAND_UID, "Time Range Command") //
105 .withSupportedItemTypes(CoreItemFactory.SWITCH) //
106 .withSupportedChannelTypeUIDs(DefaultSystemChannelTypeProvider.SYSTEM_CHANNEL_TYPE_UID_MOTION) //
108 private static final ProfileType PROFILE_STATE_FILTER = ProfileTypeBuilder
109 .newState(STATE_FILTER_UID, "Filter handler state updates based on any item state").build();
111 private static final Set<ProfileTypeUID> SUPPORTED_PROFILE_TYPE_UIDS = Set.of(GENERIC_COMMAND_UID,
112 GENERIC_TOGGLE_SWITCH_UID, DEBOUNCE_COUNTING_UID, DEBOUNCE_TIME_UID, INVERT_UID, ROUND_UID, THRESHOLD_UID,
113 TIME_RANGE_COMMAND_UID, STATE_FILTER_UID);
114 private static final Set<ProfileType> SUPPORTED_PROFILE_TYPES = Set.of(PROFILE_TYPE_GENERIC_COMMAND,
115 PROFILE_TYPE_GENERIC_TOGGLE_SWITCH, PROFILE_TYPE_DEBOUNCE_COUNTING, PROFILE_TYPE_DEBOUNCE_TIME,
116 PROFILE_TYPE_INVERT, PROFILE_TYPE_ROUND, PROFILE_TYPE_THRESHOLD, PROFILE_TYPE_TIME_RANGE_COMMAND,
117 PROFILE_STATE_FILTER);
119 private final Map<LocalizedKey, ProfileType> localizedProfileTypeCache = new ConcurrentHashMap<>();
121 private final ProfileTypeI18nLocalizationService profileTypeI18nLocalizationService;
122 private final Bundle bundle;
123 private final ItemRegistry itemRegistry;
124 private final TimeZoneProvider timeZoneProvider;
127 public BasicProfilesFactory(final @Reference ProfileTypeI18nLocalizationService profileTypeI18nLocalizationService,
128 final @Reference BundleResolver bundleResolver, @Reference ItemRegistry itemRegistry,
129 @Reference TimeZoneProvider timeZoneProvider) {
130 this.profileTypeI18nLocalizationService = profileTypeI18nLocalizationService;
131 this.bundle = bundleResolver.resolveBundle(BasicProfilesFactory.class);
132 this.itemRegistry = itemRegistry;
133 this.timeZoneProvider = timeZoneProvider;
137 public @Nullable Profile createProfile(ProfileTypeUID profileTypeUID, ProfileCallback callback,
138 ProfileContext context) {
139 if (GENERIC_COMMAND_UID.equals(profileTypeUID)) {
140 return new GenericCommandTriggerProfile(callback, context);
141 } else if (GENERIC_TOGGLE_SWITCH_UID.equals(profileTypeUID)) {
142 return new GenericToggleSwitchTriggerProfile(callback, context);
143 } else if (DEBOUNCE_COUNTING_UID.equals(profileTypeUID)) {
144 return new DebounceCountingStateProfile(callback, context);
145 } else if (DEBOUNCE_TIME_UID.equals(profileTypeUID)) {
146 return new DebounceTimeStateProfile(callback, context);
147 } else if (INVERT_UID.equals(profileTypeUID)) {
148 return new InvertStateProfile(callback);
149 } else if (ROUND_UID.equals(profileTypeUID)) {
150 return new RoundStateProfile(callback, context);
151 } else if (THRESHOLD_UID.equals(profileTypeUID)) {
152 return new ThresholdStateProfile(callback, context);
153 } else if (TIME_RANGE_COMMAND_UID.equals(profileTypeUID)) {
154 return new TimeRangeCommandProfile(callback, context, timeZoneProvider);
155 } else if (STATE_FILTER_UID.equals(profileTypeUID)) {
156 return new StateFilterProfile(callback, context, itemRegistry);
162 public Collection<ProfileType> getProfileTypes(@Nullable Locale locale) {
163 return SUPPORTED_PROFILE_TYPES.stream().map(p -> createLocalizedProfileType(p, locale)).toList();
167 public Collection<ProfileTypeUID> getSupportedProfileTypeUIDs() {
168 return SUPPORTED_PROFILE_TYPE_UIDS;
172 public @Nullable ProfileTypeUID getSuggestedProfileTypeUID(ChannelType channelType, @Nullable String itemType) {
177 public @Nullable ProfileTypeUID getSuggestedProfileTypeUID(Channel channel, @Nullable String itemType) {
181 private ProfileType createLocalizedProfileType(ProfileType profileType, @Nullable Locale locale) {
182 final LocalizedKey localizedKey = new LocalizedKey(profileType.getUID(),
183 locale != null ? locale.toLanguageTag() : null);
185 final ProfileType cachedlocalizedProfileType = localizedProfileTypeCache.get(localizedKey);
186 if (cachedlocalizedProfileType != null) {
187 return cachedlocalizedProfileType;
190 final ProfileType localizedProfileType = profileTypeI18nLocalizationService.createLocalizedProfileType(bundle,
191 profileType, locale);
192 if (localizedProfileType != null) {
193 localizedProfileTypeCache.put(localizedKey, localizedProfileType);
194 return localizedProfileType;