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 all commits
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 @@ -38,6 +39,7 @@
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 @@ -158,6 +160,40 @@ 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 state the state
* @param dateTime the date time when the state is valid
*/
private void handleHistoricStateEvent(Item item, State state, ZonedDateTime dateTime) {
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, PersistenceStrategy.Globals.UPDATE)) {
logger.debug(" trying ItemConfig '{}'", itemConfig.toString());
if (appliesToItem(itemConfig, item)) {
logger.debug(" config applies");
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 +514,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);
}

@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,40 @@ 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 the state
* @param dateTime the date time when the state is valid
*/
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) {
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 the state
* @param dateTime the date time when the state is valid
*/
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 @@ -52,11 +53,20 @@ public interface ThingHandlerCallback {
/**
* Informs about an updated state for a channel.
*
* @param channelUID channel UID (must not be null)
* @param state state (must not be null)
* @param channelUID channel UID
* @param state state
*/
void stateUpdated(ChannelUID channelUID, State state);

/**
* Informs about an update to a historic state for a channel.
*
* @param channelUID channel UID
* @param state state
* @param dateTime date time
*/
void historicStateUpdated(ChannelUID channelUID, State state, ZonedDateTime dateTime);

/**
* Informs about a command, which is sent from the channel.
*
Expand All @@ -68,23 +78,23 @@ public interface ThingHandlerCallback {
/**
* Informs about an updated status of a thing.
*
* @param thing thing (must not be null)
* @param thingStatus thing status (must not be null)
* @param thing thing
* @param thingStatus thing status
*/
void statusUpdated(Thing thing, ThingStatusInfo thingStatus);

/**
* Informs about an update of the whole thing.
*
* @param thing thing that was updated (must not be null)
* @param thing thing that was updated
* @throws IllegalStateException if the {@link Thing} is read-only.
*/
void thingUpdated(Thing thing);

/**
* Validates the given configuration parameters against the configuration description.
*
* @param thing thing with the updated configuration (must not be null)
* @param thing thing with the updated configuration
* @param configurationParameters the configuration parameters to be validated
* @throws ConfigValidationException if one or more of the given configuration parameters do not match
* their declarations in the configuration description
Expand All @@ -94,7 +104,7 @@ public interface ThingHandlerCallback {
/**
* Validates the given configuration parameters against the configuration description.
*
* @param channel channel with the updated configuration (must not be null)
* @param channel channel with the updated configuration
* @param configurationParameters the configuration parameters to be validated
* @throws ConfigValidationException if one or more of the given configuration parameters do not match
* their declarations in the configuration description
Expand Down Expand Up @@ -129,16 +139,16 @@ public interface ThingHandlerCallback {
/**
* Informs the framework that the ThingType of the given {@link Thing} should be changed.
*
* @param thing thing that should be migrated to another ThingType (must not be null)
* @param thingTypeUID the new type of the thing (must not be null)
* @param thing thing that should be migrated to another ThingType
* @param thingTypeUID the new type of the thing
* @param configuration a configuration that should be applied to the given {@link Thing}
*/
void migrateThingType(Thing thing, ThingTypeUID thingTypeUID, Configuration configuration);

/**
* Informs the framework that a channel has been triggered.
*
* @param thing thing (must not be null)
* @param thing thing
* @param channelUID UID of the channel over which has been triggered.
* @param event Event.
*/
Expand All @@ -159,7 +169,7 @@ public interface ThingHandlerCallback {
* modify it. The methods {@link BaseThingHandler#editThing(Thing)} and {@link BaseThingHandler#updateThing(Thing)}
* must be called to persist the changes.
*
* @param thing {@link Thing} (must not be null)
* @param thing {@link Thing}
* @param channelUID the UID of the {@link Channel} to be edited
* @return a preconfigured {@link ChannelBuilder}
* @throws IllegalArgumentException if no {@link Channel} with the given UID exists for the given {@link Thing}
Expand All @@ -181,15 +191,15 @@ List<ChannelBuilder> createChannelBuilders(ChannelGroupUID channelGroupUID,
/**
* Returns whether at least one item is linked for the given UID of the channel.
*
* @param channelUID UID of the channel (must not be null)
* @param channelUID UID of the channel
* @return true if at least one item is linked, false otherwise
*/
boolean isChannelLinked(ChannelUID channelUID);

/**
* Returns the bridge of the thing.
*
* @param bridgeUID {@link ThingUID} UID of the bridge (must not be null)
* @param bridgeUID {@link ThingUID} UID of the bridge
* @return returns the bridge of the thing or null if the thing has no bridge
*/
@Nullable
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,60 @@
/**
* 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.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.types.State;

/**
* A wrapper class for {@link State} which represents an item state plus a date time when the state is valid.
*
* @author Jan M. Hochstein
*
*/
@NonNullByDefault
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);
}
}
Loading