mirror of
https://github.com/danieldemus/openhab-core.git
synced 2025-01-10 13:21:53 +01:00
[uom] Add unit
metadata for NumberItem (#3481)
* Add defaultUnit metadata for NumberItem Signed-off-by: Jan N. Klug <github@klug.nrw>
This commit is contained in:
parent
67b80af872
commit
9ef076dc6a
@ -13,6 +13,7 @@
|
||||
package org.openhab.core.automation.internal.module.handler;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.time.ZoneId;
|
||||
@ -20,6 +21,8 @@ import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.measure.quantity.Temperature;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@ -34,6 +37,7 @@ import org.openhab.core.automation.Condition;
|
||||
import org.openhab.core.automation.util.ConditionBuilder;
|
||||
import org.openhab.core.config.core.Configuration;
|
||||
import org.openhab.core.i18n.TimeZoneProvider;
|
||||
import org.openhab.core.i18n.UnitProvider;
|
||||
import org.openhab.core.items.Item;
|
||||
import org.openhab.core.items.ItemNotFoundException;
|
||||
import org.openhab.core.items.ItemRegistry;
|
||||
@ -77,7 +81,9 @@ public class ItemStateConditionHandlerTest extends JavaTest {
|
||||
((NumberItem) item).setState(itemState);
|
||||
break;
|
||||
case "Number:Temperature":
|
||||
item = new NumberItem("Number:Temperature", ITEM_NAME);
|
||||
UnitProvider unitProviderMock = mock(UnitProvider.class);
|
||||
when(unitProviderMock.getUnit(Temperature.class)).thenReturn(SIUnits.CELSIUS);
|
||||
item = new NumberItem("Number:Temperature", ITEM_NAME, unitProviderMock);
|
||||
((NumberItem) item).setState(itemState);
|
||||
break;
|
||||
case "Dimmer":
|
||||
@ -102,8 +108,8 @@ public class ItemStateConditionHandlerTest extends JavaTest {
|
||||
{ new ParameterSet("Number", "5", new DecimalType(23), false) }, //
|
||||
{ new ParameterSet("Number", "5", new DecimalType(5), true) }, //
|
||||
{ new ParameterSet("Number:Temperature", "5 °C", new DecimalType(23), false) }, //
|
||||
{ new ParameterSet("Number:Temperature", "5 °C", new DecimalType(5), false) }, //
|
||||
{ new ParameterSet("Number:Temperature", "0", new QuantityType<>(), true) }, //
|
||||
{ new ParameterSet("Number:Temperature", "5 °C", new DecimalType(5), true) }, //
|
||||
{ new ParameterSet("Number:Temperature", "0", new DecimalType(), false) }, //
|
||||
{ new ParameterSet("Number:Temperature", "5", new QuantityType<>(23, SIUnits.CELSIUS), false) }, //
|
||||
{ new ParameterSet("Number:Temperature", "5", new QuantityType<>(5, SIUnits.CELSIUS), false) }, //
|
||||
{ new ParameterSet("Number:Temperature", "5 °C", new QuantityType<>(23, SIUnits.CELSIUS), false) }, //
|
||||
@ -119,7 +125,7 @@ public class ItemStateConditionHandlerTest extends JavaTest {
|
||||
{ new ParameterSet("Number", "5", new DecimalType(5), false) }, //
|
||||
{ new ParameterSet("Number", "5 °C", new DecimalType(23), true) }, //
|
||||
{ new ParameterSet("Number", "5 °C", new DecimalType(5), false) }, //
|
||||
{ new ParameterSet("Number:Temperature", "0", new QuantityType<>(), false) }, //
|
||||
{ new ParameterSet("Number:Temperature", "0", new DecimalType(), false) }, //
|
||||
{ new ParameterSet("Number:Temperature", "5", new QuantityType<>(23, SIUnits.CELSIUS), true) }, //
|
||||
{ new ParameterSet("Number:Temperature", "5", new QuantityType<>(5, SIUnits.CELSIUS), false) }, //
|
||||
{ new ParameterSet("Number:Temperature", "5 °C", new QuantityType<>(23, SIUnits.CELSIUS), true) }, //
|
||||
@ -138,7 +144,7 @@ public class ItemStateConditionHandlerTest extends JavaTest {
|
||||
{ new ParameterSet("Number", "5 °C", new DecimalType(23), true) }, //
|
||||
{ new ParameterSet("Number", "5 °C", new DecimalType(5), true) }, //
|
||||
{ new ParameterSet("Number", "5 °C", new DecimalType(4), false) }, //
|
||||
{ new ParameterSet("Number:Temperature", "0", new QuantityType<>(), true) }, //
|
||||
{ new ParameterSet("Number:Temperature", "0", new DecimalType(), true) }, //
|
||||
{ new ParameterSet("Number:Temperature", "5", new QuantityType<>(23, SIUnits.CELSIUS), true) }, //
|
||||
{ new ParameterSet("Number:Temperature", "5", new QuantityType<>(5, SIUnits.CELSIUS), true) }, //
|
||||
{ new ParameterSet("Number:Temperature", "5", new QuantityType<>(4, SIUnits.CELSIUS), false) }, //
|
||||
@ -159,7 +165,7 @@ public class ItemStateConditionHandlerTest extends JavaTest {
|
||||
{ new ParameterSet("Number", "5", new DecimalType(4), true) }, //
|
||||
{ new ParameterSet("Number", "5 °C", new DecimalType(23), false) }, //
|
||||
{ new ParameterSet("Number", "5 °C", new DecimalType(4), true) }, //
|
||||
{ new ParameterSet("Number:Temperature", "0", new QuantityType<>(), false) }, //
|
||||
{ new ParameterSet("Number:Temperature", "0", new DecimalType(), false) }, //
|
||||
{ new ParameterSet("Number:Temperature", "5", new QuantityType<>(23, SIUnits.CELSIUS), false) }, //
|
||||
{ new ParameterSet("Number:Temperature", "5", new QuantityType<>(4, SIUnits.CELSIUS), true) }, //
|
||||
{ new ParameterSet("Number:Temperature", "5 °C", new QuantityType<>(23, SIUnits.CELSIUS), false) }, //
|
||||
@ -179,7 +185,7 @@ public class ItemStateConditionHandlerTest extends JavaTest {
|
||||
{ new ParameterSet("Number", "5 °C", new DecimalType(23), false) }, //
|
||||
{ new ParameterSet("Number", "5 °C", new DecimalType(5), true) }, //
|
||||
{ new ParameterSet("Number", "5 °C", new DecimalType(4), true) }, //
|
||||
{ new ParameterSet("Number:Temperature", "0", new QuantityType<>(), true) }, //
|
||||
{ new ParameterSet("Number:Temperature", "0", new DecimalType(), true) }, //
|
||||
{ new ParameterSet("Number:Temperature", "5", new QuantityType<>(23, SIUnits.CELSIUS), false) }, //
|
||||
{ new ParameterSet("Number:Temperature", "5", new QuantityType<>(5, SIUnits.CELSIUS), true) }, //
|
||||
{ new ParameterSet("Number:Temperature", "5", new QuantityType<>(4, SIUnits.CELSIUS), true) }, //
|
||||
@ -220,9 +226,11 @@ public class ItemStateConditionHandlerTest extends JavaTest {
|
||||
ItemStateConditionHandler handler = initItemStateConditionHandler("=", parameterSet.comparisonState);
|
||||
|
||||
if (parameterSet.expectedResult) {
|
||||
assertTrue(handler.isSatisfied(Map.of()));
|
||||
assertTrue(handler.isSatisfied(Map.of()),
|
||||
parameterSet.item + ", comparisonState=" + parameterSet.comparisonState);
|
||||
} else {
|
||||
assertFalse(handler.isSatisfied(Map.of()));
|
||||
assertFalse(handler.isSatisfied(Map.of()),
|
||||
parameterSet.item + ", comparisonState=" + parameterSet.comparisonState);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -15,10 +15,12 @@ package org.openhab.core.io.rest.core.item;
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.openhab.core.i18n.UnitProvider;
|
||||
import org.openhab.core.items.GenericItem;
|
||||
import org.openhab.core.items.GroupItem;
|
||||
import org.openhab.core.library.CoreItemFactory;
|
||||
@ -38,7 +40,7 @@ public class EnrichedItemDTOMapperTest extends JavaTest {
|
||||
|
||||
@Test
|
||||
public void testFiltering() {
|
||||
CoreItemFactory itemFactory = new CoreItemFactory();
|
||||
CoreItemFactory itemFactory = new CoreItemFactory(mock(UnitProvider.class));
|
||||
|
||||
GroupItem group = new GroupItem("TestGroup");
|
||||
GroupItem subGroup = new GroupItem("TestSubGroup");
|
||||
|
@ -18,6 +18,7 @@ import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
@ -25,6 +26,7 @@ import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.mockito.junit.jupiter.MockitoSettings;
|
||||
import org.mockito.quality.Strictness;
|
||||
import org.openhab.core.i18n.UnitProvider;
|
||||
import org.openhab.core.items.GenericItem;
|
||||
import org.openhab.core.items.GroupItem;
|
||||
import org.openhab.core.items.ItemNotFoundException;
|
||||
@ -43,20 +45,22 @@ import org.openhab.core.semantics.model.location.Indoor;
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
@MockitoSettings(strictness = Strictness.LENIENT)
|
||||
@NonNullByDefault
|
||||
public class SemanticsTest {
|
||||
|
||||
private @Mock ItemRegistry mockedItemRegistry;
|
||||
private @Mock @NonNullByDefault({}) ItemRegistry itemRegistryMock;
|
||||
private @Mock @NonNullByDefault({}) UnitProvider unitProviderMock;
|
||||
|
||||
private GroupItem indoorLocationItem;
|
||||
private GroupItem bathroomLocationItem;
|
||||
private GroupItem equipmentItem;
|
||||
private GenericItem temperaturePointItem;
|
||||
private GenericItem humidityPointItem;
|
||||
private GenericItem subEquipmentItem;
|
||||
private @NonNullByDefault({}) GroupItem indoorLocationItem;
|
||||
private @NonNullByDefault({}) GroupItem bathroomLocationItem;
|
||||
private @NonNullByDefault({}) GroupItem equipmentItem;
|
||||
private @NonNullByDefault({}) GenericItem temperaturePointItem;
|
||||
private @NonNullByDefault({}) GenericItem humidityPointItem;
|
||||
private @NonNullByDefault({}) GenericItem subEquipmentItem;
|
||||
|
||||
@BeforeEach
|
||||
public void setup() throws ItemNotFoundException {
|
||||
CoreItemFactory itemFactory = new CoreItemFactory();
|
||||
CoreItemFactory itemFactory = new CoreItemFactory(unitProviderMock);
|
||||
|
||||
indoorLocationItem = new GroupItem("TestHouse");
|
||||
indoorLocationItem.addTag("Indoor");
|
||||
@ -94,13 +98,13 @@ public class SemanticsTest {
|
||||
equipmentItem.addMember(subEquipmentItem);
|
||||
subEquipmentItem.addGroupName(equipmentItem.getName());
|
||||
|
||||
when(mockedItemRegistry.getItem("TestHouse")).thenReturn(indoorLocationItem);
|
||||
when(mockedItemRegistry.getItem("TestBathRoom")).thenReturn(bathroomLocationItem);
|
||||
when(mockedItemRegistry.getItem("Test08")).thenReturn(equipmentItem);
|
||||
when(mockedItemRegistry.getItem("TestTemperature")).thenReturn(temperaturePointItem);
|
||||
when(mockedItemRegistry.getItem("TestHumidity")).thenReturn(humidityPointItem);
|
||||
when(itemRegistryMock.getItem("TestHouse")).thenReturn(indoorLocationItem);
|
||||
when(itemRegistryMock.getItem("TestBathRoom")).thenReturn(bathroomLocationItem);
|
||||
when(itemRegistryMock.getItem("Test08")).thenReturn(equipmentItem);
|
||||
when(itemRegistryMock.getItem("TestTemperature")).thenReturn(temperaturePointItem);
|
||||
when(itemRegistryMock.getItem("TestHumidity")).thenReturn(humidityPointItem);
|
||||
|
||||
new SemanticsActionService(mockedItemRegistry);
|
||||
new SemanticsActionService(itemRegistryMock);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -82,11 +82,10 @@ public class PersistenceExtensionsTest {
|
||||
public void setUp() {
|
||||
when(unitProviderMock.getUnit(Temperature.class)).thenReturn(SIUnits.CELSIUS);
|
||||
|
||||
CoreItemFactory itemFactory = new CoreItemFactory();
|
||||
CoreItemFactory itemFactory = new CoreItemFactory(unitProviderMock);
|
||||
numberItem = itemFactory.createItem(CoreItemFactory.NUMBER, TEST_NUMBER);
|
||||
quantityItem = itemFactory.createItem(CoreItemFactory.NUMBER + ItemUtil.EXTENSION_SEPARATOR + "Temperature",
|
||||
TEST_QUANTITY_NUMBER);
|
||||
quantityItem.setUnitProvider(unitProviderMock);
|
||||
switchItem = itemFactory.createItem(CoreItemFactory.SWITCH, TEST_SWITCH);
|
||||
|
||||
when(itemRegistryMock.get(TEST_NUMBER)).thenReturn(numberItem);
|
||||
|
@ -15,12 +15,14 @@ package org.openhab.core.semantics;
|
||||
import static org.hamcrest.CoreMatchers.*;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.openhab.core.i18n.UnitProvider;
|
||||
import org.openhab.core.items.GenericItem;
|
||||
import org.openhab.core.items.GroupItem;
|
||||
import org.openhab.core.library.CoreItemFactory;
|
||||
@ -49,7 +51,7 @@ public class SemanticTagsTest {
|
||||
|
||||
@BeforeEach
|
||||
public void setup() {
|
||||
CoreItemFactory itemFactory = new CoreItemFactory();
|
||||
CoreItemFactory itemFactory = new CoreItemFactory(mock(UnitProvider.class));
|
||||
|
||||
locationItem = new GroupItem("TestBathRoom");
|
||||
locationItem.addTag("Bathroom");
|
||||
|
@ -13,10 +13,12 @@
|
||||
package org.openhab.core.semantics;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.openhab.core.i18n.UnitProvider;
|
||||
import org.openhab.core.items.GenericItem;
|
||||
import org.openhab.core.items.GroupItem;
|
||||
import org.openhab.core.library.CoreItemFactory;
|
||||
@ -24,7 +26,7 @@ import org.openhab.core.semantics.model.property.Humidity;
|
||||
import org.openhab.core.semantics.model.property.Temperature;
|
||||
|
||||
/**
|
||||
* This are tests for {@link SemanticsPredicates}.
|
||||
* These are tests for {@link SemanticsPredicates}.
|
||||
*
|
||||
* @author Christoph Weitkamp - Initial contribution
|
||||
*/
|
||||
@ -37,7 +39,7 @@ public class SemanticsPredicatesTest {
|
||||
|
||||
@BeforeEach
|
||||
public void setup() {
|
||||
CoreItemFactory itemFactory = new CoreItemFactory();
|
||||
CoreItemFactory itemFactory = new CoreItemFactory(mock(UnitProvider.class));
|
||||
|
||||
locationItem = new GroupItem("TestBathRoom");
|
||||
locationItem.addTag("Bathroom");
|
||||
|
@ -25,6 +25,7 @@ import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.openhab.core.i18n.UnitProvider;
|
||||
import org.openhab.core.items.GenericItem;
|
||||
import org.openhab.core.items.GroupItem;
|
||||
import org.openhab.core.items.Item;
|
||||
@ -43,6 +44,7 @@ public class SemanticsServiceImplTest {
|
||||
|
||||
private @Mock @NonNullByDefault({}) ItemRegistry itemRegistryMock;
|
||||
private @Mock @NonNullByDefault({}) MetadataRegistry metadataRegistryMock;
|
||||
private @Mock @NonNullByDefault({}) UnitProvider unitProviderMock;
|
||||
|
||||
private @NonNullByDefault({}) GroupItem locationItem;
|
||||
private @NonNullByDefault({}) GroupItem equipmentItem;
|
||||
@ -52,7 +54,7 @@ public class SemanticsServiceImplTest {
|
||||
|
||||
@BeforeEach
|
||||
public void setup() throws Exception {
|
||||
CoreItemFactory itemFactory = new CoreItemFactory();
|
||||
CoreItemFactory itemFactory = new CoreItemFactory(unitProviderMock);
|
||||
locationItem = new GroupItem("TestBathRoom");
|
||||
locationItem.addTag("Bathroom");
|
||||
locationItem.setLabel("Joe's Room");
|
||||
|
@ -24,7 +24,7 @@ import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
import javax.measure.Quantity;
|
||||
import javax.measure.Unit;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
@ -35,6 +35,7 @@ import org.openhab.core.common.registry.RegistryChangeListener;
|
||||
import org.openhab.core.events.Event;
|
||||
import org.openhab.core.events.EventPublisher;
|
||||
import org.openhab.core.events.EventSubscriber;
|
||||
import org.openhab.core.i18n.UnitProvider;
|
||||
import org.openhab.core.items.Item;
|
||||
import org.openhab.core.items.ItemFactory;
|
||||
import org.openhab.core.items.ItemRegistry;
|
||||
@ -44,8 +45,10 @@ import org.openhab.core.items.events.AbstractItemRegistryEvent;
|
||||
import org.openhab.core.items.events.GroupStateUpdatedEvent;
|
||||
import org.openhab.core.items.events.ItemCommandEvent;
|
||||
import org.openhab.core.items.events.ItemStateUpdatedEvent;
|
||||
import org.openhab.core.library.CoreItemFactory;
|
||||
import org.openhab.core.library.items.NumberItem;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.library.types.HSBType;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.PercentType;
|
||||
import org.openhab.core.library.types.QuantityType;
|
||||
@ -70,13 +73,10 @@ import org.openhab.core.thing.profiles.ProfileFactory;
|
||||
import org.openhab.core.thing.profiles.ProfileTypeUID;
|
||||
import org.openhab.core.thing.profiles.StateProfile;
|
||||
import org.openhab.core.thing.profiles.TriggerProfile;
|
||||
import org.openhab.core.thing.type.ChannelType;
|
||||
import org.openhab.core.thing.type.ChannelTypeRegistry;
|
||||
import org.openhab.core.thing.type.ChannelTypeUID;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.State;
|
||||
import org.openhab.core.types.Type;
|
||||
import org.openhab.core.types.util.UnitUtils;
|
||||
import org.osgi.service.component.annotations.Activate;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.osgi.service.component.annotations.Deactivate;
|
||||
@ -131,6 +131,7 @@ public class CommunicationManager implements EventSubscriber, RegistryChangeList
|
||||
private final EventPublisher eventPublisher;
|
||||
private final SafeCaller safeCaller;
|
||||
private final ThingRegistry thingRegistry;
|
||||
private final UnitProvider unitProvider;
|
||||
|
||||
private final ExpiringCacheMap<Integer, Profile> profileSafeCallCache = new ExpiringCacheMap<>(CACHE_EXPIRATION);
|
||||
|
||||
@ -143,7 +144,7 @@ public class CommunicationManager implements EventSubscriber, RegistryChangeList
|
||||
final @Reference ItemStateConverter itemStateConverter, //
|
||||
final @Reference EventPublisher eventPublisher, //
|
||||
final @Reference SafeCaller safeCaller, //
|
||||
final @Reference ThingRegistry thingRegistry) {
|
||||
final @Reference ThingRegistry thingRegistry, final @Reference UnitProvider unitProvider) {
|
||||
this.autoUpdateManager = autoUpdateManager;
|
||||
this.channelTypeRegistry = channelTypeRegistry;
|
||||
this.defaultProfileFactory = defaultProfileFactory;
|
||||
@ -153,6 +154,7 @@ public class CommunicationManager implements EventSubscriber, RegistryChangeList
|
||||
this.eventPublisher = eventPublisher;
|
||||
this.safeCaller = safeCaller;
|
||||
this.thingRegistry = thingRegistry;
|
||||
this.unitProvider = unitProvider;
|
||||
|
||||
itemChannelLinkRegistry.addRegistryChangeListener(this);
|
||||
}
|
||||
@ -203,10 +205,6 @@ public class CommunicationManager implements EventSubscriber, RegistryChangeList
|
||||
}
|
||||
}
|
||||
|
||||
private @Nullable Thing getThing(ThingUID thingUID) {
|
||||
return thingRegistry.get(thingUID);
|
||||
}
|
||||
|
||||
private Profile getProfile(ItemChannelLink link, Item item, @Nullable Thing thing) {
|
||||
synchronized (profiles) {
|
||||
Profile profile = profiles.get(link.getUID());
|
||||
@ -228,8 +226,8 @@ public class CommunicationManager implements EventSubscriber, RegistryChangeList
|
||||
}
|
||||
|
||||
private ProfileCallback createCallback(ItemChannelLink link) {
|
||||
return new ProfileCallbackImpl(eventPublisher, safeCaller, itemStateConverter, link,
|
||||
thingUID -> getThing(thingUID), itemName -> getItem(itemName));
|
||||
return new ProfileCallbackImpl(eventPublisher, safeCaller, itemStateConverter, link, thingRegistry::get,
|
||||
this::getItem);
|
||||
}
|
||||
|
||||
private @Nullable ProfileTypeUID determineProfileTypeUID(ItemChannelLink link, Item item, @Nullable Thing thing) {
|
||||
@ -272,25 +270,21 @@ public class CommunicationManager implements EventSubscriber, RegistryChangeList
|
||||
String profileName = (String) link.getConfiguration()
|
||||
.get(ItemChannelLinkConfigDescriptionProvider.PARAM_PROFILE);
|
||||
if (profileName != null && !profileName.trim().isEmpty()) {
|
||||
profileName = normalizeProfileName(profileName);
|
||||
if (!profileName.contains(AbstractUID.SEPARATOR)) {
|
||||
profileName = ProfileTypeUID.SYSTEM_SCOPE + AbstractUID.SEPARATOR + profileName;
|
||||
}
|
||||
return new ProfileTypeUID(profileName);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private String normalizeProfileName(String profileName) {
|
||||
if (!profileName.contains(AbstractUID.SEPARATOR)) {
|
||||
return ProfileTypeUID.SYSTEM_SCOPE + AbstractUID.SEPARATOR + profileName;
|
||||
}
|
||||
return profileName;
|
||||
}
|
||||
|
||||
private @Nullable Profile getProfileFromFactories(ProfileTypeUID profileTypeUID, ItemChannelLink link,
|
||||
ProfileCallback callback) {
|
||||
ProfileContext context = null;
|
||||
|
||||
Item item = getItem(link.getItemName());
|
||||
Thing thing = getThing(link.getLinkedUID().getThingUID());
|
||||
ThingUID thingUID = link.getLinkedUID().getThingUID();
|
||||
Thing thing = thingRegistry.get(thingUID);
|
||||
if (item != null && thing != null) {
|
||||
Channel channel = thing.getChannel(link.getLinkedUID());
|
||||
if (channel != null) {
|
||||
@ -341,48 +335,51 @@ public class CommunicationManager implements EventSubscriber, RegistryChangeList
|
||||
autoUpdateManager.receiveCommand(commandEvent, item);
|
||||
}
|
||||
|
||||
handleEvent(itemName, command, commandEvent.getSource(), s -> acceptedCommandTypeMap.get(s),
|
||||
(profile, thing, convertedCommand) -> {
|
||||
if (profile instanceof StateProfile stateProfile) {
|
||||
int key = Objects.hash("COMMAND", profile, thing);
|
||||
Profile p = profileSafeCallCache.putIfAbsentAndGet(key,
|
||||
() -> safeCaller.create(stateProfile, StateProfile.class) //
|
||||
.withAsync() //
|
||||
.withIdentifier(thing) //
|
||||
.withTimeout(THINGHANDLER_EVENT_TIMEOUT) //
|
||||
.build());
|
||||
if (p instanceof StateProfile profileP) {
|
||||
profileP.onCommandFromItem(convertedCommand);
|
||||
} else {
|
||||
throw new IllegalStateException("ExpiringCache didn't provide a StateProfile instance!");
|
||||
}
|
||||
}
|
||||
});
|
||||
handleEvent(itemName, command, commandEvent.getSource(), acceptedCommandTypeMap::get,
|
||||
this::applyProfileForCommand);
|
||||
}
|
||||
|
||||
private void receiveUpdate(ItemStateUpdatedEvent updateEvent) {
|
||||
final String itemName = updateEvent.getItemName();
|
||||
final State newState = updateEvent.getItemState();
|
||||
handleEvent(itemName, newState, updateEvent.getSource(), s -> acceptedStateTypeMap.get(s),
|
||||
(profile, thing, convertedState) -> {
|
||||
int key = Objects.hash("UPDATE", profile, thing);
|
||||
Profile p = profileSafeCallCache.putIfAbsentAndGet(key,
|
||||
() -> safeCaller.create(profile, Profile.class) //
|
||||
.withAsync() //
|
||||
.withIdentifier(thing) //
|
||||
.withTimeout(THINGHANDLER_EVENT_TIMEOUT) //
|
||||
.build());
|
||||
if (p != null) {
|
||||
p.onStateUpdateFromItem(convertedState);
|
||||
} else {
|
||||
throw new IllegalStateException("ExpiringCache didn't provide a Profile instance!");
|
||||
}
|
||||
});
|
||||
handleEvent(itemName, newState, updateEvent.getSource(), acceptedStateTypeMap::get,
|
||||
this::applyProfileForUpdate);
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
private static interface ProfileAction<T extends Type> {
|
||||
void handle(Profile profile, Thing thing, T type);
|
||||
private interface ProfileAction<T extends Type> {
|
||||
void applyProfile(Profile profile, Thing thing, T type);
|
||||
}
|
||||
|
||||
private void applyProfileForUpdate(Profile profile, Thing thing, State convertedState) {
|
||||
int key = Objects.hash("UPDATE", profile, thing);
|
||||
Profile p = profileSafeCallCache.putIfAbsentAndGet(key, () -> safeCaller.create(profile, Profile.class) //
|
||||
.withAsync() //
|
||||
.withIdentifier(thing) //
|
||||
.withTimeout(THINGHANDLER_EVENT_TIMEOUT) //
|
||||
.build());
|
||||
if (p != null) {
|
||||
p.onStateUpdateFromItem(convertedState);
|
||||
} else {
|
||||
throw new IllegalStateException("ExpiringCache didn't provide a Profile instance!");
|
||||
}
|
||||
}
|
||||
|
||||
private void applyProfileForCommand(Profile profile, Thing thing, Command convertedCommand) {
|
||||
if (profile instanceof StateProfile stateProfile) {
|
||||
int key = Objects.hash("COMMAND", profile, thing);
|
||||
Profile p = profileSafeCallCache.putIfAbsentAndGet(key,
|
||||
() -> safeCaller.create(stateProfile, StateProfile.class) //
|
||||
.withAsync() //
|
||||
.withIdentifier(thing) //
|
||||
.withTimeout(THINGHANDLER_EVENT_TIMEOUT) //
|
||||
.build());
|
||||
if (p instanceof StateProfile profileP) {
|
||||
profileP.onCommandFromItem(convertedCommand);
|
||||
} else {
|
||||
throw new IllegalStateException("ExpiringCache didn't provide a StateProfile instance!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private <T extends Type> void handleEvent(String itemName, T type, @Nullable String source,
|
||||
@ -399,7 +396,8 @@ public class CommunicationManager implements EventSubscriber, RegistryChangeList
|
||||
return !link.getLinkedUID().toString().equals(source);
|
||||
}).forEach(link -> {
|
||||
ChannelUID channelUID = link.getLinkedUID();
|
||||
Thing thing = getThing(channelUID.getThingUID());
|
||||
ThingUID thingUID = channelUID.getThingUID();
|
||||
Thing thing = thingRegistry.get(thingUID);
|
||||
if (thing != null) {
|
||||
Channel channel = thing.getChannel(channelUID.getId());
|
||||
if (channel != null) {
|
||||
@ -408,7 +406,7 @@ public class CommunicationManager implements EventSubscriber, RegistryChangeList
|
||||
if (convertedType != null) {
|
||||
if (thing.getHandler() != null) {
|
||||
Profile profile = getProfile(link, item, thing);
|
||||
action.handle(profile, thing, convertedType);
|
||||
action.applyProfile(profile, thing, convertedType);
|
||||
}
|
||||
} else {
|
||||
logger.debug(
|
||||
@ -429,45 +427,37 @@ public class CommunicationManager implements EventSubscriber, RegistryChangeList
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T extends Type> @Nullable T toAcceptedType(T originalType, Channel channel,
|
||||
Function<@Nullable String, @Nullable List<Class<? extends T>>> acceptedTypesFunction, Item item) {
|
||||
String acceptedItemType = channel.getAcceptedItemType();
|
||||
String channelAcceptedItemType = channel.getAcceptedItemType();
|
||||
|
||||
// DecimalType command sent to a NumberItem with dimension defined:
|
||||
if (originalType instanceof DecimalType type && hasDimension(item, acceptedItemType)) {
|
||||
@Nullable
|
||||
QuantityType<?> quantityType = convertToQuantityType(type, item, acceptedItemType);
|
||||
if (quantityType != null) {
|
||||
return (T) quantityType;
|
||||
}
|
||||
}
|
||||
|
||||
// The command is sent to an item w/o dimension defined and the channel is legacy (created from a ThingType
|
||||
// definition before UoM was introduced to the binding). The dimension information might now be defined on the
|
||||
// current ThingType. The binding might expect us to provide a QuantityType so try to convert to the dimension
|
||||
// the ChannelType provides.
|
||||
// This can be removed once a suitable solution for https://github.com/eclipse/smarthome/issues/2555 (Thing
|
||||
// migration) is found.
|
||||
if (originalType instanceof DecimalType type && !hasDimension(item, acceptedItemType)
|
||||
&& channelTypeDefinesDimension(channel.getChannelTypeUID())) {
|
||||
ChannelType channelType = channelTypeRegistry.getChannelType(channel.getChannelTypeUID());
|
||||
|
||||
String acceptedItemTypeFromChannelType = channelType != null ? channelType.getItemType() : null;
|
||||
@Nullable
|
||||
QuantityType<?> quantityType = convertToQuantityType(type, item, acceptedItemTypeFromChannelType);
|
||||
if (quantityType != null) {
|
||||
return (T) quantityType;
|
||||
}
|
||||
}
|
||||
|
||||
if (acceptedItemType == null) {
|
||||
if (channelAcceptedItemType == null) {
|
||||
return originalType;
|
||||
}
|
||||
|
||||
List<Class<? extends T>> acceptedTypes = acceptedTypesFunction.apply(acceptedItemType);
|
||||
if (acceptedTypes == null) {
|
||||
return originalType;
|
||||
// handle Number-Channels for backward compatibility
|
||||
if (CoreItemFactory.NUMBER.equals(channelAcceptedItemType)
|
||||
&& originalType instanceof QuantityType<?> quantityType) {
|
||||
// strip unit from QuantityType for channels that accept plain number
|
||||
return (T) new DecimalType(quantityType.toBigDecimal());
|
||||
}
|
||||
|
||||
if (acceptedTypes.contains(originalType.getClass())) {
|
||||
String itemDimension = ItemUtil.getItemTypeExtension(item.getType());
|
||||
String channelDimension = ItemUtil.getItemTypeExtension(channelAcceptedItemType);
|
||||
|
||||
if (originalType instanceof DecimalType decimalType && channelDimension != null
|
||||
&& channelDimension.equals(itemDimension)) {
|
||||
// Add unit from item to DecimalType when dimensions are equal
|
||||
Unit<?> unit = Objects.requireNonNull(((NumberItem) item).getUnit());
|
||||
return (T) new QuantityType<>(decimalType.toBigDecimal(), unit);
|
||||
}
|
||||
|
||||
// handle HSBType/PercentType
|
||||
if (CoreItemFactory.DIMMER.equals(channelAcceptedItemType) && originalType instanceof HSBType hsb) {
|
||||
return (T) (hsb.as(PercentType.class));
|
||||
}
|
||||
|
||||
// check for other cases if the type is acceptable
|
||||
List<Class<? extends T>> acceptedTypes = acceptedTypesFunction.apply(channelAcceptedItemType);
|
||||
if (acceptedTypes == null || acceptedTypes.contains(originalType.getClass())) {
|
||||
return originalType;
|
||||
} else if (acceptedTypes.contains(PercentType.class) && originalType instanceof State state
|
||||
&& PercentType.class.isAssignableFrom(originalType.getClass())) {
|
||||
@ -476,77 +466,10 @@ public class CommunicationManager implements EventSubscriber, RegistryChangeList
|
||||
&& PercentType.class.isAssignableFrom(originalType.getClass())) {
|
||||
return (@Nullable T) state.as(OnOffType.class);
|
||||
} else {
|
||||
// Look for class hierarchy and convert appropriately
|
||||
for (Class<? extends T> typeClass : acceptedTypes) {
|
||||
if (!typeClass.isEnum() && typeClass.isAssignableFrom(originalType.getClass()) //
|
||||
&& State.class.isAssignableFrom(typeClass) && originalType instanceof State state) {
|
||||
T ret = (T) state.as((Class<? extends State>) typeClass);
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Converted '{}' ({}) to accepted type '{}' ({}) for channel '{}' ", originalType,
|
||||
originalType.getClass().getSimpleName(), ret, ret.getClass().getName(),
|
||||
channel.getUID());
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
logger.debug("Received not accepted type '{}' for channel '{}'", originalType.getClass().getSimpleName(),
|
||||
channel.getUID());
|
||||
return null;
|
||||
}
|
||||
|
||||
private boolean channelTypeDefinesDimension(@Nullable ChannelTypeUID channelTypeUID) {
|
||||
if (channelTypeUID == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ChannelType channelType = channelTypeRegistry.getChannelType(channelTypeUID);
|
||||
return channelType != null && getDimension(channelType.getItemType()) != null;
|
||||
}
|
||||
|
||||
private boolean hasDimension(Item item, @Nullable String acceptedItemType) {
|
||||
return (item instanceof NumberItem ni && ni.getDimension() != null) || getDimension(acceptedItemType) != null;
|
||||
}
|
||||
|
||||
private @Nullable QuantityType<?> convertToQuantityType(DecimalType originalType, Item item,
|
||||
@Nullable String acceptedItemType) {
|
||||
if (!(item instanceof NumberItem)) {
|
||||
// PercentType command sent via DimmerItem to a channel that's dimensioned
|
||||
// (such as Number:Dimensionless, expecting a %).
|
||||
// We can't know the proper units to add, so just pass it through and assume
|
||||
// The binding can deal with it.
|
||||
logger.debug("Received not accepted type '{}' for channel '{}'", originalType.getClass().getSimpleName(),
|
||||
channel.getUID());
|
||||
return null;
|
||||
}
|
||||
|
||||
NumberItem numberItem = (NumberItem) item;
|
||||
|
||||
// DecimalType command sent via a NumberItem with dimension:
|
||||
Class<? extends Quantity<?>> dimension = numberItem.getDimension();
|
||||
|
||||
if (dimension == null) {
|
||||
// DecimalType command sent via a plain NumberItem w/o dimension.
|
||||
// We try to guess the correct unit from the channel-type's expected item dimension
|
||||
// or from the item's state description.
|
||||
dimension = getDimension(acceptedItemType);
|
||||
}
|
||||
|
||||
if (dimension != null) {
|
||||
return numberItem.toQuantityType(originalType, dimension);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private @Nullable Class<? extends Quantity<?>> getDimension(@Nullable String acceptedItemType) {
|
||||
if (acceptedItemType == null || acceptedItemType.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
String itemTypeExtension = ItemUtil.getItemTypeExtension(acceptedItemType);
|
||||
if (itemTypeExtension == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return UnitUtils.parseDimension(itemTypeExtension);
|
||||
}
|
||||
|
||||
private @Nullable Item getItem(final String itemName) {
|
||||
@ -556,7 +479,8 @@ public class CommunicationManager implements EventSubscriber, RegistryChangeList
|
||||
private void receiveTrigger(ChannelTriggeredEvent channelTriggeredEvent) {
|
||||
final ChannelUID channelUID = channelTriggeredEvent.getChannel();
|
||||
final String event = channelTriggeredEvent.getEvent();
|
||||
final Thing thing = getThing(channelUID.getThingUID());
|
||||
ThingUID thingUID = channelUID.getThingUID();
|
||||
final Thing thing = thingRegistry.get(thingUID);
|
||||
|
||||
handleCallFromHandler(channelUID, thing, profile -> {
|
||||
if (profile instanceof TriggerProfile triggerProfile) {
|
||||
@ -566,7 +490,8 @@ public class CommunicationManager implements EventSubscriber, RegistryChangeList
|
||||
}
|
||||
|
||||
public void stateUpdated(ChannelUID channelUID, State state) {
|
||||
final Thing thing = getThing(channelUID.getThingUID());
|
||||
ThingUID thingUID = channelUID.getThingUID();
|
||||
final Thing thing = thingRegistry.get(thingUID);
|
||||
|
||||
handleCallFromHandler(channelUID, thing, profile -> {
|
||||
if (profile instanceof StateProfile stateProfile) {
|
||||
@ -576,7 +501,8 @@ public class CommunicationManager implements EventSubscriber, RegistryChangeList
|
||||
}
|
||||
|
||||
public void postCommand(ChannelUID channelUID, Command command) {
|
||||
final Thing thing = getThing(channelUID.getThingUID());
|
||||
ThingUID thingUID = channelUID.getThingUID();
|
||||
final Thing thing = thingRegistry.get(thingUID);
|
||||
|
||||
handleCallFromHandler(channelUID, thing, profile -> {
|
||||
if (profile instanceof StateProfile stateProfile) {
|
||||
@ -585,7 +511,7 @@ public class CommunicationManager implements EventSubscriber, RegistryChangeList
|
||||
});
|
||||
}
|
||||
|
||||
void handleCallFromHandler(ChannelUID channelUID, @Nullable Thing thing, Consumer<Profile> action) {
|
||||
private void handleCallFromHandler(ChannelUID channelUID, @Nullable Thing thing, Consumer<Profile> action) {
|
||||
itemChannelLinkRegistry.getLinks(channelUID).forEach(link -> {
|
||||
final Item item = getItem(link.getItemName());
|
||||
if (item != null) {
|
||||
@ -630,9 +556,7 @@ public class CommunicationManager implements EventSubscriber, RegistryChangeList
|
||||
protected void removeProfileFactory(ProfileFactory profileFactory) {
|
||||
Set<String> links = profileFactories.remove(profileFactory);
|
||||
synchronized (profiles) {
|
||||
links.forEach(link -> {
|
||||
profiles.remove(link);
|
||||
});
|
||||
links.forEach(profiles::remove);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,141 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.thing;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
import org.openhab.core.items.Item;
|
||||
import org.openhab.core.library.items.CallItem;
|
||||
import org.openhab.core.library.items.ColorItem;
|
||||
import org.openhab.core.library.items.ContactItem;
|
||||
import org.openhab.core.library.items.DateTimeItem;
|
||||
import org.openhab.core.library.items.DimmerItem;
|
||||
import org.openhab.core.library.items.ImageItem;
|
||||
import org.openhab.core.library.items.LocationItem;
|
||||
import org.openhab.core.library.items.PlayerItem;
|
||||
import org.openhab.core.library.items.RollershutterItem;
|
||||
import org.openhab.core.library.items.StringItem;
|
||||
import org.openhab.core.library.types.DateTimeType;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.library.types.HSBType;
|
||||
import org.openhab.core.library.types.IncreaseDecreaseType;
|
||||
import org.openhab.core.library.types.NextPreviousType;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.OpenClosedType;
|
||||
import org.openhab.core.library.types.PercentType;
|
||||
import org.openhab.core.library.types.PlayPauseType;
|
||||
import org.openhab.core.library.types.PointType;
|
||||
import org.openhab.core.library.types.QuantityType;
|
||||
import org.openhab.core.library.types.RawType;
|
||||
import org.openhab.core.library.types.RewindFastforwardType;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
import org.openhab.core.library.types.UpDownType;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.State;
|
||||
import org.openhab.core.types.Type;
|
||||
import org.openhab.core.types.UnDefType;
|
||||
|
||||
/**
|
||||
* @author Jan N. Klug - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class CommunicationManagerConversionTest {
|
||||
// TODO: remove test - only to show CommunicationManager is too complex
|
||||
|
||||
private static final List<Class<? extends Item>> itemTypes = List.of(CallItem.class, ColorItem.class,
|
||||
ContactItem.class, DateTimeItem.class, DimmerItem.class, ImageItem.class, LocationItem.class,
|
||||
PlayerItem.class, RollershutterItem.class, StringItem.class);
|
||||
|
||||
private static final List<Class<? extends Type>> types = List.of(DateTimeType.class, DecimalType.class,
|
||||
HSBType.class, IncreaseDecreaseType.class, NextPreviousType.class, OnOffType.class, OpenClosedType.class,
|
||||
PercentType.class, PlayPauseType.class, PointType.class, QuantityType.class, RawType.class,
|
||||
RewindFastforwardType.class, StringType.class, UpDownType.class, UnDefType.class);
|
||||
|
||||
private static Stream<Arguments> arguments()
|
||||
throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
|
||||
List<Arguments> arguments = new ArrayList<>();
|
||||
for (Class<? extends Item> itemType : itemTypes) {
|
||||
Item item = itemType.getDeclaredConstructor(String.class).newInstance("testItem");
|
||||
for (Class<? extends Type> type : types) {
|
||||
if (type.isEnum()) {
|
||||
arguments.add(Arguments.of(item, type.getEnumConstants()[0]));
|
||||
} else if (type == RawType.class) {
|
||||
arguments.add(Arguments.of(item, new RawType(new byte[] {}, "mimeType")));
|
||||
} else {
|
||||
arguments.add(Arguments.of(item, type.getDeclaredConstructor().newInstance()));
|
||||
}
|
||||
}
|
||||
}
|
||||
return arguments.stream();
|
||||
}
|
||||
|
||||
@Disabled
|
||||
@MethodSource("arguments")
|
||||
@ParameterizedTest
|
||||
public void testCommand(Item item, Type originalType) {
|
||||
Type returnType = null;
|
||||
|
||||
List<Class<? extends Command>> acceptedTypes = item.getAcceptedCommandTypes();
|
||||
if (acceptedTypes.contains(originalType.getClass())) {
|
||||
returnType = originalType;
|
||||
} else {
|
||||
// Look for class hierarchy and convert appropriately
|
||||
for (Class<? extends Type> typeClass : acceptedTypes) {
|
||||
if (!typeClass.isEnum() && typeClass.isAssignableFrom(originalType.getClass()) //
|
||||
&& State.class.isAssignableFrom(typeClass) && originalType instanceof State state) {
|
||||
returnType = state.as((Class<? extends State>) typeClass);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (returnType != null && !returnType.getClass().equals(originalType.getClass())) {
|
||||
fail("CommunicationManager did a conversion for target item " + item.getType() + " from "
|
||||
+ originalType.getClass() + " to " + returnType.getClass());
|
||||
}
|
||||
}
|
||||
|
||||
@MethodSource("arguments")
|
||||
@ParameterizedTest
|
||||
public void testState(Item item, Type originalType) {
|
||||
Type returnType = null;
|
||||
|
||||
List<Class<? extends State>> acceptedTypes = item.getAcceptedDataTypes();
|
||||
if (acceptedTypes.contains(originalType.getClass())) {
|
||||
returnType = originalType;
|
||||
} else {
|
||||
// Look for class hierarchy and convert appropriately
|
||||
for (Class<? extends Type> typeClass : acceptedTypes) {
|
||||
if (!typeClass.isEnum() && typeClass.isAssignableFrom(originalType.getClass()) //
|
||||
&& State.class.isAssignableFrom(typeClass) && originalType instanceof State state) {
|
||||
returnType = state.as((Class<? extends State>) typeClass);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (returnType != null && !returnType.equals(originalType)) {
|
||||
fail("CommunicationManager did a conversion for target item " + item.getType() + " from "
|
||||
+ originalType.getClass() + " to " + returnType.getClass());
|
||||
}
|
||||
}
|
||||
}
|
@ -17,7 +17,6 @@ import javax.measure.Unit;
|
||||
import javax.measure.spi.SystemOfUnits;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Provides {@link Unit}s and the current {@link SystemOfUnits}.
|
||||
@ -34,9 +33,10 @@ public interface UnitProvider {
|
||||
* @param dimension The {@link Quantity}, called dimension here, defines the base unit for the retrieved unit. E.g.
|
||||
* call {@code getUnit(javax.measure.quantity.Temperature.class)} to retrieve the temperature unit
|
||||
* according to the current {@link SystemOfUnits}.
|
||||
* @return The {@link Unit} matching the given {@link Quantity}, {@code null} otherwise.
|
||||
* @return The {@link Unit} matching the given {@link Quantity}
|
||||
* @throws IllegalArgumentException when the dimension is unknown
|
||||
*/
|
||||
<T extends Quantity<T>> @Nullable Unit<T> getUnit(@Nullable Class<T> dimension);
|
||||
<T extends Quantity<T>> Unit<T> getUnit(Class<T> dimension) throws IllegalArgumentException;
|
||||
|
||||
/**
|
||||
* Returns the {@link SystemOfUnits} which is currently set, must not be null.
|
||||
|
@ -142,12 +142,12 @@ public class I18nProviderImpl
|
||||
// UnitProvider
|
||||
static final String MEASUREMENT_SYSTEM = "measurementSystem";
|
||||
private @Nullable SystemOfUnits measurementSystem;
|
||||
private final Map<Class<? extends Quantity<?>>, Map<SystemOfUnits, Unit<? extends Quantity<?>>>> dimensionMap = new HashMap<>();
|
||||
private static final Map<Class<? extends Quantity<?>>, Map<SystemOfUnits, Unit<? extends Quantity<?>>>> DIMENSION_MAP = getDimensionMap();
|
||||
|
||||
@Activate
|
||||
@SuppressWarnings("unchecked")
|
||||
public I18nProviderImpl(ComponentContext componentContext) {
|
||||
initDimensionMap();
|
||||
getDimensionMap();
|
||||
modified((Map<String, Object>) componentContext.getProperties());
|
||||
|
||||
this.resourceBundleTracker = new ResourceBundleTracker(componentContext.getBundleContext(), this);
|
||||
@ -187,16 +187,12 @@ public class I18nProviderImpl
|
||||
|
||||
final SystemOfUnits newMeasurementSystem;
|
||||
switch (ms) {
|
||||
case SIUnits.MEASUREMENT_SYSTEM_NAME:
|
||||
newMeasurementSystem = SIUnits.getInstance();
|
||||
break;
|
||||
case ImperialUnits.MEASUREMENT_SYSTEM_NAME:
|
||||
newMeasurementSystem = ImperialUnits.getInstance();
|
||||
break;
|
||||
default:
|
||||
case SIUnits.MEASUREMENT_SYSTEM_NAME -> newMeasurementSystem = SIUnits.getInstance();
|
||||
case ImperialUnits.MEASUREMENT_SYSTEM_NAME -> newMeasurementSystem = ImperialUnits.getInstance();
|
||||
default -> {
|
||||
logger.debug("Error setting measurement system for value '{}'.", measurementSystem);
|
||||
newMeasurementSystem = null;
|
||||
break;
|
||||
}
|
||||
}
|
||||
this.measurementSystem = newMeasurementSystem;
|
||||
|
||||
@ -358,12 +354,14 @@ public class I18nProviderImpl
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T extends Quantity<T>> @Nullable Unit<T> getUnit(@Nullable Class<T> dimension) {
|
||||
Map<SystemOfUnits, Unit<? extends Quantity<?>>> map = dimensionMap.get(dimension);
|
||||
public <T extends Quantity<T>> Unit<T> getUnit(Class<T> dimension) {
|
||||
Map<SystemOfUnits, Unit<? extends Quantity<?>>> map = DIMENSION_MAP.get(dimension);
|
||||
if (map == null) {
|
||||
return null;
|
||||
throw new IllegalArgumentException("Dimension " + dimension.getName() + " is unknown. This is a bug.");
|
||||
}
|
||||
return (Unit<T>) map.get(getMeasurementSystem());
|
||||
Unit<T> unit = (Unit<T>) map.get(getMeasurementSystem());
|
||||
assert unit != null;
|
||||
return unit;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -380,54 +378,62 @@ public class I18nProviderImpl
|
||||
return SIUnits.getInstance();
|
||||
}
|
||||
|
||||
private void initDimensionMap() {
|
||||
addDefaultUnit(Acceleration.class, Units.METRE_PER_SQUARE_SECOND);
|
||||
addDefaultUnit(AmountOfSubstance.class, Units.MOLE);
|
||||
addDefaultUnit(Angle.class, Units.DEGREE_ANGLE, Units.DEGREE_ANGLE);
|
||||
addDefaultUnit(Area.class, SIUnits.SQUARE_METRE, ImperialUnits.SQUARE_FOOT);
|
||||
addDefaultUnit(ArealDensity.class, Units.DOBSON_UNIT);
|
||||
addDefaultUnit(CatalyticActivity.class, Units.KATAL);
|
||||
addDefaultUnit(DataAmount.class, Units.BYTE);
|
||||
addDefaultUnit(DataTransferRate.class, Units.MEGABIT_PER_SECOND);
|
||||
addDefaultUnit(Density.class, Units.KILOGRAM_PER_CUBICMETRE);
|
||||
addDefaultUnit(Dimensionless.class, Units.ONE);
|
||||
addDefaultUnit(ElectricCapacitance.class, Units.FARAD);
|
||||
addDefaultUnit(ElectricCharge.class, Units.COULOMB);
|
||||
addDefaultUnit(ElectricConductance.class, Units.SIEMENS);
|
||||
addDefaultUnit(ElectricConductivity.class, Units.SIEMENS_PER_METRE);
|
||||
addDefaultUnit(ElectricCurrent.class, Units.AMPERE);
|
||||
addDefaultUnit(ElectricInductance.class, Units.HENRY);
|
||||
addDefaultUnit(ElectricPotential.class, Units.VOLT);
|
||||
addDefaultUnit(ElectricResistance.class, Units.OHM);
|
||||
addDefaultUnit(Energy.class, Units.KILOWATT_HOUR);
|
||||
addDefaultUnit(Force.class, Units.NEWTON);
|
||||
addDefaultUnit(Frequency.class, Units.HERTZ);
|
||||
addDefaultUnit(Illuminance.class, Units.LUX);
|
||||
addDefaultUnit(Intensity.class, Units.IRRADIANCE);
|
||||
addDefaultUnit(Length.class, SIUnits.METRE, ImperialUnits.INCH);
|
||||
addDefaultUnit(LuminousFlux.class, Units.LUMEN);
|
||||
addDefaultUnit(LuminousIntensity.class, Units.CANDELA);
|
||||
addDefaultUnit(MagneticFlux.class, Units.WEBER);
|
||||
addDefaultUnit(MagneticFluxDensity.class, Units.TESLA);
|
||||
addDefaultUnit(Mass.class, SIUnits.KILOGRAM, ImperialUnits.POUND);
|
||||
addDefaultUnit(Power.class, Units.WATT);
|
||||
addDefaultUnit(Pressure.class, HECTO(SIUnits.PASCAL), ImperialUnits.INCH_OF_MERCURY);
|
||||
addDefaultUnit(RadiationDoseAbsorbed.class, Units.GRAY);
|
||||
addDefaultUnit(RadiationDoseEffective.class, Units.SIEVERT);
|
||||
addDefaultUnit(Radioactivity.class, Units.BECQUEREL);
|
||||
addDefaultUnit(SolidAngle.class, Units.STERADIAN);
|
||||
addDefaultUnit(Speed.class, SIUnits.KILOMETRE_PER_HOUR, ImperialUnits.MILES_PER_HOUR);
|
||||
addDefaultUnit(Temperature.class, SIUnits.CELSIUS, ImperialUnits.FAHRENHEIT);
|
||||
addDefaultUnit(Time.class, Units.SECOND);
|
||||
addDefaultUnit(Volume.class, SIUnits.CUBIC_METRE, ImperialUnits.GALLON_LIQUID_US);
|
||||
addDefaultUnit(VolumetricFlowRate.class, Units.LITRE_PER_MINUTE, ImperialUnits.GALLON_PER_MINUTE);
|
||||
public static Map<Class<? extends Quantity<?>>, Map<SystemOfUnits, Unit<? extends Quantity<?>>>> getDimensionMap() {
|
||||
Map<Class<? extends Quantity<?>>, Map<SystemOfUnits, Unit<? extends Quantity<?>>>> dimensionMap = new HashMap<>();
|
||||
|
||||
addDefaultUnit(dimensionMap, Acceleration.class, Units.METRE_PER_SQUARE_SECOND);
|
||||
addDefaultUnit(dimensionMap, AmountOfSubstance.class, Units.MOLE);
|
||||
addDefaultUnit(dimensionMap, Angle.class, Units.DEGREE_ANGLE, Units.DEGREE_ANGLE);
|
||||
addDefaultUnit(dimensionMap, Area.class, SIUnits.SQUARE_METRE, ImperialUnits.SQUARE_FOOT);
|
||||
addDefaultUnit(dimensionMap, ArealDensity.class, Units.DOBSON_UNIT);
|
||||
addDefaultUnit(dimensionMap, CatalyticActivity.class, Units.KATAL);
|
||||
addDefaultUnit(dimensionMap, DataAmount.class, Units.BYTE);
|
||||
addDefaultUnit(dimensionMap, DataTransferRate.class, Units.MEGABIT_PER_SECOND);
|
||||
addDefaultUnit(dimensionMap, Density.class, Units.KILOGRAM_PER_CUBICMETRE);
|
||||
addDefaultUnit(dimensionMap, Dimensionless.class, Units.ONE);
|
||||
addDefaultUnit(dimensionMap, ElectricCapacitance.class, Units.FARAD);
|
||||
addDefaultUnit(dimensionMap, ElectricCharge.class, Units.COULOMB);
|
||||
addDefaultUnit(dimensionMap, ElectricConductance.class, Units.SIEMENS);
|
||||
addDefaultUnit(dimensionMap, ElectricConductivity.class, Units.SIEMENS_PER_METRE);
|
||||
addDefaultUnit(dimensionMap, ElectricCurrent.class, Units.AMPERE);
|
||||
addDefaultUnit(dimensionMap, ElectricInductance.class, Units.HENRY);
|
||||
addDefaultUnit(dimensionMap, ElectricPotential.class, Units.VOLT);
|
||||
addDefaultUnit(dimensionMap, ElectricResistance.class, Units.OHM);
|
||||
addDefaultUnit(dimensionMap, Energy.class, Units.KILOWATT_HOUR);
|
||||
addDefaultUnit(dimensionMap, Force.class, Units.NEWTON);
|
||||
addDefaultUnit(dimensionMap, Frequency.class, Units.HERTZ);
|
||||
addDefaultUnit(dimensionMap, Illuminance.class, Units.LUX);
|
||||
addDefaultUnit(dimensionMap, Intensity.class, Units.IRRADIANCE);
|
||||
addDefaultUnit(dimensionMap, Length.class, SIUnits.METRE, ImperialUnits.INCH);
|
||||
addDefaultUnit(dimensionMap, LuminousFlux.class, Units.LUMEN);
|
||||
addDefaultUnit(dimensionMap, LuminousIntensity.class, Units.CANDELA);
|
||||
addDefaultUnit(dimensionMap, MagneticFlux.class, Units.WEBER);
|
||||
addDefaultUnit(dimensionMap, MagneticFluxDensity.class, Units.TESLA);
|
||||
addDefaultUnit(dimensionMap, Mass.class, SIUnits.KILOGRAM, ImperialUnits.POUND);
|
||||
addDefaultUnit(dimensionMap, Power.class, Units.WATT);
|
||||
addDefaultUnit(dimensionMap, Pressure.class, HECTO(SIUnits.PASCAL), ImperialUnits.INCH_OF_MERCURY);
|
||||
addDefaultUnit(dimensionMap, RadiationDoseAbsorbed.class, Units.GRAY);
|
||||
addDefaultUnit(dimensionMap, RadiationDoseEffective.class, Units.SIEVERT);
|
||||
addDefaultUnit(dimensionMap, Radioactivity.class, Units.BECQUEREL);
|
||||
addDefaultUnit(dimensionMap, SolidAngle.class, Units.STERADIAN);
|
||||
addDefaultUnit(dimensionMap, Speed.class, SIUnits.KILOMETRE_PER_HOUR, ImperialUnits.MILES_PER_HOUR);
|
||||
addDefaultUnit(dimensionMap, Temperature.class, SIUnits.CELSIUS, ImperialUnits.FAHRENHEIT);
|
||||
addDefaultUnit(dimensionMap, Time.class, Units.SECOND);
|
||||
addDefaultUnit(dimensionMap, Volume.class, SIUnits.CUBIC_METRE, ImperialUnits.GALLON_LIQUID_US);
|
||||
addDefaultUnit(dimensionMap, VolumetricFlowRate.class, Units.LITRE_PER_MINUTE, ImperialUnits.GALLON_PER_MINUTE);
|
||||
|
||||
return dimensionMap;
|
||||
}
|
||||
|
||||
private <T extends Quantity<T>> void addDefaultUnit(Class<T> dimension, Unit<T> siUnit, Unit<T> imperialUnit) {
|
||||
private static <T extends Quantity<T>> void addDefaultUnit(
|
||||
Map<Class<? extends Quantity<?>>, Map<SystemOfUnits, Unit<? extends Quantity<?>>>> dimensionMap,
|
||||
Class<T> dimension, Unit<T> siUnit, Unit<T> imperialUnit) {
|
||||
dimensionMap.put(dimension, Map.of(SIUnits.getInstance(), siUnit, ImperialUnits.getInstance(), imperialUnit));
|
||||
}
|
||||
|
||||
private <T extends Quantity<T>> void addDefaultUnit(Class<T> dimension, Unit<T> unit) {
|
||||
private static <T extends Quantity<T>> void addDefaultUnit(
|
||||
Map<Class<? extends Quantity<?>>, Map<SystemOfUnits, Unit<? extends Quantity<?>>>> dimensionMap,
|
||||
Class<T> dimension, Unit<T> unit) {
|
||||
dimensionMap.put(dimension, Map.of(SIUnits.getInstance(), unit, ImperialUnits.getInstance(), unit));
|
||||
}
|
||||
}
|
||||
|
@ -23,8 +23,8 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.common.registry.AbstractRegistry;
|
||||
import org.openhab.core.common.registry.Provider;
|
||||
import org.openhab.core.common.registry.RegistryChangeListener;
|
||||
import org.openhab.core.events.EventPublisher;
|
||||
import org.openhab.core.i18n.UnitProvider;
|
||||
import org.openhab.core.items.GenericItem;
|
||||
import org.openhab.core.items.GroupItem;
|
||||
import org.openhab.core.items.Item;
|
||||
@ -35,6 +35,8 @@ import org.openhab.core.items.ItemRegistry;
|
||||
import org.openhab.core.items.ItemStateConverter;
|
||||
import org.openhab.core.items.ItemUtil;
|
||||
import org.openhab.core.items.ManagedItemProvider;
|
||||
import org.openhab.core.items.Metadata;
|
||||
import org.openhab.core.items.MetadataAwareItem;
|
||||
import org.openhab.core.items.MetadataRegistry;
|
||||
import org.openhab.core.items.RegistryHook;
|
||||
import org.openhab.core.items.events.ItemEventFactory;
|
||||
@ -55,14 +57,15 @@ import org.slf4j.LoggerFactory;
|
||||
* This is the main implementing class of the {@link ItemRegistry} interface. It
|
||||
* keeps track of all declared items of all item providers and keeps their
|
||||
* current state in memory. This is the central point where states are kept and
|
||||
* thus it is a core part for all stateful services.
|
||||
* thus is a core part for all stateful services.
|
||||
*
|
||||
* @author Kai Kreuzer - Initial contribution
|
||||
* @author Stefan Bußweiler - Migration to new event mechanism
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@Component(immediate = true)
|
||||
public class ItemRegistryImpl extends AbstractRegistry<Item, String, ItemProvider> implements ItemRegistry {
|
||||
public class ItemRegistryImpl extends AbstractRegistry<Item, String, ItemProvider>
|
||||
implements ItemRegistry, RegistryChangeListener<Metadata> {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(ItemRegistryImpl.class);
|
||||
|
||||
@ -70,7 +73,7 @@ public class ItemRegistryImpl extends AbstractRegistry<Item, String, ItemProvide
|
||||
private @Nullable StateDescriptionService stateDescriptionService;
|
||||
private @Nullable CommandDescriptionService commandDescriptionService;
|
||||
private final MetadataRegistry metadataRegistry;
|
||||
private @Nullable UnitProvider unitProvider;
|
||||
|
||||
private @Nullable ItemStateConverter itemStateConverter;
|
||||
|
||||
@Activate
|
||||
@ -79,6 +82,19 @@ public class ItemRegistryImpl extends AbstractRegistry<Item, String, ItemProvide
|
||||
this.metadataRegistry = metadataRegistry;
|
||||
}
|
||||
|
||||
@Activate
|
||||
protected void activate(final ComponentContext componentContext) {
|
||||
super.activate(componentContext.getBundleContext());
|
||||
metadataRegistry.addRegistryChangeListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deactivate
|
||||
protected void deactivate() {
|
||||
metadataRegistry.removeRegistryChangeListener(this);
|
||||
super.deactivate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Item getItem(String name) throws ItemNotFoundException {
|
||||
final Item item = get(name);
|
||||
@ -101,13 +117,7 @@ public class ItemRegistryImpl extends AbstractRegistry<Item, String, ItemProvide
|
||||
throw new ItemNotUniqueException(name, items);
|
||||
}
|
||||
|
||||
Item item = items.iterator().next();
|
||||
|
||||
if (item == null) {
|
||||
throw new ItemNotFoundException(name);
|
||||
} else {
|
||||
return item;
|
||||
}
|
||||
return items.iterator().next();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -146,9 +156,8 @@ public class ItemRegistryImpl extends AbstractRegistry<Item, String, ItemProvide
|
||||
for (String groupName : groupItemNames) {
|
||||
if (groupName != null) {
|
||||
try {
|
||||
Item groupItem = getItem(groupName);
|
||||
if (groupItem instanceof GroupItem groupItem1) {
|
||||
groupItem1.addMember(item);
|
||||
if (getItem(groupName) instanceof GroupItem groupItem) {
|
||||
groupItem.addMember(item);
|
||||
}
|
||||
} catch (ItemNotFoundException e) {
|
||||
// the group might not yet be registered, let's ignore this
|
||||
@ -161,9 +170,8 @@ public class ItemRegistryImpl extends AbstractRegistry<Item, String, ItemProvide
|
||||
for (String groupName : groupItemNames) {
|
||||
if (groupName != null) {
|
||||
try {
|
||||
Item groupItem = getItem(groupName);
|
||||
if (groupItem instanceof GroupItem item) {
|
||||
item.replaceMember(oldItem, newItem);
|
||||
if (getItem(groupName) instanceof GroupItem groupItem) {
|
||||
groupItem.replaceMember(oldItem, newItem);
|
||||
}
|
||||
} catch (ItemNotFoundException e) {
|
||||
// the group might not yet be registered, let's ignore this
|
||||
@ -199,9 +207,12 @@ public class ItemRegistryImpl extends AbstractRegistry<Item, String, ItemProvide
|
||||
genericItem.setEventPublisher(getEventPublisher());
|
||||
genericItem.setStateDescriptionService(stateDescriptionService);
|
||||
genericItem.setCommandDescriptionService(commandDescriptionService);
|
||||
genericItem.setUnitProvider(unitProvider);
|
||||
genericItem.setItemStateConverter(itemStateConverter);
|
||||
}
|
||||
if (item instanceof MetadataAwareItem metadataAwareItem) {
|
||||
metadataRegistry.stream().filter(m -> m.getUID().getItemName().equals(item.getName()))
|
||||
.forEach(metadataAwareItem::addedMetadata);
|
||||
}
|
||||
}
|
||||
|
||||
private void addMembersToGroupItem(GroupItem groupItem) {
|
||||
@ -216,9 +227,8 @@ public class ItemRegistryImpl extends AbstractRegistry<Item, String, ItemProvide
|
||||
for (String groupName : groupItemNames) {
|
||||
if (groupName != null) {
|
||||
try {
|
||||
Item groupItem = getItem(groupName);
|
||||
if (groupItem instanceof GroupItem groupItem1) {
|
||||
groupItem1.removeMember(item);
|
||||
if (getItem(groupName) instanceof GroupItem groupItem) {
|
||||
groupItem.removeMember(item);
|
||||
}
|
||||
} catch (ItemNotFoundException e) {
|
||||
// the group might not yet be registered, let's ignore this
|
||||
@ -234,16 +244,16 @@ public class ItemRegistryImpl extends AbstractRegistry<Item, String, ItemProvide
|
||||
|
||||
@Override
|
||||
protected void onRemoveElement(Item element) {
|
||||
if (element instanceof GenericItem item) {
|
||||
item.dispose();
|
||||
if (element instanceof GenericItem genericItem) {
|
||||
genericItem.dispose();
|
||||
}
|
||||
removeFromGroupItems(element, element.getGroupNames());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void beforeUpdateElement(Item existingElement) {
|
||||
if (existingElement instanceof GenericItem item) {
|
||||
item.dispose();
|
||||
if (existingElement instanceof GenericItem genericItem) {
|
||||
genericItem.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@ -291,21 +301,6 @@ public class ItemRegistryImpl extends AbstractRegistry<Item, String, ItemProvide
|
||||
super.unsetReadyService(readyService);
|
||||
}
|
||||
|
||||
@Reference(cardinality = ReferenceCardinality.OPTIONAL, policy = ReferencePolicy.DYNAMIC)
|
||||
public void setUnitProvider(UnitProvider unitProvider) {
|
||||
this.unitProvider = unitProvider;
|
||||
for (Item item : getItems()) {
|
||||
((GenericItem) item).setUnitProvider(unitProvider);
|
||||
}
|
||||
}
|
||||
|
||||
public void unsetUnitProvider(UnitProvider unitProvider) {
|
||||
this.unitProvider = null;
|
||||
for (Item item : getItems()) {
|
||||
((GenericItem) item).setUnitProvider(null);
|
||||
}
|
||||
}
|
||||
|
||||
@Reference(cardinality = ReferenceCardinality.OPTIONAL, policy = ReferencePolicy.DYNAMIC)
|
||||
protected void setItemStateConverter(ItemStateConverter itemStateConverter) {
|
||||
this.itemStateConverter = itemStateConverter;
|
||||
@ -442,17 +437,6 @@ public class ItemRegistryImpl extends AbstractRegistry<Item, String, ItemProvide
|
||||
registryHooks.remove(hook);
|
||||
}
|
||||
|
||||
@Activate
|
||||
protected void activate(final ComponentContext componentContext) {
|
||||
super.activate(componentContext.getBundleContext());
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deactivate
|
||||
protected void deactivate() {
|
||||
super.deactivate();
|
||||
}
|
||||
|
||||
@Reference(cardinality = ReferenceCardinality.OPTIONAL, policy = ReferencePolicy.DYNAMIC)
|
||||
public void setStateDescriptionService(StateDescriptionService stateDescriptionService) {
|
||||
this.stateDescriptionService = stateDescriptionService;
|
||||
@ -495,4 +479,31 @@ public class ItemRegistryImpl extends AbstractRegistry<Item, String, ItemProvide
|
||||
protected void unsetManagedProvider(ManagedItemProvider provider) {
|
||||
super.unsetManagedProvider(provider);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void added(Metadata element) {
|
||||
String itemName = element.getUID().getItemName();
|
||||
Item item = get(itemName);
|
||||
if (item instanceof MetadataAwareItem metadataAwareItem) {
|
||||
metadataAwareItem.addedMetadata(element);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removed(Metadata element) {
|
||||
String itemName = element.getUID().getItemName();
|
||||
Item item = get(itemName);
|
||||
if (item instanceof MetadataAwareItem metadataAwareItem) {
|
||||
metadataAwareItem.removedMetadata(element);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updated(Metadata oldElement, Metadata element) {
|
||||
String itemName = element.getUID().getItemName();
|
||||
Item item = get(itemName);
|
||||
if (item instanceof MetadataAwareItem metadataAwareItem) {
|
||||
metadataAwareItem.updatedMetadata(oldElement, element);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -29,7 +29,6 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.common.ThreadPoolManager;
|
||||
import org.openhab.core.events.EventPublisher;
|
||||
import org.openhab.core.i18n.UnitProvider;
|
||||
import org.openhab.core.items.events.ItemEventFactory;
|
||||
import org.openhab.core.service.CommandDescriptionService;
|
||||
import org.openhab.core.service.StateDescriptionService;
|
||||
@ -83,8 +82,6 @@ public abstract class GenericItem implements ActiveItem {
|
||||
|
||||
private @Nullable CommandDescriptionService commandDescriptionService;
|
||||
|
||||
protected @Nullable UnitProvider unitProvider;
|
||||
|
||||
protected @Nullable ItemStateConverter itemStateConverter;
|
||||
|
||||
public GenericItem(String type, String name) {
|
||||
@ -176,7 +173,6 @@ public abstract class GenericItem implements ActiveItem {
|
||||
this.eventPublisher = null;
|
||||
this.stateDescriptionService = null;
|
||||
this.commandDescriptionService = null;
|
||||
this.unitProvider = null;
|
||||
this.itemStateConverter = null;
|
||||
}
|
||||
|
||||
@ -192,10 +188,6 @@ public abstract class GenericItem implements ActiveItem {
|
||||
this.commandDescriptionService = commandDescriptionService;
|
||||
}
|
||||
|
||||
public void setUnitProvider(@Nullable UnitProvider unitProvider) {
|
||||
this.unitProvider = unitProvider;
|
||||
}
|
||||
|
||||
public void setItemStateConverter(@Nullable ItemStateConverter itemStateConverter) {
|
||||
this.itemStateConverter = itemStateConverter;
|
||||
}
|
||||
|
@ -26,7 +26,6 @@ import java.util.stream.Collectors;
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.events.EventPublisher;
|
||||
import org.openhab.core.i18n.UnitProvider;
|
||||
import org.openhab.core.items.events.ItemEventFactory;
|
||||
import org.openhab.core.service.CommandDescriptionService;
|
||||
import org.openhab.core.service.StateDescriptionService;
|
||||
@ -40,7 +39,7 @@ import org.slf4j.LoggerFactory;
|
||||
* @author Kai Kreuzer - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class GroupItem extends GenericItem implements StateChangeListener {
|
||||
public class GroupItem extends GenericItem implements StateChangeListener, MetadataAwareItem {
|
||||
|
||||
public static final String TYPE = "Group";
|
||||
|
||||
@ -405,14 +404,6 @@ public class GroupItem extends GenericItem implements StateChangeListener {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUnitProvider(@Nullable UnitProvider unitProvider) {
|
||||
super.setUnitProvider(unitProvider);
|
||||
if (baseItem instanceof GenericItem item) {
|
||||
item.setUnitProvider(unitProvider);
|
||||
}
|
||||
}
|
||||
|
||||
private void sendGroupStateUpdatedEvent(String memberName, State state) {
|
||||
EventPublisher eventPublisher1 = this.eventPublisher;
|
||||
if (eventPublisher1 != null) {
|
||||
@ -457,4 +448,25 @@ public class GroupItem extends GenericItem implements StateChangeListener {
|
||||
private boolean hasOwnState(GroupItem item) {
|
||||
return item.getFunction() != null && item.getBaseItem() != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addedMetadata(Metadata metadata) {
|
||||
if (baseItem instanceof MetadataAwareItem metadataAwareItem) {
|
||||
metadataAwareItem.addedMetadata(metadata);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updatedMetadata(Metadata oldMetadata, Metadata newMetadata) {
|
||||
if (baseItem instanceof MetadataAwareItem metadataAwareItem) {
|
||||
metadataAwareItem.updatedMetadata(oldMetadata, newMetadata);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removedMetadata(Metadata metadata) {
|
||||
if (baseItem instanceof MetadataAwareItem metadataAwareItem) {
|
||||
metadataAwareItem.removedMetadata(metadata);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,48 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.items;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link MetadataAwareItem} is an interface that can be implemented by {@link Item}s that need to be notified of
|
||||
* metadata changes.
|
||||
*
|
||||
* @author Jan N. Klug - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface MetadataAwareItem {
|
||||
|
||||
/**
|
||||
* Can be implemented by subclasses to be informed about added metadata
|
||||
*
|
||||
* @param metadata the added {@link Metadata} object for this {@link Item}
|
||||
*/
|
||||
void addedMetadata(Metadata metadata);
|
||||
|
||||
/**
|
||||
* Can be implemented by subclasses to be informed about updated metadata
|
||||
*
|
||||
* @param oldMetadata the old {@link Metadata} object for this {@link Item}
|
||||
* @param newMetadata the new {@link Metadata} object for this {@link Item}
|
||||
*
|
||||
*/
|
||||
void updatedMetadata(Metadata oldMetadata, Metadata newMetadata);
|
||||
|
||||
/**
|
||||
* Can be implemented by subclasses to be informed about removed metadata
|
||||
*
|
||||
* @param metadata the removed {@link Metadata} object for this {@link Item}
|
||||
*/
|
||||
void removedMetadata(Metadata metadata);
|
||||
}
|
@ -14,6 +14,7 @@ package org.openhab.core.library;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.i18n.UnitProvider;
|
||||
import org.openhab.core.items.GenericItem;
|
||||
import org.openhab.core.items.ItemFactory;
|
||||
import org.openhab.core.items.ItemUtil;
|
||||
@ -29,7 +30,9 @@ import org.openhab.core.library.items.PlayerItem;
|
||||
import org.openhab.core.library.items.RollershutterItem;
|
||||
import org.openhab.core.library.items.StringItem;
|
||||
import org.openhab.core.library.items.SwitchItem;
|
||||
import org.osgi.service.component.annotations.Activate;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.osgi.service.component.annotations.Reference;
|
||||
|
||||
/**
|
||||
* {@link CoreItemFactory}-Implementation for the core ItemTypes
|
||||
@ -54,6 +57,12 @@ public class CoreItemFactory implements ItemFactory {
|
||||
public static final String ROLLERSHUTTER = "Rollershutter";
|
||||
public static final String STRING = "String";
|
||||
public static final String SWITCH = "Switch";
|
||||
private final UnitProvider unitProvider;
|
||||
|
||||
@Activate
|
||||
public CoreItemFactory(final @Reference UnitProvider unitProvider) {
|
||||
this.unitProvider = unitProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable GenericItem createItem(@Nullable String itemTypeName, String itemName) {
|
||||
@ -62,34 +71,21 @@ public class CoreItemFactory implements ItemFactory {
|
||||
}
|
||||
|
||||
String itemType = ItemUtil.getMainItemType(itemTypeName);
|
||||
switch (itemType) {
|
||||
case CALL:
|
||||
return new CallItem(itemName);
|
||||
case COLOR:
|
||||
return new ColorItem(itemName);
|
||||
case CONTACT:
|
||||
return new ContactItem(itemName);
|
||||
case DATETIME:
|
||||
return new DateTimeItem(itemName);
|
||||
case DIMMER:
|
||||
return new DimmerItem(itemName);
|
||||
case IMAGE:
|
||||
return new ImageItem(itemName);
|
||||
case LOCATION:
|
||||
return new LocationItem(itemName);
|
||||
case NUMBER:
|
||||
return new NumberItem(itemTypeName, itemName);
|
||||
case PLAYER:
|
||||
return new PlayerItem(itemName);
|
||||
case ROLLERSHUTTER:
|
||||
return new RollershutterItem(itemName);
|
||||
case STRING:
|
||||
return new StringItem(itemName);
|
||||
case SWITCH:
|
||||
return new SwitchItem(itemName);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
return switch (itemType) {
|
||||
case CALL -> new CallItem(itemName);
|
||||
case COLOR -> new ColorItem(itemName);
|
||||
case CONTACT -> new ContactItem(itemName);
|
||||
case DATETIME -> new DateTimeItem(itemName);
|
||||
case DIMMER -> new DimmerItem(itemName);
|
||||
case IMAGE -> new ImageItem(itemName);
|
||||
case LOCATION -> new LocationItem(itemName);
|
||||
case NUMBER -> new NumberItem(itemTypeName, itemName, unitProvider);
|
||||
case PLAYER -> new PlayerItem(itemName);
|
||||
case ROLLERSHUTTER -> new RollershutterItem(itemName);
|
||||
case STRING -> new StringItem(itemName);
|
||||
case SWITCH -> new SwitchItem(itemName);
|
||||
default -> null;
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -21,11 +21,15 @@ import javax.measure.Unit;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.i18n.UnitProvider;
|
||||
import org.openhab.core.items.GenericItem;
|
||||
import org.openhab.core.items.ItemUtil;
|
||||
import org.openhab.core.items.Metadata;
|
||||
import org.openhab.core.items.MetadataAwareItem;
|
||||
import org.openhab.core.library.CoreItemFactory;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.library.types.QuantityType;
|
||||
import org.openhab.core.library.unit.Units;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.RefreshType;
|
||||
import org.openhab.core.types.State;
|
||||
@ -33,6 +37,8 @@ import org.openhab.core.types.StateDescription;
|
||||
import org.openhab.core.types.StateDescriptionFragmentBuilder;
|
||||
import org.openhab.core.types.UnDefType;
|
||||
import org.openhab.core.types.util.UnitUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* A NumberItem has a decimal value and is usually used for all kinds
|
||||
@ -43,26 +49,40 @@ import org.openhab.core.types.util.UnitUtils;
|
||||
* @author Kai Kreuzer - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class NumberItem extends GenericItem {
|
||||
|
||||
public class NumberItem extends GenericItem implements MetadataAwareItem {
|
||||
public static final String UNIT_METADATA_NAMESPACE = "unit";
|
||||
private static final List<Class<? extends State>> ACCEPTED_DATA_TYPES = List.of(DecimalType.class,
|
||||
QuantityType.class, UnDefType.class);
|
||||
private static final List<Class<? extends Command>> ACCEPTED_COMMAND_TYPES = List.of(DecimalType.class,
|
||||
QuantityType.class, RefreshType.class);
|
||||
|
||||
@Nullable
|
||||
private Class<? extends Quantity<?>> dimension;
|
||||
private final Logger logger = LoggerFactory.getLogger(NumberItem.class);
|
||||
|
||||
private final @Nullable Class<? extends Quantity<?>> dimension;
|
||||
private Unit<?> unit = Units.ONE;
|
||||
private final @Nullable UnitProvider unitProvider;
|
||||
|
||||
public NumberItem(String name) {
|
||||
this(CoreItemFactory.NUMBER, name);
|
||||
this(CoreItemFactory.NUMBER, name, null);
|
||||
}
|
||||
|
||||
public NumberItem(String type, String name) {
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
public NumberItem(String type, String name, @Nullable UnitProvider unitProvider) {
|
||||
super(type, name);
|
||||
this.unitProvider = unitProvider;
|
||||
|
||||
String itemTypeExtension = ItemUtil.getItemTypeExtension(getType());
|
||||
if (itemTypeExtension != null) {
|
||||
dimension = UnitUtils.parseDimension(itemTypeExtension);
|
||||
if (dimension == null) {
|
||||
throw new IllegalArgumentException("The given dimension " + itemTypeExtension + " is unknown.");
|
||||
} else if (unitProvider == null) {
|
||||
throw new IllegalArgumentException("A unit provider is required for items with a dimension.");
|
||||
}
|
||||
this.unit = unitProvider.getUnit((Class<? extends Quantity>) dimension);
|
||||
logger.trace("Item '{}' now has unit '{}'", name, unit);
|
||||
} else {
|
||||
dimension = null;
|
||||
}
|
||||
}
|
||||
|
||||
@ -85,7 +105,12 @@ public class NumberItem extends GenericItem {
|
||||
DecimalType strippedCommand = new DecimalType(command.toBigDecimal());
|
||||
internalSend(strippedCommand);
|
||||
} else {
|
||||
internalSend(command);
|
||||
if (command.getUnit().isCompatible(unit) || command.getUnit().inverse().isCompatible(unit)) {
|
||||
internalSend(command);
|
||||
} else {
|
||||
logger.warn("Command '{}' to item '{}' was rejected because it is incompatible with the item unit '{}'",
|
||||
command, name, unit);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -114,41 +139,34 @@ public class NumberItem extends GenericItem {
|
||||
|
||||
@Override
|
||||
public void setState(State state) {
|
||||
// QuantityType update to a NumberItem without, strip unit
|
||||
if (state instanceof QuantityType quantityType && dimension == null) {
|
||||
DecimalType plainState = new DecimalType(quantityType.toBigDecimal());
|
||||
super.setState(plainState);
|
||||
return;
|
||||
}
|
||||
|
||||
// DecimalType update for a NumberItem with dimension, convert to QuantityType:
|
||||
if (state instanceof DecimalType decimalType && dimension != null) {
|
||||
Unit<?> unit = getUnit(dimension, false);
|
||||
if (unit != null) {
|
||||
super.setState(new QuantityType<>(decimalType.doubleValue(), unit));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// QuantityType update, check unit and convert if necessary:
|
||||
if (state instanceof QuantityType quantityType) {
|
||||
Unit<?> itemUnit = getUnit(dimension, true);
|
||||
Unit<?> stateUnit = quantityType.getUnit();
|
||||
if (itemUnit != null && (!stateUnit.getSystemUnit().equals(itemUnit.getSystemUnit())
|
||||
|| UnitUtils.isDifferentMeasurementSystem(itemUnit, stateUnit))) {
|
||||
QuantityType<?> convertedState = quantityType.toInvertibleUnit(itemUnit);
|
||||
if (state instanceof QuantityType<?> quantityType) {
|
||||
if (dimension == null) {
|
||||
// QuantityType update to a NumberItem without unit, strip unit
|
||||
DecimalType plainState = new DecimalType(quantityType.toBigDecimal());
|
||||
super.applyState(plainState);
|
||||
} else {
|
||||
// QuantityType update to a NumberItem with unit, convert to item unit (if possible)
|
||||
Unit<?> stateUnit = quantityType.getUnit();
|
||||
State convertedState = (stateUnit.isCompatible(unit) || stateUnit.inverse().isCompatible(unit))
|
||||
? quantityType.toInvertibleUnit(unit)
|
||||
: null;
|
||||
if (convertedState != null) {
|
||||
super.setState(convertedState);
|
||||
return;
|
||||
super.applyState(convertedState);
|
||||
} else {
|
||||
logger.warn("Failed to update item '{}' because '{}' could not be converted to the item unit '{}'",
|
||||
name, state, unit);
|
||||
}
|
||||
|
||||
// the state could not be converted to an accepted unit.
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (isAcceptedState(ACCEPTED_DATA_TYPES, state)) {
|
||||
super.setState(state);
|
||||
} else if (state instanceof DecimalType decimalType) {
|
||||
if (dimension == null) {
|
||||
// DecimalType update to NumberItem with unit
|
||||
super.applyState(decimalType);
|
||||
} else {
|
||||
// DecimalType update for a NumberItem with dimension, convert to QuantityType
|
||||
super.applyState(new QuantityType<>(decimalType.doubleValue(), unit));
|
||||
}
|
||||
} else if (state instanceof UnDefType) {
|
||||
super.applyState(state);
|
||||
} else {
|
||||
logSetTypeError(state);
|
||||
}
|
||||
@ -160,83 +178,54 @@ public class NumberItem extends GenericItem {
|
||||
* @return the optional unit symbol for this {@link NumberItem}.
|
||||
*/
|
||||
public @Nullable String getUnitSymbol() {
|
||||
Unit<?> unit = getUnit(dimension, true);
|
||||
return unit != null ? unit.toString() : null;
|
||||
return (dimension != null) ? unit.toString() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Derive the unit for this item by the following priority:
|
||||
* Get the unit for this item, either:
|
||||
*
|
||||
* <ul>
|
||||
* <li>the unit parsed from the state description</li>
|
||||
* <li>no unit if state description contains <code>%unit%</code></li>
|
||||
* <li>the default system unit from the item's dimension</li>
|
||||
* <li>the unit retrieved from the <code>unit</code> namespace in the item's metadata</li>
|
||||
* <li>the default system unit for the item's dimension</li>
|
||||
* </ul>
|
||||
*
|
||||
* @return the {@link Unit} for this item if available, {@code null} otherwise.
|
||||
*/
|
||||
public @Nullable Unit<? extends Quantity<?>> getUnit() {
|
||||
return getUnit(dimension, true);
|
||||
return (dimension != null) ? unit : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to convert a {@link DecimalType} into a new {@link QuantityType}. The unit for the new
|
||||
* type is derived either from the state description (which might also give a hint on items w/o dimension) or from
|
||||
* the system default unit of the given dimension.
|
||||
*
|
||||
* @param originalType the source {@link DecimalType}.
|
||||
* @param dimension the dimension to which the new {@link QuantityType} should adhere.
|
||||
* @return the new {@link QuantityType} from the given originalType, {@code null} if a unit could not be calculated.
|
||||
*/
|
||||
public @Nullable QuantityType<?> toQuantityType(DecimalType originalType,
|
||||
@Nullable Class<? extends Quantity<?>> dimension) {
|
||||
Unit<? extends Quantity<?>> itemUnit = getUnit(dimension, false);
|
||||
if (itemUnit != null) {
|
||||
return new QuantityType<>(originalType.toBigDecimal(), itemUnit);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Derive the unit for this item by the following priority:
|
||||
* <ul>
|
||||
* <li>the unit parsed from the state description</li>
|
||||
* <li>the unit from the value if <code>hasUnit = true</code> and state description has unit
|
||||
* <code>%unit%</code></li>
|
||||
* <li>the default system unit from the (optional) dimension parameter</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param dimension the (optional) dimension
|
||||
* @param hasUnit if the value has a unit
|
||||
* @return the {@link Unit} for this item if available, {@code null} otherwise.
|
||||
*/
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
private @Nullable Unit<? extends Quantity<?>> getUnit(@Nullable Class<? extends Quantity<?>> dimension,
|
||||
boolean hasUnit) {
|
||||
if (dimension == null) {
|
||||
// if it is a plain number without dimension, we do not have a unit.
|
||||
return null;
|
||||
}
|
||||
StateDescription stateDescription = getStateDescription();
|
||||
if (stateDescription != null) {
|
||||
String pattern = stateDescription.getPattern();
|
||||
if (pattern != null) {
|
||||
if (hasUnit && pattern.contains(UnitUtils.UNIT_PLACEHOLDER)) {
|
||||
// use provided unit if present
|
||||
return null;
|
||||
}
|
||||
Unit<?> stateDescriptionUnit = UnitUtils.parseUnit(pattern);
|
||||
if (stateDescriptionUnit != null) {
|
||||
return stateDescriptionUnit;
|
||||
}
|
||||
@Override
|
||||
public void addedMetadata(Metadata metadata) {
|
||||
if (dimension != null && UNIT_METADATA_NAMESPACE.equals(metadata.getUID().getNamespace())) {
|
||||
Unit<?> unit = UnitUtils.parseUnit(metadata.getValue());
|
||||
if (unit == null) {
|
||||
logger.warn("Unit '{}' could not be parsed to a known unit. Keeping old unit '{}' for item '{}'.",
|
||||
metadata.getValue(), this.unit, name);
|
||||
return;
|
||||
}
|
||||
if (!unit.isCompatible(this.unit) && !unit.inverse().isCompatible(this.unit)) {
|
||||
logger.warn("Unit '{}' could not be parsed to a known unit. Keeping old unit '{}' for item '{}'.",
|
||||
metadata.getValue(), this.unit, name);
|
||||
return;
|
||||
}
|
||||
this.unit = unit;
|
||||
logger.trace("Item '{}' now has unit '{}'", name, unit);
|
||||
}
|
||||
}
|
||||
|
||||
if (unitProvider != null) {
|
||||
// explicit cast to Class<? extends Quantity> as JDK compiler complains
|
||||
return unitProvider.getUnit((Class<? extends Quantity>) dimension);
|
||||
@Override
|
||||
public void updatedMetadata(Metadata oldMetadata, Metadata newMetadata) {
|
||||
addedMetadata(newMetadata);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
public void removedMetadata(Metadata metadata) {
|
||||
if (dimension != null && UNIT_METADATA_NAMESPACE.equals(metadata.getUID().getNamespace())) {
|
||||
assert unitProvider != null;
|
||||
unit = unitProvider.getUnit((Class<? extends Quantity>) dimension);
|
||||
logger.trace("Item '{}' now has unit '{}'", name, unit);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
@ -298,7 +298,9 @@ public class QuantityType<T extends Quantity<T>> extends Number
|
||||
* @return the new {@link QuantityType} in the given {@link Unit} or {@code null} in case of an erro.
|
||||
*/
|
||||
public @Nullable QuantityType<?> toInvertibleUnit(Unit<?> targetUnit) {
|
||||
if (!targetUnit.equals(getUnit()) && getUnit().inverse().isCompatible(targetUnit)) {
|
||||
// only invert if unit is not equal and inverse is compatible and targetUnit is not ONE
|
||||
if (!targetUnit.equals(getUnit()) && !targetUnit.isCompatible(AbstractUnit.ONE)
|
||||
&& getUnit().inverse().isCompatible(targetUnit)) {
|
||||
return inverse().toUnit(targetUnit);
|
||||
}
|
||||
return toUnit(targetUnit);
|
||||
|
@ -76,8 +76,8 @@ public class UnitUtils {
|
||||
* @return the {@link Class} instance of the interface or {@code null} if the given dimension is blank.
|
||||
* @throws IllegalArgumentException in case no class instance could be parsed from the given dimension.
|
||||
*/
|
||||
public static @Nullable Class<? extends Quantity<?>> parseDimension(String dimension) {
|
||||
if (dimension.isBlank()) {
|
||||
public static @Nullable Class<? extends Quantity<?>> parseDimension(@Nullable String dimension) {
|
||||
if (dimension == null || dimension.isBlank()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -149,7 +149,7 @@ public class UnitUtils {
|
||||
* label). In the latter case, the unit is expected to be the last part of the pattern separated by " " (e.g. "%.2f
|
||||
* °C" for °C).
|
||||
*
|
||||
* @param stringWithUnit the string to extract the unit symbol from
|
||||
* @param pattern the string to extract the unit symbol from
|
||||
* @return the unit symbol extracted from the string or {@code null} if no unit could be parsed
|
||||
*
|
||||
*/
|
||||
@ -173,7 +173,7 @@ public class UnitUtils {
|
||||
return quantity.getUnit();
|
||||
} catch (IllegalArgumentException | MeasurementParseException e) {
|
||||
// we expect this exception in case the extracted string does not match any known unit
|
||||
LOGGER.debug("Unknown unit from pattern: {}", unitSymbol);
|
||||
LOGGER.error("Unknown unit from pattern: {}", unitSymbol);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,48 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.i18n;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import javax.measure.Quantity;
|
||||
import javax.measure.Unit;
|
||||
import javax.measure.spi.SystemOfUnits;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.i18n.UnitProvider;
|
||||
import org.openhab.core.library.unit.SIUnits;
|
||||
|
||||
/**
|
||||
* The {@link TestUnitProvider} implements a {@link UnitProvider} for testing purposes
|
||||
*
|
||||
* @author Jan N. Klug - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class TestUnitProvider implements UnitProvider {
|
||||
|
||||
private final Map<Class<? extends Quantity<?>>, Map<SystemOfUnits, Unit<? extends Quantity<?>>>> dimensionMap = I18nProviderImpl
|
||||
.getDimensionMap();
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T extends Quantity<T>> Unit<T> getUnit(Class<T> dimension) {
|
||||
Unit<T> unit = (Unit<T>) dimensionMap.getOrDefault(dimension, Map.of()).get(SIUnits.getInstance());
|
||||
assert unit != null;
|
||||
return unit;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SystemOfUnits getMeasurementSystem() {
|
||||
return SIUnits.getInstance();
|
||||
}
|
||||
}
|
@ -30,6 +30,7 @@ import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.openhab.core.events.Event;
|
||||
import org.openhab.core.events.EventPublisher;
|
||||
import org.openhab.core.i18n.UnitProvider;
|
||||
import org.openhab.core.internal.items.ExpireManager.ExpireConfig;
|
||||
import org.openhab.core.items.Item;
|
||||
import org.openhab.core.items.ItemNotFoundException;
|
||||
@ -47,6 +48,8 @@ import org.openhab.core.library.types.QuantityType;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
import org.openhab.core.types.UnDefType;
|
||||
|
||||
import tech.units.indriya.unit.Units;
|
||||
|
||||
/**
|
||||
* The {@link ExpireManagerTest} tests the {@link ExpireManager}.
|
||||
*
|
||||
@ -341,7 +344,9 @@ class ExpireManagerTest {
|
||||
// expected as state is invalid
|
||||
}
|
||||
|
||||
testItem = new NumberItem("Number:Temperature", ITEMNAME);
|
||||
UnitProvider unitProviderMock = mock(UnitProvider.class);
|
||||
when(unitProviderMock.getUnit(Temperature.class)).thenReturn(Units.CELSIUS);
|
||||
testItem = new NumberItem("Number:Temperature", ITEMNAME, unitProviderMock);
|
||||
cfg = new ExpireManager.ExpireConfig(testItem, "1h,15 °C", Map.of());
|
||||
assertEquals(Duration.ofHours(1), cfg.duration);
|
||||
assertEquals(new QuantityType<Temperature>("15 °C"), cfg.expireState);
|
||||
|
@ -26,7 +26,6 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.openhab.core.events.EventPublisher;
|
||||
import org.openhab.core.i18n.UnitProvider;
|
||||
import org.openhab.core.items.events.ItemEvent;
|
||||
import org.openhab.core.items.events.ItemStateChangedEvent;
|
||||
import org.openhab.core.items.events.ItemStateUpdatedEvent;
|
||||
@ -160,7 +159,6 @@ public class GenericItemTest {
|
||||
item.setEventPublisher(mock(EventPublisher.class));
|
||||
item.setItemStateConverter(mock(ItemStateConverter.class));
|
||||
item.setStateDescriptionService(null);
|
||||
item.setUnitProvider(mock(UnitProvider.class));
|
||||
|
||||
item.addStateChangeListener(mock(StateChangeListener.class));
|
||||
|
||||
@ -170,7 +168,6 @@ public class GenericItemTest {
|
||||
assertNull(item.itemStateConverter);
|
||||
// can not be tested as stateDescriptionProviders is private in GenericItem
|
||||
// assertThat(item.stateDescriptionProviders, is(nullValue()));
|
||||
assertNull(item.unitProvider);
|
||||
assertEquals(0, item.listeners.size());
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,56 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.items;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.mockito.junit.jupiter.MockitoSettings;
|
||||
import org.mockito.quality.Strictness;
|
||||
import org.openhab.core.library.items.NumberItem;
|
||||
|
||||
/**
|
||||
* The {@link GroupItemTest} contains tests for {@link GroupItem}
|
||||
*
|
||||
* @author Jan N. Klug - Initial contribution
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
@MockitoSettings(strictness = Strictness.LENIENT)
|
||||
@NonNullByDefault
|
||||
public class GroupItemTest {
|
||||
private static final String ITEM_NAME = "test";
|
||||
|
||||
private @Mock @NonNullByDefault({}) NumberItem baseItemMock;
|
||||
|
||||
@Test
|
||||
public void testMetadataIsPropagatedToBaseItem() {
|
||||
GroupItem groupItem = new GroupItem(ITEM_NAME, baseItemMock, new GroupFunction.Equality());
|
||||
|
||||
Metadata metadata = new Metadata(new MetadataKey("foo", ITEM_NAME), "foo", null);
|
||||
Metadata updatedMetadata = new Metadata(new MetadataKey("foo", ITEM_NAME), "bar", null);
|
||||
|
||||
groupItem.addedMetadata(metadata);
|
||||
verify(baseItemMock).addedMetadata(eq(metadata));
|
||||
|
||||
groupItem.updatedMetadata(metadata, updatedMetadata);
|
||||
verify(baseItemMock).updatedMetadata(eq(metadata), eq(updatedMetadata));
|
||||
|
||||
groupItem.removedMetadata(updatedMetadata);
|
||||
verify(baseItemMock).removedMetadata(eq(updatedMetadata));
|
||||
}
|
||||
}
|
@ -15,6 +15,7 @@ package org.openhab.core.library;
|
||||
import static org.hamcrest.CoreMatchers.*;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.core.IsEqual.equalTo;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@ -22,18 +23,30 @@ import javax.measure.quantity.Temperature;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.mockito.junit.jupiter.MockitoSettings;
|
||||
import org.mockito.quality.Strictness;
|
||||
import org.openhab.core.i18n.UnitProvider;
|
||||
import org.openhab.core.items.GenericItem;
|
||||
import org.openhab.core.library.items.NumberItem;
|
||||
|
||||
import tech.units.indriya.unit.Units;
|
||||
|
||||
/**
|
||||
* @author Henning Treu - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
@MockitoSettings(strictness = Strictness.LENIENT)
|
||||
public class CoreItemFactoryTest {
|
||||
|
||||
private @Mock @NonNullByDefault({}) UnitProvider unitProviderMock;
|
||||
|
||||
@Test
|
||||
public void shouldCreateItems() {
|
||||
CoreItemFactory coreItemFactory = new CoreItemFactory();
|
||||
CoreItemFactory coreItemFactory = new CoreItemFactory(unitProviderMock);
|
||||
List<String> itemTypeNames = List.of(coreItemFactory.getSupportedItemTypes());
|
||||
for (String itemTypeName : itemTypeNames) {
|
||||
GenericItem item = coreItemFactory.createItem(itemTypeName, itemTypeName.toLowerCase());
|
||||
@ -45,7 +58,8 @@ public class CoreItemFactoryTest {
|
||||
|
||||
@Test
|
||||
public void createNumberItemWithDimension() {
|
||||
CoreItemFactory coreItemFactory = new CoreItemFactory();
|
||||
CoreItemFactory coreItemFactory = new CoreItemFactory(unitProviderMock);
|
||||
when(unitProviderMock.getUnit(Temperature.class)).thenReturn(Units.CELSIUS);
|
||||
NumberItem numberItem = (NumberItem) coreItemFactory.createItem(CoreItemFactory.NUMBER + ":Temperature",
|
||||
"myNumberItem");
|
||||
|
||||
@ -54,7 +68,7 @@ public class CoreItemFactoryTest {
|
||||
|
||||
@Test
|
||||
public void shouldReturnNullForUnsupportedItemTypeName() {
|
||||
CoreItemFactory coreItemFactory = new CoreItemFactory();
|
||||
CoreItemFactory coreItemFactory = new CoreItemFactory(unitProviderMock);
|
||||
GenericItem item = coreItemFactory.createItem("NoValidItemTypeName", "IWantMyItem");
|
||||
|
||||
assertThat(item, is(nullValue()));
|
||||
|
@ -14,18 +14,15 @@ package org.openhab.core.library.items;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.*;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.hasSize;
|
||||
import static org.hamcrest.Matchers.instanceOf;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import javax.measure.quantity.Energy;
|
||||
import javax.measure.Unit;
|
||||
import javax.measure.quantity.Mass;
|
||||
import javax.measure.quantity.Temperature;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.hamcrest.Matchers;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
@ -34,16 +31,17 @@ import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.mockito.junit.jupiter.MockitoSettings;
|
||||
import org.mockito.quality.Strictness;
|
||||
import org.openhab.core.events.Event;
|
||||
import org.openhab.core.events.EventPublisher;
|
||||
import org.openhab.core.i18n.UnitProvider;
|
||||
import org.openhab.core.internal.i18n.TestUnitProvider;
|
||||
import org.openhab.core.items.Metadata;
|
||||
import org.openhab.core.items.MetadataKey;
|
||||
import org.openhab.core.items.events.ItemCommandEvent;
|
||||
import org.openhab.core.items.events.ItemStateUpdatedEvent;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.library.types.HSBType;
|
||||
import org.openhab.core.library.types.PercentType;
|
||||
import org.openhab.core.library.types.QuantityType;
|
||||
import org.openhab.core.library.unit.ImperialUnits;
|
||||
import org.openhab.core.library.unit.MetricPrefix;
|
||||
import org.openhab.core.library.unit.SIUnits;
|
||||
import org.openhab.core.library.unit.Units;
|
||||
import org.openhab.core.service.StateDescriptionService;
|
||||
@ -67,7 +65,10 @@ public class NumberItemTest {
|
||||
private @Mock @NonNullByDefault({}) UnitProvider unitProviderMock;
|
||||
private @Mock @NonNullByDefault({}) EventPublisher eventPublisherMock;
|
||||
|
||||
private final UnitProvider unitProvider = new TestUnitProvider();
|
||||
|
||||
@BeforeEach
|
||||
@SuppressWarnings("unchecked")
|
||||
public void setup() {
|
||||
when(stateDescriptionServiceMock.getStateDescription(ITEM_NAME, null))
|
||||
.thenReturn(StateDescriptionFragmentBuilder.create().withPattern("%.1f " + UnitUtils.UNIT_PLACEHOLDER)
|
||||
@ -75,30 +76,9 @@ public class NumberItemTest {
|
||||
when(unitProviderMock.getUnit(Temperature.class)).thenReturn(SIUnits.CELSIUS);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setDecimalType() {
|
||||
NumberItem item = new NumberItem(ITEM_NAME);
|
||||
State decimal = new DecimalType("23");
|
||||
item.setState(decimal);
|
||||
assertEquals(decimal, item.getState());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setPercentType() {
|
||||
NumberItem item = new NumberItem(ITEM_NAME);
|
||||
State percent = new PercentType(50);
|
||||
item.setState(percent);
|
||||
assertEquals(percent, item.getState());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setHSBType() {
|
||||
NumberItem item = new NumberItem(ITEM_NAME);
|
||||
State hsb = new HSBType("5,23,42");
|
||||
item.setState(hsb);
|
||||
assertEquals(hsb, item.getState());
|
||||
}
|
||||
|
||||
/*
|
||||
* State handling
|
||||
*/
|
||||
@Test
|
||||
public void testUndefType() {
|
||||
NumberItem item = new NumberItem(ITEM_NAME);
|
||||
@ -112,46 +92,76 @@ public class NumberItemTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetQuantityTypeAccepted() {
|
||||
NumberItem item = new NumberItem("Number:Temperature", ITEM_NAME);
|
||||
item.setState(new QuantityType<>("20 °C"));
|
||||
|
||||
assertThat(item.getState(), is(new QuantityType<>("20 °C")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetQuantityOnPlainNumberStripsUnit() {
|
||||
public void testSetDecimalTypeToPlainItem() {
|
||||
NumberItem item = new NumberItem(ITEM_NAME);
|
||||
item.setState(new QuantityType<>("20 °C"));
|
||||
|
||||
assertThat(item.getState(), is(new DecimalType("20")));
|
||||
State decimal = new DecimalType("23");
|
||||
item.setState(decimal);
|
||||
assertThat(item.getState(), is(decimal));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetQuantityTypeConverted() {
|
||||
NumberItem item = new NumberItem("Number:Temperature", ITEM_NAME);
|
||||
item.setState(new QuantityType<>(68, ImperialUnits.FAHRENHEIT));
|
||||
|
||||
assertThat(item.getState(), is(new QuantityType<>("20 °C")));
|
||||
public void testSetDecimalTypeToDimensionItem() {
|
||||
NumberItem item = new NumberItem("Number:Temperature", ITEM_NAME, unitProvider);
|
||||
State decimal = new DecimalType("23");
|
||||
item.setState(decimal);
|
||||
assertThat(item.getState(), is(new QuantityType<>("23 °C")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetQuantityTypeUnconverted() {
|
||||
NumberItem item = new NumberItem("Number:Temperature", ITEM_NAME);
|
||||
UnitProvider unitProvider = mock(UnitProvider.class);
|
||||
when(unitProvider.getUnit(Temperature.class)).thenReturn(SIUnits.CELSIUS);
|
||||
item.setUnitProvider(unitProvider);
|
||||
item.setState(new QuantityType<>("10 A")); // should not be accepted as valid state
|
||||
public void testSetQuantityTypeToPlainItem() {
|
||||
NumberItem item = new NumberItem(ITEM_NAME);
|
||||
State quantity = new QuantityType<>("23 °C");
|
||||
item.setState(quantity);
|
||||
assertThat(item.getState(), is(new DecimalType("23")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetValidQuantityTypeWithSameUnitToDimensionItem() {
|
||||
NumberItem item = new NumberItem("Number:Temperature", ITEM_NAME, unitProvider);
|
||||
State quantity = new QuantityType<>("23 °C");
|
||||
item.setState(quantity);
|
||||
assertThat(item.getState(), is(quantity));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetValidQuantityTypeWithDifferentUnitToDimensionItem() {
|
||||
NumberItem item = new NumberItem("Number:Temperature", ITEM_NAME, unitProvider);
|
||||
QuantityType<?> quantity = new QuantityType<>("23 K");
|
||||
item.setState(quantity);
|
||||
assertThat(item.getState(),
|
||||
is(quantity.toUnit(Objects.requireNonNull(unitProvider.getUnit(Temperature.class)))));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetInvalidQuantityTypeToDimensionItemIsRejected() {
|
||||
NumberItem item = new NumberItem("Number:Temperature", ITEM_NAME, unitProvider);
|
||||
QuantityType<?> quantity = new QuantityType<>("23 N");
|
||||
item.setState(quantity);
|
||||
assertThat(item.getState(), is(UnDefType.NULL));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCommandUnitIsPassedForDimensionItem() {
|
||||
NumberItem item = new NumberItem("Number:Temperature", ITEM_NAME);
|
||||
UnitProvider unitProvider = mock(UnitProvider.class);
|
||||
when(unitProvider.getUnit(Temperature.class)).thenReturn(SIUnits.CELSIUS);
|
||||
item.setUnitProvider(unitProvider);
|
||||
public void testSetPercentType() {
|
||||
NumberItem item = new NumberItem(ITEM_NAME);
|
||||
State percent = new PercentType(50);
|
||||
item.setState(percent);
|
||||
assertThat(item.getState(), is(percent));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetHSBType() {
|
||||
NumberItem item = new NumberItem(ITEM_NAME);
|
||||
State hsb = new HSBType("5,23,42");
|
||||
item.setState(hsb);
|
||||
assertThat(item.getState(), is(hsb));
|
||||
}
|
||||
|
||||
/*
|
||||
* Command handling
|
||||
*/
|
||||
@Test
|
||||
public void testValidCommandUnitIsPassedForDimensionItem() {
|
||||
NumberItem item = new NumberItem("Number:Temperature", ITEM_NAME, unitProvider);
|
||||
EventPublisher eventPublisher = mock(EventPublisher.class);
|
||||
item.setEventPublisher(eventPublisher);
|
||||
|
||||
@ -165,9 +175,37 @@ public class NumberItemTest {
|
||||
assertThat(event.getItemCommand(), is(command));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidCommandDifferentUnitIsPassedForDimensionItem() {
|
||||
NumberItem item = new NumberItem("Number:Temperature", ITEM_NAME, unitProvider);
|
||||
EventPublisher eventPublisher = mock(EventPublisher.class);
|
||||
item.setEventPublisher(eventPublisher);
|
||||
|
||||
QuantityType<?> command = new QuantityType<>("15 K");
|
||||
item.send(command);
|
||||
|
||||
ArgumentCaptor<ItemCommandEvent> captor = ArgumentCaptor.forClass(ItemCommandEvent.class);
|
||||
verify(eventPublisher).post(captor.capture());
|
||||
|
||||
ItemCommandEvent event = captor.getValue();
|
||||
assertThat(event.getItemCommand(), is(command));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInvalidCommandUnitIsRejectedForDimensionItem() {
|
||||
NumberItem item = new NumberItem("Number:Temperature", ITEM_NAME, unitProvider);
|
||||
EventPublisher eventPublisher = mock(EventPublisher.class);
|
||||
item.setEventPublisher(eventPublisher);
|
||||
|
||||
QuantityType<?> command = new QuantityType<>("15 N");
|
||||
item.send(command);
|
||||
|
||||
verify(eventPublisher, never()).post(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCommandUnitIsStrippedForDimensionlessItem() {
|
||||
NumberItem item = new NumberItem("Number", ITEM_NAME);
|
||||
NumberItem item = new NumberItem(ITEM_NAME);
|
||||
EventPublisher eventPublisher = mock(EventPublisher.class);
|
||||
item.setEventPublisher(eventPublisher);
|
||||
|
||||
@ -180,10 +218,13 @@ public class NumberItemTest {
|
||||
assertThat(event.getItemCommand(), is(new DecimalType("15")));
|
||||
}
|
||||
|
||||
/*
|
||||
* + State description handling
|
||||
*/
|
||||
@SuppressWarnings("null")
|
||||
@Test
|
||||
public void testStripUnitPlaceholderFromPlainNumberItem() {
|
||||
NumberItem item = new NumberItem("Number", ITEM_NAME);
|
||||
public void testStripUnitPlaceholderInStateDescriptionFromPlainNumberItem() {
|
||||
NumberItem item = new NumberItem(ITEM_NAME);
|
||||
item.setStateDescriptionService(stateDescriptionServiceMock);
|
||||
|
||||
assertThat(item.getStateDescription().getPattern(), is("%.1f"));
|
||||
@ -191,20 +232,54 @@ public class NumberItemTest {
|
||||
|
||||
@SuppressWarnings("null")
|
||||
@Test
|
||||
public void testLeaveUnitPlaceholderOnDimensionNumberItem() {
|
||||
NumberItem item = new NumberItem("Number:Temperature", ITEM_NAME);
|
||||
public void testLeaveUnitPlaceholderInStateDescriptionOnDimensionNumberItem() {
|
||||
NumberItem item = new NumberItem("Number:Temperature", ITEM_NAME, unitProvider);
|
||||
item.setStateDescriptionService(stateDescriptionServiceMock);
|
||||
|
||||
assertThat(item.getStateDescription().getPattern(), is("%.1f " + UnitUtils.UNIT_PLACEHOLDER));
|
||||
}
|
||||
|
||||
/*
|
||||
* Unit / metadata handling
|
||||
*/
|
||||
@Test
|
||||
void testSystemDefaultUnitIsUsedWithoutMetadata() {
|
||||
final NumberItem item = new NumberItem("Number:Mass", ITEM_NAME, unitProvider);
|
||||
assertThat(item.getUnit(), is(unitProvider.getUnit(Mass.class)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testMetadataUnitLifecycleIsObserved() {
|
||||
final NumberItem item = new NumberItem("Number:Mass", ITEM_NAME, unitProvider);
|
||||
|
||||
Metadata initialMetadata = getUnitMetadata(MetricPrefix.MEGA(SIUnits.GRAM));
|
||||
item.addedMetadata(initialMetadata);
|
||||
assertThat(item.getUnit(), is(MetricPrefix.MEGA(SIUnits.GRAM)));
|
||||
|
||||
Metadata updatedMetadata = getUnitMetadata(MetricPrefix.MILLI(SIUnits.GRAM));
|
||||
item.updatedMetadata(initialMetadata, updatedMetadata);
|
||||
assertThat(item.getUnit(), is(MetricPrefix.MILLI(SIUnits.GRAM)));
|
||||
|
||||
item.removedMetadata(updatedMetadata);
|
||||
assertThat(item.getUnit(), is(unitProvider.getUnit(Mass.class)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testInvalidMetadataUnitIsRejected() {
|
||||
final NumberItem item = new NumberItem("Number:Temperature", ITEM_NAME, unitProvider);
|
||||
item.addedMetadata(getUnitMetadata(MetricPrefix.MEGA(SIUnits.GRAM)));
|
||||
assertThat(item.getUnit(), is(unitProvider.getUnit(Temperature.class)));
|
||||
}
|
||||
|
||||
/*
|
||||
* Other tests
|
||||
*/
|
||||
|
||||
@SuppressWarnings("null")
|
||||
@Test
|
||||
public void testMiredToKelvin() {
|
||||
NumberItem item = new NumberItem("Number:Temperature", ITEM_NAME);
|
||||
when(stateDescriptionServiceMock.getStateDescription(ITEM_NAME, null)).thenReturn(
|
||||
StateDescriptionFragmentBuilder.create().withPattern("%.0f K").build().toStateDescription());
|
||||
item.setStateDescriptionService(stateDescriptionServiceMock);
|
||||
NumberItem item = new NumberItem("Number:Temperature", ITEM_NAME, unitProvider);
|
||||
item.addedMetadata(getUnitMetadata(Units.KELVIN));
|
||||
item.setState(new QuantityType<>("370 mired"));
|
||||
|
||||
assertThat(item.getState().format("%.0f K"), is("2703 K"));
|
||||
@ -213,130 +288,16 @@ public class NumberItemTest {
|
||||
@SuppressWarnings("null")
|
||||
@Test
|
||||
public void testKelvinToMired() {
|
||||
NumberItem item = new NumberItem("Number:Temperature", ITEM_NAME);
|
||||
when(stateDescriptionServiceMock.getStateDescription(ITEM_NAME, null)).thenReturn(
|
||||
StateDescriptionFragmentBuilder.create().withPattern("%.0f mired").build().toStateDescription());
|
||||
item.setStateDescriptionService(stateDescriptionServiceMock);
|
||||
NumberItem item = new NumberItem("Number:Temperature", ITEM_NAME, unitProvider);
|
||||
item.addedMetadata(getUnitMetadata(Units.MIRED));
|
||||
|
||||
item.setState(new QuantityType<>("2700 K"));
|
||||
|
||||
assertThat(item.getState().format("%.0f mired"), is("370 mired"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testStateDescriptionUnitUsedWhenStateDescriptionPresent() {
|
||||
UnitProvider unitProviderMock = mock(UnitProvider.class);
|
||||
when(unitProviderMock.getUnit(Temperature.class)).thenReturn(SIUnits.CELSIUS);
|
||||
when(stateDescriptionServiceMock.getStateDescription(ITEM_NAME, null)).thenReturn(
|
||||
StateDescriptionFragmentBuilder.create().withPattern("%.0f °F").build().toStateDescription());
|
||||
|
||||
NumberItem item = new NumberItem("Number:Temperature", ITEM_NAME);
|
||||
item.setStateDescriptionService(stateDescriptionServiceMock);
|
||||
item.setUnitProvider(unitProviderMock);
|
||||
|
||||
assertThat(item.getUnit(), is(ImperialUnits.FAHRENHEIT));
|
||||
|
||||
item.setState(new QuantityType<>("429 °F"));
|
||||
assertThat(item.getState(), is(new QuantityType<>("429 °F")));
|
||||
|
||||
item.setState(new QuantityType<>("165 °C"));
|
||||
assertThat(item.getState(), is(new QuantityType<>("329 °F")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testPreservedWhenStateDescriptionContainsWildCard() {
|
||||
UnitProvider unitProviderMock = mock(UnitProvider.class);
|
||||
when(unitProviderMock.getUnit(Temperature.class)).thenReturn(SIUnits.CELSIUS);
|
||||
when(stateDescriptionServiceMock.getStateDescription(ITEM_NAME, null))
|
||||
.thenReturn(StateDescriptionFragmentBuilder.create().withPattern("%.0f " + UnitUtils.UNIT_PLACEHOLDER)
|
||||
.build().toStateDescription());
|
||||
|
||||
NumberItem item = new NumberItem("Number:Temperature", ITEM_NAME);
|
||||
item.setStateDescriptionService(stateDescriptionServiceMock);
|
||||
item.setUnitProvider(unitProviderMock);
|
||||
|
||||
assertThat(item.getUnit(), is(nullValue()));
|
||||
|
||||
item.setState(new QuantityType<>("329 °F"));
|
||||
assertThat(item.getState(), is(new QuantityType<>("329 °F")));
|
||||
|
||||
item.setState(new QuantityType<>("100 °C"));
|
||||
assertThat(item.getState(), is(new QuantityType<>("100 °C")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDefaultUnitUsedWhenStateDescriptionEmpty() {
|
||||
UnitProvider unitProviderMock = mock(UnitProvider.class);
|
||||
when(unitProviderMock.getUnit(Temperature.class)).thenReturn(SIUnits.CELSIUS);
|
||||
|
||||
NumberItem item = new NumberItem("Number:Temperature", ITEM_NAME);
|
||||
item.setUnitProvider(unitProviderMock);
|
||||
|
||||
assertThat(item.getUnit(), is(SIUnits.CELSIUS));
|
||||
|
||||
item.setState(new QuantityType<>("329 °F"));
|
||||
assertThat(item.getState(), is(new QuantityType<>("165 °C")));
|
||||
|
||||
item.setState(new QuantityType<>("100 °C"));
|
||||
assertThat(item.getState(), is(new QuantityType<>("100 °C")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testNoUnitWhenUnitPlaceholderUsed() {
|
||||
final UnitProvider unitProviderMock = mock(UnitProvider.class);
|
||||
when(unitProviderMock.getUnit(Energy.class)).thenReturn(Units.JOULE);
|
||||
|
||||
final NumberItem item = new NumberItem("Number:Energy", ITEM_NAME);
|
||||
item.setUnitProvider(unitProviderMock);
|
||||
|
||||
assertThat(item.getUnit(), is(Units.JOULE));
|
||||
|
||||
item.setStateDescriptionService(stateDescriptionServiceMock);
|
||||
item.setState(new QuantityType<>("329 kWh"));
|
||||
|
||||
assertThat(item.getState(), is(new QuantityType<>("329 kWh")));
|
||||
assertThat(item.getUnit(), is(nullValue()));
|
||||
}
|
||||
|
||||
public void quantityTypeCorrectlySetWithDifferentUnit() {
|
||||
NumberItem numberItem = new NumberItem("Number:Temperature", ITEM_NAME);
|
||||
numberItem.setUnitProvider(unitProviderMock);
|
||||
numberItem.setEventPublisher(eventPublisherMock);
|
||||
numberItem.setState(new QuantityType<>("140 °F"));
|
||||
|
||||
assertThat(numberItem.getState(), Matchers.is(new QuantityType<>("60 °C")));
|
||||
|
||||
ArgumentCaptor<Event> captor = ArgumentCaptor.forClass(Event.class);
|
||||
verify(eventPublisherMock, times(2)).post(captor.capture());
|
||||
|
||||
List<Event> events = captor.getAllValues();
|
||||
assertThat(events, hasSize(2));
|
||||
|
||||
assertThat(events.get(0), Matchers.is(instanceOf(ItemStateUpdatedEvent.class)));
|
||||
|
||||
ItemStateUpdatedEvent updatedEvent = (ItemStateUpdatedEvent) events.get(0);
|
||||
assertThat(updatedEvent.getItemName(), Matchers.is(ITEM_NAME));
|
||||
assertThat(updatedEvent.getItemState(), Matchers.is(new QuantityType<>("60°C")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void decimalTypeCorrectlySetWithUnit() {
|
||||
NumberItem numberItem = new NumberItem("Number:Temperature", ITEM_NAME);
|
||||
numberItem.setUnitProvider(unitProviderMock);
|
||||
numberItem.setEventPublisher(eventPublisherMock);
|
||||
numberItem.setState(new DecimalType(10));
|
||||
|
||||
assertThat(numberItem.getState(), Matchers.is(new QuantityType<>("10 °C")));
|
||||
|
||||
ArgumentCaptor<Event> captor = ArgumentCaptor.forClass(Event.class);
|
||||
verify(eventPublisherMock, times(2)).post(captor.capture());
|
||||
|
||||
List<Event> events = captor.getAllValues();
|
||||
assertThat(events, hasSize(2));
|
||||
|
||||
assertThat(events.get(0), Matchers.is(instanceOf(ItemStateUpdatedEvent.class)));
|
||||
|
||||
ItemStateUpdatedEvent updatedEvent = (ItemStateUpdatedEvent) events.get(0);
|
||||
assertThat(updatedEvent.getItemName(), Matchers.is(ITEM_NAME));
|
||||
assertThat(updatedEvent.getItemState(), Matchers.is(new QuantityType<>("10°C")));
|
||||
private Metadata getUnitMetadata(Unit<?> unit) {
|
||||
MetadataKey key = new MetadataKey(NumberItem.UNIT_METADATA_NAMESPACE, ITEM_NAME);
|
||||
return new Metadata(key, unit.toString(), null);
|
||||
}
|
||||
}
|
||||
|
@ -32,6 +32,7 @@ import org.junit.jupiter.params.provider.MethodSource;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.openhab.core.i18n.UnitProvider;
|
||||
import org.openhab.core.internal.i18n.TestUnitProvider;
|
||||
import org.openhab.core.items.GroupFunction;
|
||||
import org.openhab.core.items.GroupItem;
|
||||
import org.openhab.core.items.Item;
|
||||
@ -39,6 +40,7 @@ import org.openhab.core.library.CoreItemFactory;
|
||||
import org.openhab.core.library.items.NumberItem;
|
||||
import org.openhab.core.types.State;
|
||||
import org.openhab.core.types.UnDefType;
|
||||
import org.osgi.service.component.ComponentContext;
|
||||
|
||||
/**
|
||||
* @author Henning Treu - Initial contribution
|
||||
@ -47,7 +49,8 @@ import org.openhab.core.types.UnDefType;
|
||||
@NonNullByDefault
|
||||
public class QuantityTypeArithmeticGroupFunctionTest {
|
||||
|
||||
private @NonNullByDefault({}) @Mock UnitProvider unitProvider;
|
||||
private @Mock @NonNullByDefault({}) ComponentContext componentContext;
|
||||
private final UnitProvider unitProvider = new TestUnitProvider();
|
||||
|
||||
/**
|
||||
* Locales having a different decimal and grouping separators to test string parsing and generation.
|
||||
@ -313,16 +316,14 @@ public class QuantityTypeArithmeticGroupFunctionTest {
|
||||
}
|
||||
|
||||
private NumberItem createNumberItem(String name, Class<? extends Quantity<?>> dimension, State state) {
|
||||
NumberItem item = new NumberItem(CoreItemFactory.NUMBER + ":" + dimension.getSimpleName(), name);
|
||||
item.setUnitProvider(unitProvider);
|
||||
NumberItem item = new NumberItem(CoreItemFactory.NUMBER + ":" + dimension.getSimpleName(), name, unitProvider);
|
||||
item.setState(state);
|
||||
return item;
|
||||
}
|
||||
|
||||
private GroupItem createGroupItem(String name, Class<? extends Quantity<?>> dimension, State state) {
|
||||
GroupItem item = new GroupItem(name,
|
||||
new NumberItem(CoreItemFactory.NUMBER + ":" + dimension.getSimpleName(), name));
|
||||
item.setUnitProvider(unitProvider);
|
||||
new NumberItem(CoreItemFactory.NUMBER + ":" + dimension.getSimpleName(), name, unitProvider));
|
||||
item.setState(state);
|
||||
return item;
|
||||
}
|
||||
|
@ -116,4 +116,9 @@ Fragment-Host: org.openhab.core.model.script
|
||||
junit-platform-commons;version='[1.9.2,1.9.3)',\
|
||||
junit-platform-engine;version='[1.9.2,1.9.3)',\
|
||||
junit-platform-launcher;version='[1.9.2,1.9.3)',\
|
||||
org.openhab.core.model.thing.runtime;version='[4.0.0,4.0.1)'
|
||||
org.openhab.core.model.thing.runtime;version='[4.0.0,4.0.1)',\
|
||||
net.bytebuddy.byte-buddy;version='[1.12.19,1.12.20)',\
|
||||
net.bytebuddy.byte-buddy-agent;version='[1.12.19,1.12.20)',\
|
||||
org.mockito.junit-jupiter;version='[4.11.0,4.11.1)',\
|
||||
org.mockito.mockito-core;version='[4.11.0,4.11.1)',\
|
||||
org.objenesis;version='[3.3.0,3.3.1)'
|
||||
|
@ -15,6 +15,7 @@ package org.openhab.core.model.script.engine;
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
@ -26,8 +27,14 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.mockito.junit.jupiter.MockitoSettings;
|
||||
import org.mockito.quality.Strictness;
|
||||
import org.openhab.core.common.registry.ProviderChangeListener;
|
||||
import org.openhab.core.events.EventPublisher;
|
||||
import org.openhab.core.i18n.UnitProvider;
|
||||
import org.openhab.core.items.Item;
|
||||
import org.openhab.core.items.ItemProvider;
|
||||
import org.openhab.core.items.ItemRegistry;
|
||||
@ -48,6 +55,8 @@ import org.openhab.core.types.State;
|
||||
* @author Henning Treu - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
@MockitoSettings(strictness = Strictness.LENIENT)
|
||||
public class ScriptEngineOSGiTest extends JavaOSGiTest {
|
||||
|
||||
private static final String ITEM_NAME = "Switch1";
|
||||
@ -58,9 +67,13 @@ public class ScriptEngineOSGiTest extends JavaOSGiTest {
|
||||
private @NonNullByDefault({}) ItemProvider itemProvider;
|
||||
private @NonNullByDefault({}) ItemRegistry itemRegistry;
|
||||
private @NonNullByDefault({}) ScriptEngine scriptEngine;
|
||||
private @Mock @NonNullByDefault({}) UnitProvider unitProviderMock;
|
||||
|
||||
@BeforeEach
|
||||
public void setup() {
|
||||
when(unitProviderMock.getUnit(Temperature.class)).thenReturn(SIUnits.CELSIUS);
|
||||
when(unitProviderMock.getUnit(Length.class)).thenReturn(SIUnits.METRE);
|
||||
|
||||
registerVolatileStorageService();
|
||||
|
||||
EventPublisher eventPublisher = event -> {
|
||||
@ -351,7 +364,7 @@ public class ScriptEngineOSGiTest extends JavaOSGiTest {
|
||||
}
|
||||
|
||||
private Item createNumberItem(String numberItemName, Class<?> dimension) {
|
||||
return new NumberItem("Number:" + dimension.getSimpleName(), numberItemName);
|
||||
return new NumberItem("Number:" + dimension.getSimpleName(), numberItemName, unitProviderMock);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
|
@ -17,6 +17,7 @@ import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.collection.IsCollectionWithSize.hasSize;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.openhab.core.library.unit.Units.ONE;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
@ -64,6 +65,7 @@ import org.openhab.core.library.types.PercentType;
|
||||
import org.openhab.core.library.types.QuantityType;
|
||||
import org.openhab.core.library.types.RawType;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
import org.openhab.core.library.unit.SIUnits;
|
||||
import org.openhab.core.test.java.JavaOSGiTest;
|
||||
import org.openhab.core.types.RefreshType;
|
||||
import org.openhab.core.types.State;
|
||||
@ -796,7 +798,6 @@ public class GroupItemOSGiTest extends JavaOSGiTest {
|
||||
gfDTO.name = "sum";
|
||||
GroupFunction function = groupFunctionHelper.createGroupFunction(gfDTO, baseItem);
|
||||
GroupItem groupItem = new GroupItem("number", baseItem, function);
|
||||
groupItem.setUnitProvider(unitProviderMock);
|
||||
|
||||
NumberItem celsius = createNumberItem("C", Temperature.class, new QuantityType<>("23 °C"));
|
||||
groupItem.addMember(celsius);
|
||||
@ -820,12 +821,14 @@ public class GroupItemOSGiTest extends JavaOSGiTest {
|
||||
|
||||
@Test
|
||||
public void assertThatNumberGroupItemWithDifferentDimensionsCalculatesCorrectState() {
|
||||
when(unitProviderMock.getUnit(Temperature.class)).thenReturn(SIUnits.CELSIUS);
|
||||
when(unitProviderMock.getUnit(Pressure.class)).thenReturn(SIUnits.PASCAL);
|
||||
when(unitProviderMock.getUnit(Dimensionless.class)).thenReturn(ONE);
|
||||
NumberItem baseItem = createNumberItem("baseItem", Temperature.class, UnDefType.NULL);
|
||||
GroupFunctionDTO gfDTO = new GroupFunctionDTO();
|
||||
gfDTO.name = "sum";
|
||||
GroupFunction function = groupFunctionHelper.createGroupFunction(gfDTO, baseItem);
|
||||
GroupItem groupItem = new GroupItem("number", baseItem, function);
|
||||
groupItem.setUnitProvider(unitProviderMock);
|
||||
groupItem.setItemStateConverter(itemStateConverter);
|
||||
|
||||
NumberItem celsius = createNumberItem("C", Temperature.class, new QuantityType<>("23 °C"));
|
||||
@ -844,8 +847,8 @@ public class GroupItemOSGiTest extends JavaOSGiTest {
|
||||
}
|
||||
|
||||
private NumberItem createNumberItem(String name, Class<? extends Quantity<?>> dimension, State state) {
|
||||
NumberItem item = new NumberItem(CoreItemFactory.NUMBER + ":" + dimension.getSimpleName(), name);
|
||||
item.setUnitProvider(unitProviderMock);
|
||||
NumberItem item = new NumberItem(CoreItemFactory.NUMBER + ":" + dimension.getSimpleName(), name,
|
||||
unitProviderMock);
|
||||
item.setState(state);
|
||||
|
||||
return item;
|
||||
|
@ -77,6 +77,7 @@ public class ItemRegistryImplTest extends JavaTest {
|
||||
private @NonNullByDefault({}) ManagedItemProvider itemProvider;
|
||||
|
||||
private @Mock @NonNullByDefault({}) EventPublisher eventPublisherMock;
|
||||
private @Mock @NonNullByDefault({}) UnitProvider unitProviderMock;
|
||||
|
||||
@BeforeEach
|
||||
public void beforeEach() {
|
||||
@ -92,7 +93,7 @@ public class ItemRegistryImplTest extends JavaTest {
|
||||
|
||||
// setup ManageItemProvider with necessary dependencies:
|
||||
itemProvider = new ManagedItemProvider(new VolatileStorageService(),
|
||||
new ItemBuilderFactoryImpl(new CoreItemFactory()));
|
||||
new ItemBuilderFactoryImpl(new CoreItemFactory(unitProviderMock)));
|
||||
|
||||
itemProvider.add(new SwitchItem(ITEM_NAME));
|
||||
itemProvider.add(cameraItem1);
|
||||
@ -107,7 +108,6 @@ public class ItemRegistryImplTest extends JavaTest {
|
||||
setManagedProvider(itemProvider);
|
||||
setEventPublisher(ItemRegistryImplTest.this.eventPublisherMock);
|
||||
setStateDescriptionService(mock(StateDescriptionService.class));
|
||||
setUnitProvider(mock(UnitProvider.class));
|
||||
setItemStateConverter(mock(ItemStateConverter.class));
|
||||
}
|
||||
};
|
||||
@ -369,13 +369,11 @@ public class ItemRegistryImplTest extends JavaTest {
|
||||
|
||||
assertNotNull(item.eventPublisher);
|
||||
assertNotNull(item.itemStateConverter);
|
||||
assertNotNull(item.unitProvider);
|
||||
|
||||
itemProvider.update(new SwitchItem("Item1"));
|
||||
|
||||
assertNull(item.eventPublisher);
|
||||
assertNull(item.itemStateConverter);
|
||||
assertNull(item.unitProvider);
|
||||
assertEquals(0, item.listeners.size());
|
||||
}
|
||||
|
||||
@ -391,18 +389,6 @@ public class ItemRegistryImplTest extends JavaTest {
|
||||
verify(baseItem).setStateDescriptionService(any(StateDescriptionService.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void assertUnitProviderGetsInjected() {
|
||||
GenericItem item = spy(new SwitchItem("Item1"));
|
||||
NumberItem baseItem = spy(new NumberItem("baseItem"));
|
||||
GenericItem group = new GroupItem("Group", baseItem);
|
||||
itemProvider.add(item);
|
||||
itemProvider.add(group);
|
||||
|
||||
verify(item).setUnitProvider(any(UnitProvider.class));
|
||||
verify(baseItem).setUnitProvider(any(UnitProvider.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void assertCommandDescriptionServiceGetsInjected() {
|
||||
GenericItem item = spy(new SwitchItem("Item1"));
|
||||
|
@ -40,6 +40,8 @@ import org.openhab.core.i18n.UnitProvider;
|
||||
import org.openhab.core.items.Item;
|
||||
import org.openhab.core.items.ItemRegistry;
|
||||
import org.openhab.core.items.ItemStateConverter;
|
||||
import org.openhab.core.items.Metadata;
|
||||
import org.openhab.core.items.MetadataKey;
|
||||
import org.openhab.core.items.events.ItemCommandEvent;
|
||||
import org.openhab.core.items.events.ItemEventFactory;
|
||||
import org.openhab.core.library.CoreItemFactory;
|
||||
@ -50,8 +52,8 @@ import org.openhab.core.library.types.HSBType;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.PercentType;
|
||||
import org.openhab.core.library.types.QuantityType;
|
||||
import org.openhab.core.library.unit.ImperialUnits;
|
||||
import org.openhab.core.library.unit.SIUnits;
|
||||
import org.openhab.core.service.StateDescriptionService;
|
||||
import org.openhab.core.test.java.JavaOSGiTest;
|
||||
import org.openhab.core.thing.Channel;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
@ -81,7 +83,6 @@ import org.openhab.core.thing.type.ChannelTypeRegistry;
|
||||
import org.openhab.core.thing.type.ChannelTypeUID;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.State;
|
||||
import org.openhab.core.types.StateDescriptionFragmentBuilder;
|
||||
|
||||
/**
|
||||
*
|
||||
@ -103,21 +104,27 @@ public class CommunicationManagerOSGiTest extends JavaOSGiTest {
|
||||
}
|
||||
}
|
||||
|
||||
private static final UnitProvider unitProviderMock = mock(UnitProvider.class);
|
||||
|
||||
private static final String EVENT = "event";
|
||||
private static final String ITEM_NAME_1 = "testItem1";
|
||||
private static final String ITEM_NAME_2 = "testItem2";
|
||||
private static final String ITEM_NAME_3 = "testItem3";
|
||||
private static final String ITEM_NAME_4 = "testItem4";
|
||||
private static final String ITEM_NAME_5 = "testItem5";
|
||||
private static final SwitchItem ITEM_1 = new SwitchItem(ITEM_NAME_1);
|
||||
private static final SwitchItem ITEM_2 = new SwitchItem(ITEM_NAME_2);
|
||||
private static final NumberItem ITEM_3 = new NumberItem(ITEM_NAME_3);
|
||||
private static final NumberItem ITEM_4 = new NumberItem(ITEM_NAME_4);
|
||||
private static NumberItem ITEM_5 = new NumberItem(ITEM_NAME_5); // will be replaced later by dimension item
|
||||
|
||||
private static final ThingTypeUID THING_TYPE_UID = new ThingTypeUID("test", "type");
|
||||
private static final ThingUID THING_UID = new ThingUID("test", "thing");
|
||||
private static final ChannelUID STATE_CHANNEL_UID_1 = new ChannelUID(THING_UID, "state-channel1");
|
||||
private static final ChannelUID STATE_CHANNEL_UID_2 = new ChannelUID(THING_UID, "state-channel2");
|
||||
private static final ChannelUID STATE_CHANNEL_UID_3 = new ChannelUID(THING_UID, "state-channel3");
|
||||
private static final ChannelUID STATE_CHANNEL_UID_4 = new ChannelUID(THING_UID, "state-channel4");
|
||||
private static final ChannelUID STATE_CHANNEL_UID_5 = new ChannelUID(THING_UID, "state-channel5");
|
||||
private static final ChannelTypeUID CHANNEL_TYPE_UID_4 = new ChannelTypeUID("test", "channeltype");
|
||||
private static final ChannelUID TRIGGER_CHANNEL_UID_1 = new ChannelUID(THING_UID, "trigger-channel1");
|
||||
private static final ChannelUID TRIGGER_CHANNEL_UID_2 = new ChannelUID(THING_UID, "trigger-channel2");
|
||||
@ -126,6 +133,7 @@ public class CommunicationManagerOSGiTest extends JavaOSGiTest {
|
||||
private static final ItemChannelLink LINK_2_S2 = new ItemChannelLink(ITEM_NAME_2, STATE_CHANNEL_UID_2);
|
||||
private static final ItemChannelLink LINK_3_S3 = new ItemChannelLink(ITEM_NAME_3, STATE_CHANNEL_UID_3);
|
||||
private static final ItemChannelLink LINK_4_S4 = new ItemChannelLink(ITEM_NAME_4, STATE_CHANNEL_UID_4);
|
||||
private static final ItemChannelLink LINK_5_S5 = new ItemChannelLink(ITEM_NAME_5, STATE_CHANNEL_UID_5);
|
||||
private static final ItemChannelLink LINK_1_T1 = new ItemChannelLink(ITEM_NAME_1, TRIGGER_CHANNEL_UID_1);
|
||||
private static final ItemChannelLink LINK_1_T2 = new ItemChannelLink(ITEM_NAME_1, TRIGGER_CHANNEL_UID_2);
|
||||
private static final ItemChannelLink LINK_2_T2 = new ItemChannelLink(ITEM_NAME_2, TRIGGER_CHANNEL_UID_2);
|
||||
@ -135,6 +143,7 @@ public class CommunicationManagerOSGiTest extends JavaOSGiTest {
|
||||
ChannelBuilder.create(STATE_CHANNEL_UID_3, "Number:Temperature").withKind(ChannelKind.STATE).build(),
|
||||
ChannelBuilder.create(STATE_CHANNEL_UID_4, CoreItemFactory.NUMBER).withKind(ChannelKind.STATE)
|
||||
.withType(CHANNEL_TYPE_UID_4).build(),
|
||||
ChannelBuilder.create(STATE_CHANNEL_UID_5, "Number:Temperature").withKind(ChannelKind.STATE).build(),
|
||||
ChannelBuilder.create(TRIGGER_CHANNEL_UID_1).withKind(ChannelKind.TRIGGER).build(),
|
||||
ChannelBuilder.create(TRIGGER_CHANNEL_UID_2).withKind(ChannelKind.TRIGGER).build()).build();
|
||||
|
||||
@ -158,6 +167,9 @@ public class CommunicationManagerOSGiTest extends JavaOSGiTest {
|
||||
|
||||
@BeforeEach
|
||||
public void beforeEach() {
|
||||
when(unitProviderMock.getUnit(Temperature.class)).thenReturn(SIUnits.CELSIUS);
|
||||
ITEM_5 = new NumberItem("Number:Temperature", ITEM_NAME_5, unitProviderMock);
|
||||
|
||||
safeCaller = getService(SafeCaller.class);
|
||||
assertNotNull(safeCaller);
|
||||
|
||||
@ -166,7 +178,8 @@ public class CommunicationManagerOSGiTest extends JavaOSGiTest {
|
||||
assertNotNull(profileFactory);
|
||||
|
||||
manager = new CommunicationManager(autoUpdateManagerMock, channelTypeRegistryMock, profileFactory, iclRegistry,
|
||||
itemRegistryMock, itemStateConverterMock, eventPublisherMock, safeCaller, thingRegistryMock);
|
||||
itemRegistryMock, itemStateConverterMock, eventPublisherMock, safeCaller, thingRegistryMock,
|
||||
unitProviderMock);
|
||||
|
||||
doAnswer(invocation -> {
|
||||
switch (((Channel) invocation.getArguments()[0]).getKind()) {
|
||||
@ -205,7 +218,8 @@ public class CommunicationManagerOSGiTest extends JavaOSGiTest {
|
||||
|
||||
@Override
|
||||
public Collection<ItemChannelLink> getAll() {
|
||||
return List.of(LINK_1_S1, LINK_1_S2, LINK_2_S2, LINK_1_T1, LINK_1_T2, LINK_2_T2, LINK_3_S3, LINK_4_S4);
|
||||
return List.of(LINK_1_S1, LINK_1_S2, LINK_2_S2, LINK_1_T1, LINK_1_T2, LINK_2_T2, LINK_3_S3, LINK_4_S4,
|
||||
LINK_5_S5);
|
||||
}
|
||||
});
|
||||
|
||||
@ -213,6 +227,7 @@ public class CommunicationManagerOSGiTest extends JavaOSGiTest {
|
||||
when(itemRegistryMock.get(eq(ITEM_NAME_2))).thenReturn(ITEM_2);
|
||||
when(itemRegistryMock.get(eq(ITEM_NAME_3))).thenReturn(ITEM_3);
|
||||
when(itemRegistryMock.get(eq(ITEM_NAME_4))).thenReturn(ITEM_4);
|
||||
when(itemRegistryMock.get(eq(ITEM_NAME_5))).thenReturn(ITEM_5);
|
||||
|
||||
ChannelType channelType4 = mock(ChannelType.class);
|
||||
when(channelType4.getItemType()).thenReturn("Number:Temperature");
|
||||
@ -222,12 +237,8 @@ public class CommunicationManagerOSGiTest extends JavaOSGiTest {
|
||||
THING.setHandler(thingHandlerMock);
|
||||
|
||||
when(thingRegistryMock.get(eq(THING_UID))).thenReturn(THING);
|
||||
manager.addItemFactory(new CoreItemFactory());
|
||||
|
||||
UnitProvider unitProvider = mock(UnitProvider.class);
|
||||
when(unitProvider.getUnit(Temperature.class)).thenReturn(SIUnits.CELSIUS);
|
||||
ITEM_3.setUnitProvider(unitProvider);
|
||||
ITEM_4.setUnitProvider(unitProvider);
|
||||
manager.addItemFactory(new CoreItemFactory(unitProviderMock));
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -284,7 +295,7 @@ public class CommunicationManagerOSGiTest extends JavaOSGiTest {
|
||||
@Test
|
||||
public void testItemCommandEventDecimal2Quantity() {
|
||||
// Take unit from accepted item type (see channel built from STATE_CHANNEL_UID_3)
|
||||
manager.receive(ItemEventFactory.createCommandEvent(ITEM_NAME_3, DecimalType.valueOf("20")));
|
||||
manager.receive(ItemEventFactory.createCommandEvent(ITEM_NAME_5, DecimalType.valueOf("20")));
|
||||
waitForAssert(() -> {
|
||||
verify(stateProfileMock).onCommandFromItem(eq(QuantityType.valueOf("20 °C")));
|
||||
});
|
||||
@ -294,33 +305,16 @@ public class CommunicationManagerOSGiTest extends JavaOSGiTest {
|
||||
|
||||
@Test
|
||||
public void testItemCommandEventDecimal2Quantity2() {
|
||||
// Take unit from state description
|
||||
StateDescriptionService stateDescriptionService = mock(StateDescriptionService.class);
|
||||
when(stateDescriptionService.getStateDescription(ITEM_NAME_3, null)).thenReturn(
|
||||
StateDescriptionFragmentBuilder.create().withPattern("%.1f °F").build().toStateDescription());
|
||||
ITEM_3.setStateDescriptionService(stateDescriptionService);
|
||||
MetadataKey key = new MetadataKey(NumberItem.UNIT_METADATA_NAMESPACE, ITEM_NAME_5);
|
||||
Metadata metadata = new Metadata(key, ImperialUnits.FAHRENHEIT.toString(), null);
|
||||
ITEM_5.addedMetadata(metadata);
|
||||
|
||||
manager.receive(ItemEventFactory.createCommandEvent(ITEM_NAME_3, DecimalType.valueOf("20")));
|
||||
manager.receive(ItemEventFactory.createCommandEvent(ITEM_NAME_5, DecimalType.valueOf("20")));
|
||||
waitForAssert(() -> {
|
||||
verify(stateProfileMock).onCommandFromItem(eq(QuantityType.valueOf("20 °F")));
|
||||
});
|
||||
verifyNoMoreInteractions(stateProfileMock);
|
||||
verifyNoMoreInteractions(triggerProfileMock);
|
||||
|
||||
ITEM_3.setStateDescriptionService(null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testItemCommandEventDecimal2QuantityChannelType() {
|
||||
// The command is sent to an item w/o dimension defined and the channel is legacy (created from a ThingType
|
||||
// definition before UoM was introduced to the binding). The dimension information might now be defined on the
|
||||
// current ThingType.
|
||||
manager.receive(ItemEventFactory.createCommandEvent(ITEM_NAME_4, DecimalType.valueOf("20")));
|
||||
waitForAssert(() -> {
|
||||
verify(stateProfileMock).onCommandFromItem(eq(QuantityType.valueOf("20 °C")));
|
||||
});
|
||||
verifyNoMoreInteractions(stateProfileMock);
|
||||
verifyNoMoreInteractions(triggerProfileMock);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -25,6 +25,7 @@ import org.openhab.core.config.core.Configuration;
|
||||
import org.openhab.core.items.ManagedItemProvider;
|
||||
import org.openhab.core.items.Metadata;
|
||||
import org.openhab.core.items.MetadataKey;
|
||||
import org.openhab.core.library.items.NumberItem;
|
||||
import org.openhab.core.storage.json.internal.JsonStorage;
|
||||
import org.openhab.core.thing.internal.link.ItemChannelLinkConfigDescriptionProvider;
|
||||
import org.openhab.core.thing.link.ItemChannelLink;
|
||||
@ -91,7 +92,7 @@ public class Upgrader {
|
||||
itemStorage.getKeys().forEach(itemName -> {
|
||||
ManagedItemProvider.PersistedItem item = itemStorage.get(itemName);
|
||||
if (item != null && item.itemType.startsWith("Number:")) {
|
||||
if (metadataStorage.containsKey("unit" + ":" + itemName)) {
|
||||
if (metadataStorage.containsKey(NumberItem.UNIT_METADATA_NAMESPACE + ":" + itemName)) {
|
||||
logger.debug("{}: already contains a 'unit' metadata, skipping it", itemName);
|
||||
} else {
|
||||
Metadata metadata = metadataStorage.get("stateDescription:" + itemName);
|
||||
@ -107,7 +108,8 @@ public class Upgrader {
|
||||
Unit<?> stateDescriptionUnit = UnitUtils.parseUnit(pattern);
|
||||
if (stateDescriptionUnit != null) {
|
||||
String unit = stateDescriptionUnit.toString();
|
||||
MetadataKey defaultUnitMetadataKey = new MetadataKey("unit", itemName);
|
||||
MetadataKey defaultUnitMetadataKey = new MetadataKey(NumberItem.UNIT_METADATA_NAMESPACE,
|
||||
itemName);
|
||||
Metadata defaultUnitMetadata = new Metadata(defaultUnitMetadataKey, unit, null);
|
||||
metadataStorage.put(defaultUnitMetadataKey.toString(), defaultUnitMetadata);
|
||||
logger.info("{}: Wrote 'unit={}' to metadata.", itemName, unit);
|
||||
|
Loading…
Reference in New Issue
Block a user