Skip to content
Draft
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
@@ -0,0 +1,134 @@
/*
* Copyright (c) 2010-2025 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.automation.module.script.action;

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

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.items.Item;
import org.openhab.core.types.Command;
import org.openhab.core.types.State;
import org.openhab.core.types.TimeSeries;

/**
* The {@link BusEvent} allows write access to the openHAB event bus from within scripts.
* Items should not be updated directly (setting the state property), but updates should
* be sent to the bus, so that all interested bundles are notified.
*
* @author Florian Hotze - Initial contribution
*/
@NonNullByDefault
public interface BusEvent {
/**
* Sends a command for a specified item to the event bus.
*
* @param item the item to send the command to
* @param commandString the command to send
*/
void sendCommand(Item item, String commandString);

/**
* Sends a number as a command for a specified item to the event bus.
*
* @param item the item to send the command to
* @param command the number to send as a command
*/
void sendCommand(Item item, Number command);

/**
* Sends a command for a specified item to the event bus.
*
* @param itemName the name of the item to send the command to
* @param commandString the command to send
*/
void sendCommand(String itemName, String commandString);

/**
* Sends a command for a specified item to the event bus.
*
* @param item the item to send the command to
* @param command the command to send
*/
void sendCommand(Item item, Command command);

/**
* Posts a status update for a specified item to the event bus.
*
* @param item the item to send the status update for
* @param stateString the new state of the item
*/
void postUpdate(Item item, String stateString);

/**
* Posts a status update for a specified item to the event bus.
*
* @param item the item to send the status update for
* @param state the new state of the item as a number
*/
void postUpdate(Item item, Number state);

/**
* Posts a status update for a specified item to the event bus.
*
* @param itemName the name of the item to send the status update for
* @param stateString the new state of the item
*/
void postUpdate(String itemName, String stateString);

/**
* Posts a status update for a specified item to the event bus.
*
* @param item the item to send the status update for
* @param state the new state of the item
*/
void postUpdate(Item item, State state);

/**
* Sends a time series to the event bus
*
* @param item the item to send the time series for
* @param timeSeries a {@link TimeSeries} containing policy and values
*/
void sendTimeSeries(@Nullable Item item, @Nullable TimeSeries timeSeries);

/**
* Sends a time series to the event bus
*
* @param itemName the name of the item to send the status update for
* @param values a {@link Map} containing the timeseries, composed of pairs of {@link ZonedDateTime} and
* {@link State}
* @param policy either <code>ADD</code> or <code>REPLACE</code>
*/
void sendTimeSeries(@Nullable String itemName, @Nullable Map<ZonedDateTime, State> values, String policy);

/**
* Stores the current states for a list of items in a map.
* A group item is not itself put into the map, but instead all its members.
*
* @param items the items for which the state should be stored
* @return the map of items with their states
*/
Map<Item, State> storeStates(Item... items);

/**
* Restores item states from a map.
* If the saved state can be interpreted as a command, a command is sent for the item
* (and the physical device can send a status update if occurred). If it is no valid
* command, the item state is directly updated to the saved value.
*
* @param statesMap a map with ({@link Item}, {@link State}) entries
*/
void restoreStates(Map<Item, State> statesMap);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
/*
* Copyright (c) 2010-2025 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.automation.module.script.internal.action;

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

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.automation.module.script.action.BusEvent;
import org.openhab.core.events.EventPublisher;
import org.openhab.core.items.GroupItem;
import org.openhab.core.items.Item;
import org.openhab.core.items.ItemNotFoundException;
import org.openhab.core.items.ItemRegistry;
import org.openhab.core.items.events.ItemEventFactory;
import org.openhab.core.types.Command;
import org.openhab.core.types.State;
import org.openhab.core.types.TimeSeries;
import org.openhab.core.types.TypeParser;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* The static methods of this class are made available as functions in the scripts.
* This allows a script to write to the openHAB event bus.
*
* @author Kai Kreuzer - Initial contribution
*/
@Component(immediate = true, service = BusEvent.class)
@NonNullByDefault
public class BusEventImpl implements BusEvent {

private final Logger logger = LoggerFactory.getLogger(BusEventImpl.class);
private final ItemRegistry itemRegistry;
private final EventPublisher publisher;

@Activate
public BusEventImpl(final @Reference ItemRegistry itemRegistry, final @Reference EventPublisher publisher) {
this.itemRegistry = itemRegistry;
this.publisher = publisher;
}

@Override
public void sendCommand(Item item, String commandString) {
if (item != null) {
sendCommand(item.getName(), commandString);
}
}

@Override
public void sendCommand(Item item, Number command) {
if (item != null && command != null) {
sendCommand(item.getName(), command.toString());
}
}

@Override
public void sendCommand(String itemName, String commandString) {
try {
Item item = itemRegistry.getItem(itemName);
Command command = TypeParser.parseCommand(item.getAcceptedCommandTypes(), commandString);
if (command != null) {
publisher.post(ItemEventFactory.createCommandEvent(itemName, command));
} else {
logger.warn("Cannot convert '{}' to a command type which item '{}' accepts: {}.", commandString,
itemName, getAcceptedCommandNames(item));
}
} catch (ItemNotFoundException e) {
logger.warn("Item '{}' does not exist.", itemName);
}
}

private static <T extends State> List<String> getAcceptedCommandNames(Item item) {
return item.getAcceptedCommandTypes().stream().map(Class::getSimpleName).toList();
}

@Override
public void sendCommand(Item item, Command command) {
if (item != null) {
publisher.post(ItemEventFactory.createCommandEvent(item.getName(), command));
}
}

@Override
public void postUpdate(Item item, String stateString) {
if (item != null) {
postUpdate(item.getName(), stateString);
}
}

@Override
public void postUpdate(Item item, Number state) {
if (item != null && state != null) {
postUpdate(item.getName(), state.toString());
}
}

private static <T extends State> List<String> getAcceptedDataTypeNames(Item item) {
return item.getAcceptedDataTypes().stream().map(Class::getSimpleName).toList();
}

@Override
public void postUpdate(String itemName, String stateString) {
try {
Item item = itemRegistry.getItem(itemName);
State state = TypeParser.parseState(item.getAcceptedDataTypes(), stateString);
if (state != null) {
publisher.post(ItemEventFactory.createStateEvent(itemName, state));
} else {
logger.warn("Cannot convert '{}' to a state type which item '{}' accepts: {}.", stateString, itemName,
getAcceptedDataTypeNames(item));
}
} catch (ItemNotFoundException e) {
logger.warn("Item '{}' does not exist.", itemName);
}
}

@Override
public void postUpdate(Item item, State state) {
if (item != null) {
publisher.post(ItemEventFactory.createStateEvent(item.getName(), state));
}
}

@Override
public void sendTimeSeries(@Nullable Item item, @Nullable TimeSeries timeSeries) {
if (item != null && timeSeries != null) {
publisher.post(ItemEventFactory.createTimeSeriesEvent(item.getName(), timeSeries, null));
}
}

@Override
public void sendTimeSeries(@Nullable String itemName, @Nullable Map<ZonedDateTime, State> values, String policy) {
if (itemName != null && values != null) {
try {
TimeSeries timeSeries = new TimeSeries(TimeSeries.Policy.valueOf(policy));
values.forEach((key, value) -> timeSeries.add(key.toInstant(), value));
publisher.post(ItemEventFactory.createTimeSeriesEvent(itemName, timeSeries, null));
} catch (IllegalArgumentException e) {
logger.warn("Policy '{}' does not exist.", policy);
}
}
}

@Override
public Map<Item, State> storeStates(Item... items) {
Map<Item, State> statesMap = new HashMap<>();
if (items != null) {
for (Item item : items) {
if (item instanceof GroupItem groupItem) {
for (Item member : groupItem.getAllMembers()) {
statesMap.put(member, member.getState());
}
} else {
statesMap.put(item, item.getState());
}
}
}
return statesMap;
}

@Override
public void restoreStates(Map<Item, State> statesMap) {
if (statesMap != null) {
for (Map.Entry<Item, State> entry : statesMap.entrySet()) {
if (entry.getValue() instanceof Command) {
sendCommand(entry.getKey(), (Command) entry.getValue());
} else {
postUpdate(entry.getKey(), entry.getValue());
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.automation.module.script.ScriptExtensionProvider;
import org.openhab.core.automation.module.script.action.BusEvent;
import org.openhab.core.automation.module.script.action.ScriptExecution;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
Expand All @@ -38,8 +39,9 @@ public class ScriptActionScriptScopeProvider implements ScriptExtensionProvider
private final Map<String, Object> elements;

@Activate
public ScriptActionScriptScopeProvider(final @Reference ScriptExecution scriptExecution) {
elements = Map.of("scriptExecution", scriptExecution);
public ScriptActionScriptScopeProvider(final @Reference BusEvent busEvent,
final @Reference ScriptExecution scriptExecution) {
elements = Map.of("busEvent", busEvent, "scriptExecution", scriptExecution);
}

@Override
Expand Down Expand Up @@ -72,6 +74,6 @@ public Map<String, Object> importPreset(String scriptIdentifier, String preset)

@Override
public void unload(String scriptIdentifier) {
// nothing todo
// nothing to do
}
}
Loading