mirror of
https://github.com/danieldemus/openhab-core.git
synced 2025-01-10 13:21:53 +01:00
Set a default presentation pattern for String/Number/Datetime items (#4175)
* Set a default presentation pattern for String/Number/Datetime items A default state pattern was previously provided by ChannelStateDescriptionProvider only for String and Number items linked to a channel. It is now the class DefaultStateDescriptionFragmentProvider which is responsible for providing the default state pattern for items, whether the item is linked to a channel or not. This new class is the lowest ranked StateDescriptionFragmentProvider so that all other providers have priority in setting the state pattern. Default pattern for string item: %s Default pattern for datetime item or group with datetime state: %1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS Default pattern for number item or group with number state: %.0f Default pattern for number+dimension item or group with number+dimension state: %.0f %unit% Closes #4071 Closes #3835 Signed-off-by: Laurent Garnier <lg.hc@free.fr>
This commit is contained in:
parent
1b503afdbb
commit
1fb949ba8f
@ -21,7 +21,6 @@ import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.items.Item;
|
||||
import org.openhab.core.library.CoreItemFactory;
|
||||
import org.openhab.core.thing.Channel;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.ThingRegistry;
|
||||
@ -100,22 +99,6 @@ public class ChannelStateDescriptionProvider implements StateDescriptionFragment
|
||||
ChannelType channelType = thingTypeRegistry.getChannelType(channel, locale);
|
||||
if (channelType != null) {
|
||||
stateDescription = channelType.getState();
|
||||
String itemType = channelType.getItemType();
|
||||
if (itemType != null && (stateDescription == null || stateDescription.getPattern() == null)) {
|
||||
String pattern = null;
|
||||
if (CoreItemFactory.STRING.equalsIgnoreCase(itemType)) {
|
||||
pattern = "%s";
|
||||
} else if (itemType.startsWith(CoreItemFactory.NUMBER)) {
|
||||
pattern = "%.0f";
|
||||
}
|
||||
if (pattern != null) {
|
||||
logger.trace("Provide a default pattern {} for item {}", pattern, itemName);
|
||||
StateDescriptionFragmentBuilder builder = (stateDescription == null)
|
||||
? StateDescriptionFragmentBuilder.create()
|
||||
: StateDescriptionFragmentBuilder.create(stateDescription);
|
||||
stateDescription = builder.withPattern(pattern).build().toStateDescription();
|
||||
}
|
||||
}
|
||||
}
|
||||
StateDescription dynamicStateDescription = getDynamicStateDescription(channel, stateDescription,
|
||||
locale);
|
||||
|
@ -0,0 +1,114 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2024 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.core.internal.items;
|
||||
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.items.GroupItem;
|
||||
import org.openhab.core.items.Item;
|
||||
import org.openhab.core.library.CoreItemFactory;
|
||||
import org.openhab.core.types.StateDescriptionFragment;
|
||||
import org.openhab.core.types.StateDescriptionFragmentBuilder;
|
||||
import org.openhab.core.types.StateDescriptionFragmentProvider;
|
||||
import org.osgi.framework.Constants;
|
||||
import org.osgi.service.component.annotations.Activate;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.osgi.service.component.annotations.Deactivate;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* A {@link StateDescriptionFragment} provider providing a default state pattern for items of type String,
|
||||
* DateTime and Number (with or without dimension).
|
||||
*
|
||||
* @author Laurent Garnier - initial contribution
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@Component(service = { StateDescriptionFragmentProvider.class,
|
||||
DefaultStateDescriptionFragmentProvider.class }, immediate = true, property = { "service.ranking:Integer=-2" })
|
||||
public class DefaultStateDescriptionFragmentProvider implements StateDescriptionFragmentProvider {
|
||||
|
||||
private static final StateDescriptionFragment DEFAULT_STRING = StateDescriptionFragmentBuilder.create()
|
||||
.withPattern("%s").build();
|
||||
private static final StateDescriptionFragment DEFAULT_DATETIME = StateDescriptionFragmentBuilder.create()
|
||||
.withPattern("%1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS").build();
|
||||
private static final StateDescriptionFragment DEFAULT_NUMBER = StateDescriptionFragmentBuilder.create()
|
||||
.withPattern("%.0f").build();
|
||||
private static final StateDescriptionFragment DEFAULT_NUMBER_WITH_DIMENSION = StateDescriptionFragmentBuilder
|
||||
.create().withPattern("%.0f %unit%").build();
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(DefaultStateDescriptionFragmentProvider.class);
|
||||
|
||||
private final Map<String, StateDescriptionFragment> stateDescriptionFragments = new ConcurrentHashMap<>();
|
||||
|
||||
private Integer rank = -2; // takes less precedence than all other providers
|
||||
|
||||
@Activate
|
||||
public DefaultStateDescriptionFragmentProvider(Map<String, Object> properties) {
|
||||
Object serviceRanking = properties.get(Constants.SERVICE_RANKING);
|
||||
if (serviceRanking instanceof Integer rankValue) {
|
||||
rank = rankValue;
|
||||
}
|
||||
}
|
||||
|
||||
@Deactivate
|
||||
protected void deactivate() {
|
||||
stateDescriptionFragments.clear();
|
||||
}
|
||||
|
||||
public void onItemAdded(Item item) {
|
||||
logger.trace("onItemAdded {} {}", item.getName(), item.getType());
|
||||
if (item instanceof GroupItem group) {
|
||||
Item baseItem = group.getBaseItem();
|
||||
if (baseItem != null) {
|
||||
onItemAdded(baseItem);
|
||||
}
|
||||
} else if (item.getType().startsWith(CoreItemFactory.NUMBER + ":")) {
|
||||
stateDescriptionFragments.put(item.getName(), DEFAULT_NUMBER_WITH_DIMENSION);
|
||||
} else {
|
||||
switch (item.getType()) {
|
||||
case CoreItemFactory.STRING:
|
||||
stateDescriptionFragments.put(item.getName(), DEFAULT_STRING);
|
||||
break;
|
||||
case CoreItemFactory.DATETIME:
|
||||
stateDescriptionFragments.put(item.getName(), DEFAULT_DATETIME);
|
||||
break;
|
||||
case CoreItemFactory.NUMBER:
|
||||
stateDescriptionFragments.put(item.getName(), DEFAULT_NUMBER);
|
||||
break;
|
||||
default:
|
||||
stateDescriptionFragments.remove(item.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void onItemRemoved(Item item) {
|
||||
logger.trace("onItemRemoved {}", item.getName());
|
||||
stateDescriptionFragments.remove(item.getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable StateDescriptionFragment getStateDescriptionFragment(String itemName, @Nullable Locale locale) {
|
||||
return stateDescriptionFragments.get(itemName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer getRank() {
|
||||
return rank;
|
||||
}
|
||||
}
|
@ -59,6 +59,7 @@ import org.slf4j.LoggerFactory;
|
||||
*
|
||||
* @author Kai Kreuzer - Initial contribution
|
||||
* @author Stefan Bußweiler - Migration to new event mechanism
|
||||
* @author Laurent Garnier - handle new DefaultStateDescriptionFragmentProvider
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@Component(immediate = true)
|
||||
@ -71,13 +72,16 @@ public class ItemRegistryImpl extends AbstractRegistry<Item, String, ItemProvide
|
||||
private @Nullable StateDescriptionService stateDescriptionService;
|
||||
private @Nullable CommandDescriptionService commandDescriptionService;
|
||||
private final MetadataRegistry metadataRegistry;
|
||||
private final DefaultStateDescriptionFragmentProvider defaultStateDescriptionFragmentProvider;
|
||||
|
||||
private @Nullable ItemStateConverter itemStateConverter;
|
||||
|
||||
@Activate
|
||||
public ItemRegistryImpl(final @Reference MetadataRegistry metadataRegistry) {
|
||||
public ItemRegistryImpl(final @Reference MetadataRegistry metadataRegistry,
|
||||
final @Reference DefaultStateDescriptionFragmentProvider defaultStateDescriptionFragmentProvider) {
|
||||
super(ItemProvider.class);
|
||||
this.metadataRegistry = metadataRegistry;
|
||||
this.defaultStateDescriptionFragmentProvider = defaultStateDescriptionFragmentProvider;
|
||||
}
|
||||
|
||||
@Activate
|
||||
@ -198,6 +202,8 @@ public class ItemRegistryImpl extends AbstractRegistry<Item, String, ItemProvide
|
||||
|
||||
// add the item to all relevant groups
|
||||
addToGroupItems(item, item.getGroupNames());
|
||||
|
||||
defaultStateDescriptionFragmentProvider.onItemAdded(item);
|
||||
}
|
||||
|
||||
private void injectServices(Item item) {
|
||||
@ -246,6 +252,7 @@ public class ItemRegistryImpl extends AbstractRegistry<Item, String, ItemProvide
|
||||
genericItem.dispose();
|
||||
}
|
||||
removeFromGroupItems(element, element.getGroupNames());
|
||||
defaultStateDescriptionFragmentProvider.onItemRemoved(element);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -269,6 +276,9 @@ public class ItemRegistryImpl extends AbstractRegistry<Item, String, ItemProvide
|
||||
addMembersToGroupItem(groupItem);
|
||||
}
|
||||
injectServices(item);
|
||||
|
||||
defaultStateDescriptionFragmentProvider.onItemRemoved(oldItem);
|
||||
defaultStateDescriptionFragmentProvider.onItemAdded(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -159,6 +159,20 @@ public class StateDescriptionServiceImplTest {
|
||||
assertThat(stateDescription.getOptions(), is(stateDescriptionFragment2.getOptions()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPatternWhenTwoDescriptionProvidersHigherRankingDoesntProvidePattern() {
|
||||
StateDescriptionFragment stateDescriptionFragment1 = StateDescriptionFragmentBuilder.create().build();
|
||||
registerStateDescriptionFragmentProvider(stateDescriptionFragment1, -1);
|
||||
|
||||
StateDescriptionFragment stateDescriptionFragment2 = StateDescriptionFragmentBuilder.create()
|
||||
.withPattern("pattern").build();
|
||||
registerStateDescriptionFragmentProvider(stateDescriptionFragment2, -2);
|
||||
|
||||
StateDescription stateDescription = Objects.requireNonNull(item.getStateDescription());
|
||||
|
||||
assertThat(stateDescription.getPattern(), is(stateDescriptionFragment2.getPattern()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFragmentsAreMergedInProviderOrder() {
|
||||
final List<StateOption> options = List.of(new StateOption("value", "label"));
|
||||
|
@ -35,6 +35,7 @@ import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.openhab.core.common.registry.RegistryChangeListener;
|
||||
import org.openhab.core.events.EventPublisher;
|
||||
import org.openhab.core.i18n.UnitProvider;
|
||||
import org.openhab.core.internal.items.DefaultStateDescriptionFragmentProvider;
|
||||
import org.openhab.core.internal.items.ItemBuilderFactoryImpl;
|
||||
import org.openhab.core.internal.items.ItemRegistryImpl;
|
||||
import org.openhab.core.items.events.ItemAddedEvent;
|
||||
@ -101,7 +102,8 @@ public class ItemRegistryImplTest extends JavaTest {
|
||||
itemProvider.add(cameraItem4);
|
||||
|
||||
// setup ItemRegistryImpl with necessary dependencies:
|
||||
itemRegistry = new ItemRegistryImpl(mock(MetadataRegistry.class)) {
|
||||
itemRegistry = new ItemRegistryImpl(mock(MetadataRegistry.class),
|
||||
mock(DefaultStateDescriptionFragmentProvider.class)) {
|
||||
{
|
||||
addProvider(itemProvider);
|
||||
setManagedProvider(itemProvider);
|
||||
|
Loading…
Reference in New Issue
Block a user