mirror of
https://github.com/danieldemus/openhab-core.git
synced 2026-07-29 12:34:22 +02:00
[rest] Persistence: Optionally add current Item state to response (#4394)
* [rest] Persistence endpoint: Optionally add current Item state to response This new optional parameter gives the UI additional possibilities for charts. E.g. it is now possible to display a bar chart with monthly energy consumption, where the consumption is only persisted at the end of the month, that includes the data from this month. Signed-off-by: Florian Hotze <florianh_dev@icloud.com>
This commit is contained in:
+36
-5
@@ -12,6 +12,7 @@
|
||||
*/
|
||||
package org.openhab.core.io.rest.core.internal.persistence;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.ArrayList;
|
||||
@@ -71,6 +72,7 @@ import org.openhab.core.persistence.registry.PersistenceServiceConfigurationRegi
|
||||
import org.openhab.core.persistence.strategy.PersistenceStrategy;
|
||||
import org.openhab.core.types.State;
|
||||
import org.openhab.core.types.TypeParser;
|
||||
import org.openhab.core.types.UnDefType;
|
||||
import org.osgi.service.component.annotations.Activate;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.osgi.service.component.annotations.Reference;
|
||||
@@ -294,8 +296,9 @@ public class PersistenceResource implements RESTResource {
|
||||
+ DateTimeType.DATE_PATTERN_WITH_TZ_AND_MS + "]") @QueryParam("endtime") @Nullable String endTime,
|
||||
@Parameter(description = "Page number of data to return. This parameter will enable paging.") @QueryParam("page") int pageNumber,
|
||||
@Parameter(description = "The length of each page.") @QueryParam("pagelength") int pageLength,
|
||||
@Parameter(description = "Gets one value before and after the requested period.") @QueryParam("boundary") boolean boundary) {
|
||||
return getItemHistoryDTO(serviceId, itemName, startTime, endTime, pageNumber, pageLength, boundary);
|
||||
@Parameter(description = "Gets one value before and after the requested period.") @QueryParam("boundary") boolean boundary,
|
||||
@Parameter(description = "Adds the current Item state into the requested period (the item state will be before or at the endtime)") @QueryParam("itemState") boolean itemState) {
|
||||
return getItemHistoryDTO(serviceId, itemName, startTime, endTime, pageNumber, pageLength, boundary, itemState);
|
||||
}
|
||||
|
||||
@DELETE
|
||||
@@ -341,12 +344,13 @@ public class PersistenceResource implements RESTResource {
|
||||
}
|
||||
|
||||
private Response getItemHistoryDTO(@Nullable String serviceId, String itemName, @Nullable String timeBegin,
|
||||
@Nullable String timeEnd, int pageNumber, int pageLength, boolean boundary) {
|
||||
@Nullable String timeEnd, int pageNumber, int pageLength, boolean boundary, boolean itemState) {
|
||||
// Benchmarking timer...
|
||||
long timerStart = System.currentTimeMillis();
|
||||
|
||||
@Nullable
|
||||
ItemHistoryDTO dto = createDTO(serviceId, itemName, timeBegin, timeEnd, pageNumber, pageLength, boundary);
|
||||
ItemHistoryDTO dto = createDTO(serviceId, itemName, timeBegin, timeEnd, pageNumber, pageLength, boundary,
|
||||
itemState);
|
||||
|
||||
if (dto == null) {
|
||||
return JSONResponse.createErrorResponse(Status.BAD_REQUEST,
|
||||
@@ -359,7 +363,8 @@ public class PersistenceResource implements RESTResource {
|
||||
}
|
||||
|
||||
protected @Nullable ItemHistoryDTO createDTO(@Nullable String serviceId, String itemName,
|
||||
@Nullable String timeBegin, @Nullable String timeEnd, int pageNumber, int pageLength, boolean boundary) {
|
||||
@Nullable String timeBegin, @Nullable String timeEnd, int pageNumber, int pageLength, boolean boundary,
|
||||
boolean itemState) {
|
||||
// If serviceId is null, then use the default service
|
||||
PersistenceService service;
|
||||
String effectiveServiceId = serviceId != null ? serviceId : persistenceServiceRegistry.getDefaultId();
|
||||
@@ -465,6 +470,7 @@ public class PersistenceResource implements RESTResource {
|
||||
lastState = state;
|
||||
}
|
||||
|
||||
boolean addedBoundaryEnd = false;
|
||||
if (boundary) {
|
||||
// Get the value after the end time.
|
||||
FilterCriteria filterAfterEnd = new FilterCriteria();
|
||||
@@ -476,6 +482,31 @@ public class PersistenceResource implements RESTResource {
|
||||
if (result.iterator().hasNext()) {
|
||||
dto.addData(dateTimeEnd.toInstant().toEpochMilli(), result.iterator().next().getState());
|
||||
quantity++;
|
||||
addedBoundaryEnd = true;
|
||||
}
|
||||
}
|
||||
|
||||
// only add the item state if it was requested and the boundary end was not added
|
||||
// if the boundary end was added, there is no need to add the item state moved to the end time
|
||||
if (itemState && !addedBoundaryEnd) {
|
||||
try {
|
||||
long time = Instant.now().toEpochMilli();
|
||||
// if the current time is after the requested end time, move the item state to the end time
|
||||
if (time > dateTimeEnd.toInstant().toEpochMilli()) {
|
||||
time = dateTimeEnd.toInstant().toEpochMilli();
|
||||
}
|
||||
State state = itemRegistry.getItem(itemName).getState();
|
||||
if (state instanceof UnDefType) {
|
||||
logger.debug("State of item '{}' is undefined, not adding it to the response.", itemName);
|
||||
} else {
|
||||
logger.debug("Adding state of item '{}' to the response: {} - {}", itemName, time, state);
|
||||
dto.addData(time, state);
|
||||
quantity++;
|
||||
dto.sortData();
|
||||
}
|
||||
} catch (ItemNotFoundException e) {
|
||||
logger.debug("Item '{}' not found, not adding the state to the response.", itemName);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+53
-2
@@ -13,6 +13,7 @@
|
||||
package org.openhab.core.io.rest.core.internal.persistence;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.hamcrest.CoreMatchers.not;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.collection.IsCollectionWithSize.hasSize;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
@@ -40,6 +41,7 @@ import org.openhab.core.items.Item;
|
||||
import org.openhab.core.items.ItemNotFoundException;
|
||||
import org.openhab.core.items.ItemRegistry;
|
||||
import org.openhab.core.library.items.NumberItem;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.persistence.HistoricItem;
|
||||
import org.openhab.core.persistence.ModifiablePersistenceService;
|
||||
@@ -50,6 +52,7 @@ import org.openhab.core.persistence.internal.PersistenceManagerImpl;
|
||||
import org.openhab.core.persistence.registry.ManagedPersistenceServiceConfigurationProvider;
|
||||
import org.openhab.core.persistence.registry.PersistenceServiceConfigurationRegistry;
|
||||
import org.openhab.core.types.State;
|
||||
import org.openhab.core.types.UnDefType;
|
||||
|
||||
/**
|
||||
* Tests for PersistenceItem Restresource
|
||||
@@ -74,6 +77,7 @@ public class PersistenceResourceTest {
|
||||
private @Mock @NonNullByDefault({}) PersistenceServiceConfigurationRegistry persistenceServiceConfigurationRegistryMock;
|
||||
private @Mock @NonNullByDefault({}) ManagedPersistenceServiceConfigurationProvider managedPersistenceServiceConfigurationProviderMock;
|
||||
private @Mock @NonNullByDefault({}) TimeZoneProvider timeZoneProviderMock;
|
||||
private @Mock @NonNullByDefault({}) Item itemMock;
|
||||
|
||||
@BeforeEach
|
||||
public void beforeEach() {
|
||||
@@ -116,7 +120,7 @@ public class PersistenceResourceTest {
|
||||
|
||||
@Test
|
||||
public void testGetPersistenceItemData() {
|
||||
ItemHistoryDTO dto = pResource.createDTO(PERSISTENCE_SERVICE_ID, "testItem", null, null, 1, 10, false);
|
||||
ItemHistoryDTO dto = pResource.createDTO(PERSISTENCE_SERVICE_ID, "testItem", null, null, 1, 10, false, false);
|
||||
|
||||
assertThat(Integer.parseInt(dto.datapoints), is(5));
|
||||
assertThat(dto.data, hasSize(5));
|
||||
@@ -147,12 +151,59 @@ public class PersistenceResourceTest {
|
||||
|
||||
@Test
|
||||
public void testGetPersistenceItemDataWithBoundery() {
|
||||
ItemHistoryDTO dto = pResource.createDTO(PERSISTENCE_SERVICE_ID, "testItem", null, null, 1, 10, true);
|
||||
ItemHistoryDTO dto = pResource.createDTO(PERSISTENCE_SERVICE_ID, "testItem", null, null, 1, 10, true, false);
|
||||
|
||||
assertThat(Integer.parseInt(dto.datapoints), is(7));
|
||||
assertThat(dto.data, hasSize(7));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetPersistenceItemDataWithItemState() throws ItemNotFoundException {
|
||||
when(itemRegistryMock.getItem("testItem")).thenReturn(itemMock);
|
||||
when(itemMock.getState()).thenReturn(DecimalType.ZERO);
|
||||
|
||||
ItemHistoryDTO dto = pResource.createDTO(PERSISTENCE_SERVICE_ID, "testItem", null, null, 1, 10, false, true);
|
||||
|
||||
assertThat(Integer.parseInt(dto.datapoints), is(6));
|
||||
assertThat(dto.data, hasSize(6));
|
||||
assertThat(dto.data.get(dto.data.size() - 1).state, is("0"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetPersistenceItemDataWithItemStateUndefined() throws ItemNotFoundException {
|
||||
when(itemRegistryMock.getItem("testItem")).thenReturn(itemMock);
|
||||
when(itemMock.getState()).thenReturn(UnDefType.UNDEF);
|
||||
|
||||
ItemHistoryDTO dto = pResource.createDTO(PERSISTENCE_SERVICE_ID, "testItem", null, null, 1, 10, false, true);
|
||||
|
||||
assertThat(Integer.parseInt(dto.datapoints), is(5));
|
||||
assertThat(dto.data, hasSize(5));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetPersistenceItemDataWithItemStateNull() throws ItemNotFoundException {
|
||||
when(itemRegistryMock.getItem("testItem")).thenReturn(itemMock);
|
||||
when(itemMock.getState()).thenReturn(UnDefType.NULL);
|
||||
|
||||
ItemHistoryDTO dto = pResource.createDTO(PERSISTENCE_SERVICE_ID, "testItem", null, null, 1, 10, false, true);
|
||||
|
||||
assertThat(Integer.parseInt(dto.datapoints), is(5));
|
||||
assertThat(dto.data, hasSize(5));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetPersistenceItemDataWithBoundaryAndItemStateButNoItemStateRequired()
|
||||
throws ItemNotFoundException {
|
||||
when(itemRegistryMock.getItem("testItem")).thenReturn(itemMock);
|
||||
when(itemMock.getState()).thenReturn(DecimalType.ZERO);
|
||||
|
||||
ItemHistoryDTO dto = pResource.createDTO(PERSISTENCE_SERVICE_ID, "testItem", null, null, 1, 10, true, true);
|
||||
|
||||
assertThat(Integer.parseInt(dto.datapoints), is(7));
|
||||
assertThat(dto.data, hasSize(7));
|
||||
assertThat(dto.data.get(dto.data.size() - 1).state, not("0"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPutPersistenceItemData() throws ItemNotFoundException {
|
||||
HttpHeaders headersMock = mock(HttpHeaders.class);
|
||||
|
||||
+8
@@ -13,6 +13,7 @@
|
||||
package org.openhab.core.persistence.dto;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
@@ -57,6 +58,13 @@ public class ItemHistoryDTO {
|
||||
data.add(newVal);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort the data history by time.
|
||||
*/
|
||||
public void sortData() {
|
||||
data.sort(Comparator.comparingLong(o -> o.time));
|
||||
}
|
||||
|
||||
public static class HistoryDataBean {
|
||||
public long time;
|
||||
public String state;
|
||||
|
||||
Reference in New Issue
Block a user