Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable binding to store historic states #3000

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
*/
package org.openhab.core.persistence.internal;

import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Collection;
import java.util.Collections;
Expand All @@ -31,13 +32,14 @@
import org.openhab.core.common.SafeCaller;
import org.openhab.core.items.GenericItem;
import org.openhab.core.items.GroupItem;
import org.openhab.core.items.HistoricStateChangeListener;
import org.openhab.core.items.Item;
import org.openhab.core.items.ItemNotFoundException;
import org.openhab.core.items.ItemRegistry;
import org.openhab.core.items.ItemRegistryChangeListener;
import org.openhab.core.items.StateChangeListener;
import org.openhab.core.persistence.FilterCriteria;
import org.openhab.core.persistence.HistoricItem;
import org.openhab.core.persistence.ModifiablePersistenceService;
import org.openhab.core.persistence.PersistenceItemConfiguration;
import org.openhab.core.persistence.PersistenceManager;
import org.openhab.core.persistence.PersistenceService;
Expand Down Expand Up @@ -76,7 +78,7 @@
@Component(immediate = true, service = PersistenceManager.class)
@NonNullByDefault
public class PersistenceManagerImpl
implements ItemRegistryChangeListener, PersistenceManager, StateChangeListener, ReadyTracker {
implements ItemRegistryChangeListener, PersistenceManager, HistoricStateChangeListener, ReadyTracker {

private final Logger logger = LoggerFactory.getLogger(PersistenceManagerImpl.class);

Expand Down Expand Up @@ -158,6 +160,39 @@ private void handleStateEvent(Item item, boolean onlyChanges) {
}
}

/**
* Calls all persistence services which use change or update policy for the given item
*
* @param item the item to persist
* @param onlyChanges true, if it has the change strategy, false otherwise
*/
private void handleHistoricStateEvent(Item item, State state, ZonedDateTime dateTime, boolean onlyChanges) {
altaroca marked this conversation as resolved.
Show resolved Hide resolved
logger.debug("Persisting item '{}' historic state '{}' at {}", item.getName(), state.toString(), dateTime.toString());
synchronized (persistenceServiceConfigs) {
for (Entry<String, @Nullable PersistenceServiceConfiguration> entry : persistenceServiceConfigs
.entrySet()) {
final String serviceName = entry.getKey();
final PersistenceServiceConfiguration config = entry.getValue();
if (config != null && persistenceServices.containsKey(serviceName)
&& persistenceServices.get(serviceName) instanceof ModifiablePersistenceService) {
ModifiablePersistenceService service = (ModifiablePersistenceService) persistenceServices
.get(serviceName);
logger.debug(" Using ModifiablePersistenceService '{}'", serviceName);
for (PersistenceItemConfiguration itemConfig : config.getConfigs()) {
if (hasStrategy(config, itemConfig, onlyChanges ? PersistenceStrategy.Globals.CHANGE
: PersistenceStrategy.Globals.UPDATE)) {
logger.debug(" trying ItemConfig '{}'", itemConfig.toString());
if (appliesToItem(itemConfig, item)) {
logger.debug(" config applies"); // false ??
service.store(item, dateTime, state);
}
}
}
}
}
}
}

/**
* Checks if a given persistence configuration entry has a certain strategy for the given service
*
Expand Down Expand Up @@ -478,6 +513,11 @@ public void stateUpdated(Item item, State state) {
handleStateEvent(item, false);
}

@Override
public void historicStateUpdated(Item item, State state, ZonedDateTime dateTime) {
handleHistoricStateEvent(item, state, dateTime, false);
}

@Override
public void onReadyMarkerAdded(ReadyMarker readyMarker) {
ExecutorService scheduler = Executors.newSingleThreadExecutor(new NamedThreadFactory("persistenceManager"));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
*/
package org.openhab.core.thing.binding;

import java.time.ZonedDateTime;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
Expand Down Expand Up @@ -287,6 +288,39 @@ protected void updateState(String channelID, State state) {
updateState(channelUID, state);
}

/**
*
* Updates a historic state of the thing.
*
* @param channelUID unique id of the channel, which was updated
* @param state new state
*/
protected void updateHistoricState(ChannelUID channelUID, State state, ZonedDateTime dateTime) {
altaroca marked this conversation as resolved.
Show resolved Hide resolved
synchronized (this) {
if (this.callback != null) {
// TODO: check if this historic state is later than any existing state
altaroca marked this conversation as resolved.
Show resolved Hide resolved
this.callback.historicStateUpdated(channelUID, state, dateTime);
} else {
logger.warn(
"Handler {} of thing {} tried updating channel {} although the handler was already disposed.",
this.getClass().getSimpleName(), channelUID.getThingUID(), channelUID.getId());
}
}
}

/**
*
* Updates a historic state of the thing. Will use the thing UID to infer the
* unique channel UID from the given ID.
*
* @param channelID id of the channel, which was updated
* @param state new state
*/
protected void updateHistoricState(String channelID, State state, ZonedDateTime dateTime) {
altaroca marked this conversation as resolved.
Show resolved Hide resolved
ChannelUID channelUID = new ChannelUID(this.getThing().getUID(), channelID);
updateHistoricState(channelUID, state, dateTime);
}

/**
* Emits an event for the given channel.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
*/
package org.openhab.core.thing.binding;

import java.time.ZonedDateTime;
import java.util.List;
import java.util.Map;

Expand Down Expand Up @@ -57,6 +58,15 @@ public interface ThingHandlerCallback {
*/
void stateUpdated(ChannelUID channelUID, State state);

/**
* Informs about an update to a historic state for a channel.
*
* @param channelUID channel UID (must not be null)
altaroca marked this conversation as resolved.
Show resolved Hide resolved
* @param state state (must not be null)
* @param dateTime date time (must not be null)
*/
void historicStateUpdated(ChannelUID channelUID, State state, ZonedDateTime dateTime);

/**
* Informs about a command, which is sent from the channel.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
package org.openhab.core.thing.internal;

import java.time.Duration;
import java.time.ZonedDateTime;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
Expand Down Expand Up @@ -568,6 +569,16 @@ public void stateUpdated(ChannelUID channelUID, State state) {
});
}

public void historicStateUpdated(ChannelUID channelUID, State state, ZonedDateTime dateTime) {
final Thing thing = getThing(channelUID.getThingUID());

handleCallFromHandler(channelUID, thing, profile -> {
if (profile instanceof StateProfile) {
((StateProfile) profile).onStateUpdateFromHandler(new HistoricState(state, dateTime));
}
});
}

public void postCommand(ChannelUID channelUID, Command command) {
final Thing thing = getThing(channelUID.getThingUID());

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/**
* Copyright (c) 2010-2022 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.internal;

import java.time.ZonedDateTime;

import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.types.State;

public class HistoricState implements State {
altaroca marked this conversation as resolved.
Show resolved Hide resolved

private State state;
private ZonedDateTime dateTime;

HistoricState(State state, ZonedDateTime dateTime) {
this.state = state;
this.dateTime = dateTime;
}

public State getState() {
return state;
}

public ZonedDateTime getDateTime() {
return dateTime;
}

@Override
public String format(String pattern) {
return state.format(pattern);
}

@Override
public String toFullString() {
return state.toFullString();
}

@Override
public <T extends @Nullable State> @Nullable T as(@Nullable Class<T> target) {
return state.as(target);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.text.MessageFormat;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
Expand Down Expand Up @@ -176,6 +177,11 @@ public void stateUpdated(ChannelUID channelUID, State state) {
communicationManager.stateUpdated(channelUID, state);
}

@Override
public void historicStateUpdated(ChannelUID channelUID, State state, ZonedDateTime dateTime) {
communicationManager.historicStateUpdated(channelUID, state, dateTime);
}

@Override
public void postCommand(ChannelUID channelUID, Command command) {
communicationManager.postCommand(channelUID, command);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
*/
package org.openhab.core.thing.internal.profiles;

import java.time.ZonedDateTime;
import java.util.function.Function;

import org.eclipse.jdt.annotation.NonNullByDefault;
Expand All @@ -20,13 +21,15 @@
import org.openhab.core.events.EventPublisher;
import org.openhab.core.items.Item;
import org.openhab.core.items.ItemStateConverter;
import org.openhab.core.items.events.ItemEvent;
import org.openhab.core.items.events.ItemEventFactory;
import org.openhab.core.library.items.StringItem;
import org.openhab.core.library.types.StringType;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingUID;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.thing.internal.CommunicationManager;
import org.openhab.core.thing.internal.HistoricState;
import org.openhab.core.thing.link.ItemChannelLink;
import org.openhab.core.thing.profiles.ProfileCallback;
import org.openhab.core.thing.util.ThingHandlerHelper;
Expand Down Expand Up @@ -111,6 +114,12 @@ public void sendUpdate(State state) {
return;
}

ZonedDateTime dateTime = null;
if (state instanceof HistoricState) {
dateTime = ((HistoricState) state).getDateTime();
state = ((HistoricState) state).getState();
}

State acceptedState;
if (state instanceof StringType && !(item instanceof StringItem)) {
acceptedState = TypeParser.parseState(item.getAcceptedDataTypes(), state.toString());
Expand All @@ -121,7 +130,15 @@ public void sendUpdate(State state) {
acceptedState = itemStateConverter.convertToAcceptedState(state, item);
}

eventPublisher.post(
ItemEventFactory.createStateEvent(link.getItemName(), acceptedState, link.getLinkedUID().toString()));
ItemEvent event;
if (dateTime != null) {
event = ItemEventFactory.createHistoricStateEvent(link.getItemName(), acceptedState, dateTime,
link.getLinkedUID().toString());
} else {
event = ItemEventFactory.createStateEvent(link.getItemName(), acceptedState,
link.getLinkedUID().toString());
}

eventPublisher.post(event);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
*/
package org.openhab.core.internal.items;

import java.time.ZonedDateTime;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.events.EventSubscriber;
import org.openhab.core.items.GenericItem;
Expand All @@ -21,6 +23,7 @@
import org.openhab.core.items.ItemRegistry;
import org.openhab.core.items.events.AbstractItemEventSubscriber;
import org.openhab.core.items.events.ItemCommandEvent;
import org.openhab.core.items.events.ItemHistoricStateEvent;
import org.openhab.core.items.events.ItemStateEvent;
import org.openhab.core.types.State;
import org.osgi.service.component.annotations.Activate;
Expand Down Expand Up @@ -97,4 +100,41 @@ protected void receiveCommand(ItemCommandEvent commandEvent) {
logger.debug("Received command for non-existing item: {}", e.getMessage());
}
}

@Override
protected void receiveHistoricState(ItemHistoricStateEvent event) {
altaroca marked this conversation as resolved.
Show resolved Hide resolved
String itemName = event.getItemName();
State newState = event.getItemState();
ZonedDateTime dateTime = event.getDateTime();
try {
GenericItem item = (GenericItem) itemRegistry.getItem(itemName);
boolean isAccepted = false;
if (item.getAcceptedDataTypes().contains(newState.getClass())) {
isAccepted = true;
} else {
// Look for class hierarchy
for (Class<? extends State> state : item.getAcceptedDataTypes()) {
try {
if (!state.isEnum() && state.getDeclaredConstructor().newInstance().getClass()
.isAssignableFrom(newState.getClass())) {
isAccepted = true;
break;
}
} catch (ReflectiveOperationException e) {
// Should never happen
logger.warn("{} while creating {} instance: {}", e.getClass().getSimpleName(),
state.getClass().getSimpleName(), e.getMessage());
}
}
}
if (isAccepted) {
item.setHistoricState(newState, dateTime);
} else {
logger.debug("Received update of a not accepted type ({}) for item {}",
newState.getClass().getSimpleName(), itemName);
}
} catch (ItemNotFoundException e) {
logger.debug("Received update for non-existing item: {}", e.getMessage());
}
}
}
Loading