Skip to content

Commit

Permalink
openhab#16308 Added unit storage in DB and fixed bug in HistoricItem.…
Browse files Browse the repository at this point in the history
…toString

Signed-off-by: René Ulbricht <rene_ulbricht@outlook.com>
  • Loading branch information
ulbi committed Feb 3, 2024
1 parent 2fcac9c commit 09ee313
Show file tree
Hide file tree
Showing 7 changed files with 177 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ public final class MongoDBFields {
public static final String FIELD_REALNAME = "realName";
public static final String FIELD_TIMESTAMP = "timestamp";
public static final String FIELD_VALUE = "value";
public static final String FIELD_UNIT = "unit";
public static final String FIELD_VALUE_DATA = "value.data";
public static final String FIELD_VALUE_TYPE = "value.type";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

import java.text.DateFormat;
import java.time.ZonedDateTime;
import java.util.Date;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.persistence.HistoricItem;
Expand Down Expand Up @@ -54,6 +55,7 @@ public ZonedDateTime getTimestamp() {

@Override
public String toString() {
return DateFormat.getDateTimeInstance().format(timestamp) + ": " + name + " -> " + state.toString();
Date date = Date.from(timestamp.toInstant());
return DateFormat.getDateTimeInstance().format(date) + ": " + name + " -> " + state.toString();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -169,14 +169,18 @@ public void store(Item item, @Nullable String alias) {
}

String name = (alias != null) ? alias : realItemName;
Object value = MongoDBTypeConversions.convertValue(item.getState());
State state = item.getState();
Object value = MongoDBTypeConversions.convertValue(state);

Document obj = new Document();
obj.put(MongoDBFields.FIELD_ID, new ObjectId());
obj.put(MongoDBFields.FIELD_ITEM, name);
obj.put(MongoDBFields.FIELD_REALNAME, realItemName);
obj.put(MongoDBFields.FIELD_TIMESTAMP, new Date());
obj.put(MongoDBFields.FIELD_VALUE, value);
if (item instanceof NumberItem && state instanceof QuantityType<?>) {
obj.put(MongoDBFields.FIELD_UNIT, ((QuantityType<?>) state).getUnit().toString());
}
try {
collection.insertOne(obj);
} catch (org.bson.BsonMaximumSizeExceededException e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import org.openhab.core.persistence.FilterCriteria.Operator;
import org.openhab.core.types.*;
import org.openhab.core.types.State;
import org.openhab.core.types.util.UnitUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand Down Expand Up @@ -85,6 +86,16 @@ public static Object convertValue(State state) {
NumberItem numberItem = (NumberItem) item;
Unit<?> unit = numberItem.getUnit();
Object value = doc.get(MongoDBFields.FIELD_VALUE);
// check whether doc contains MongoDBFields.FIELD_UNIT, if so use this unit, otherwise use the unit from the
// item
if (doc.containsKey(MongoDBFields.FIELD_UNIT)) {
String unitString = doc.getString(MongoDBFields.FIELD_UNIT);
Unit<?> docUnit = UnitUtils.parseUnit(unitString);
if (docUnit != null) {
unit = docUnit;
}
}

if (value instanceof String) {
return new QuantityType<>(value.toString());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,16 @@
*/
package org.openhab.persistence.mongodb.internal;

import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
import static org.mockito.Mockito.when;

import java.time.LocalDate;
import java.time.ZonedDateTime;
import java.util.Date;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;
import java.util.UUID;
import java.util.stream.Stream;
Expand All @@ -27,13 +30,18 @@
import org.bson.types.ObjectId;
import org.junit.jupiter.params.provider.Arguments;
import org.mockito.Mockito;
import org.openhab.core.i18n.UnitProvider;
import org.openhab.core.internal.i18n.I18nProviderImpl;
import org.openhab.core.items.GenericItem;
import org.openhab.core.items.ItemRegistry;
import org.openhab.core.library.items.*;
import org.openhab.core.library.types.*;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.unit.SIUnits;
import org.openhab.core.persistence.FilterCriteria;
import org.openhab.core.types.State;
import org.osgi.framework.BundleContext;
import org.osgi.service.component.ComponentContext;
import org.slf4j.LoggerFactory;
import org.testcontainers.DockerClientFactory;

Expand All @@ -55,6 +63,17 @@
*/
public class DataCreationHelper {

protected static final UnitProvider UNIT_PROVIDER;
static {
ComponentContext context = Mockito.mock(ComponentContext.class);
BundleContext bundleContext = Mockito.mock(BundleContext.class);
Hashtable<String, Object> properties = new Hashtable<>();
properties.put("measurementSystem", SIUnits.MEASUREMENT_SYSTEM_NAME);
when(context.getProperties()).thenReturn(properties);
when(context.getBundleContext()).thenReturn(bundleContext);
UNIT_PROVIDER = new I18nProviderImpl(context);
}

/**
* Creates a NumberItem with a given name and value.
*
Expand All @@ -77,6 +96,22 @@ public static StringItem createStringItem(String name, String value) {
return createItem(StringItem.class, name, new StringType(value));
}

/**
* Creates an instance of a NumberItem with a unit type and sets its state.
*
* @param itemType The Class object representing the type of the item to create.
* @param unitType The string representation of the unit type to set on the new item.
* @param name The name to give to the new item.
* @param state The state to set on the new item.
* @return The newly created item.
* @throws RuntimeException if an error occurs while creating the item or setting its state.
*/
public static NumberItem createNumberItem(String unitType, String name, State state) {
NumberItem item = new NumberItem(unitType, name, UNIT_PROVIDER);
item.setState(state);
return item;
}

/**
* Creates an instance of a specific GenericItem subclass and sets its state.
*
Expand Down Expand Up @@ -151,8 +186,10 @@ public static Stream<Arguments> provideOpenhabItemTypes() {
new StringListType("+49 123 456 789"))),
Arguments.of(DataCreationHelper.createItem(ImageItem.class, "ImageItem",
new RawType(new byte[] { 0x00, 0x01, 0x02 }, "image/png"))),
Arguments.of(DataCreationHelper.createItem(NumberItem.class, "NumberItemCelcius",
new QuantityType<>("25.00 °C"))));
Arguments.of(DataCreationHelper.createNumberItem("Number:Energy", "NumberItemCelcius",
new QuantityType<>("25.00 MWh"))),
Arguments.of(DataCreationHelper.createNumberItem("Number:Temperature", "NumberItemCelcius",
new QuantityType<>("25.00 °F"))));
}

/**
Expand Down Expand Up @@ -310,6 +347,13 @@ private static Object convertValue(State state) {
return value;
}

/**
* Stores the old data of an item into a MongoDB collection.
*
* @param collection The MongoDB collection where the data will be stored.
* @param realItemName The real name of the item.
* @param state The state of the item.
*/
public static void storeOldData(MongoCollection<Document> collection, String realItemName, State state) {
// use the old way to store data
Object value = convertValue(state);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,16 @@
import static org.junit.jupiter.api.Assertions.*;
import static org.junit.jupiter.api.Assertions.assertEquals;

import java.text.DateFormat;
import java.time.LocalDate;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;

import org.bson.Document;
import org.bson.types.ObjectId;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
Expand All @@ -30,6 +33,7 @@
import org.openhab.core.items.ItemNotFoundException;
import org.openhab.core.library.items.*;
import org.openhab.core.library.types.*;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.persistence.FilterCriteria;
import org.openhab.core.persistence.HistoricItem;
import org.osgi.framework.BundleContext;
Expand Down Expand Up @@ -693,4 +697,106 @@ public void testOldDataQueryAllOpenhabItemTypesSingleCollection(GenericItem item
dbContainer.stop();
}
}

/**
* Tests the writting of NumberItems including units
* Each item should be written to the database with the unit information
*
* @param item The item to store in the database.
*/
@Test
public void testStoreNumberItemWithUnit() {
// Preparation
DatabaseTestContainer dbContainer = new DatabaseTestContainer(new MemoryBackend());
try {
SetupResult setupResult = DataCreationHelper.setupMongoDB("testCollection", dbContainer);
MongoDBPersistenceService service = setupResult.service;
MongoDatabase database = setupResult.database;

service.activate(setupResult.bundleContext, setupResult.config);
MongoCollection<Document> collection = database.getCollection("testCollection");

NumberItem item = DataCreationHelper.createNumberItem("Number:Energy", "TestItem",
new QuantityType("10.1 kWh"));

// Execution
service.store(item, null);

// Verification
List<Document> documents = (ArrayList<Document>) collection.find().into(new ArrayList<>());

assertEquals(1, documents.size()); // Assert that there is only one document

Document insertedDocument = documents.get(0); // Get the first (and only) document

assertEquals(10.1, insertedDocument.get(MongoDBFields.FIELD_VALUE));
assertEquals("kWh", insertedDocument.get(MongoDBFields.FIELD_UNIT));
} finally {
dbContainer.stop();
}
}

/**
* Tests the reading of NumberItems including units
* Each item should be written to the database with the unit information
*
* @param item The item to store in the database.
*/
@Test
public void testQueryNumberItemWithUnit() {
// Preparation
DatabaseTestContainer dbContainer = new DatabaseTestContainer(new MemoryBackend());
try {
SetupResult setupResult = DataCreationHelper.setupMongoDB("testCollection", dbContainer);
MongoDBPersistenceService service = setupResult.service;
MongoDatabase database = setupResult.database;

service.activate(setupResult.bundleContext, setupResult.config);
MongoCollection<Document> collection = database.getCollection("testCollection");

NumberItem item = DataCreationHelper.createNumberItem("Number:Energy", "TestItem",
new QuantityType("10.1 MWh"));
try {
Mockito.when(setupResult.itemRegistry.getItem("TestItem")).thenReturn(item);
} catch (ItemNotFoundException e) {
}

Document obj = new Document();
obj.put(MongoDBFields.FIELD_ID, new ObjectId());
obj.put(MongoDBFields.FIELD_ITEM, "TestItem");
obj.put(MongoDBFields.FIELD_REALNAME, "TestItem");
obj.put(MongoDBFields.FIELD_TIMESTAMP, new Date());
obj.put(MongoDBFields.FIELD_VALUE, 201.5);
obj.put(MongoDBFields.FIELD_UNIT, "Wh");
collection.insertOne(obj);

// Execution
FilterCriteria filter = DataCreationHelper.createFilterCriteria("TestItem");
Iterable<HistoricItem> result = service.query(filter);
VerificationHelper.verifyQueryResult(result, new QuantityType("201.5 Wh"));
} finally {
dbContainer.stop();
}
}

/**
* Tests the toString of a MongoDBItem
*
*
* @param item The item to store in the database.
*/
@Test
public void testHistoricItemToString() {
// Preparation
ZonedDateTime now = ZonedDateTime.now();
HistoricItem item = new MongoDBItem("TestItem", new DecimalType(10.1), now);

// Execution
String result = item.toString();

// Verification
// Jan 29, 2024, 8:43:26 PM: TestItem -> 10.1
String expected = DateFormat.getDateTimeInstance().format(Date.from(now.toInstant())) + ": TestItem -> 10.1";
assertEquals(expected, result);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,11 @@ public static void verifyDocumentWithAlias(Document document, String expectedAli
(ev, doc) -> Pair.of(ev.toString(), doc.getString(MongoDBFields.FIELD_VALUE)));
handlers.put(StringType.class, (ev, doc) -> Pair.of(ev, doc.get(MongoDBFields.FIELD_VALUE)));
handlers.put(UpDownType.class, (ev, doc) -> Pair.of(ev.toString(), doc.getString(MongoDBFields.FIELD_VALUE)));
handlers.put(QuantityType.class, (ev, doc) -> {
QuantityType<?> quantityType = (QuantityType<?>) ev;
return Pair.of(quantityType.doubleValue() + "--" + quantityType.getUnit(),
doc.getDouble(MongoDBFields.FIELD_VALUE) + "--" + doc.getString(MongoDBFields.FIELD_UNIT));
});
handlers.put(RawType.class, (ev, doc) -> {
RawType rawType = (RawType) ev;
Document expectedDoc = new Document();
Expand Down

0 comments on commit 09ee313

Please sign in to comment.