2 * Copyright (c) 2010-2024 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
7 * This program and the accompanying materials are made available under the
8 * terms of the Eclipse Public License 2.0 which is available at
9 * http://www.eclipse.org/legal/epl-2.0
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.transform.basicprofiles.internal.profiles;
15 import static org.openhab.transform.basicprofiles.internal.factory.BasicProfilesFactory.STATE_FILTER_UID;
17 import java.util.ArrayList;
18 import java.util.List;
19 import java.util.Locale;
21 import org.eclipse.jdt.annotation.NonNullByDefault;
22 import org.eclipse.jdt.annotation.Nullable;
23 import org.openhab.core.items.Item;
24 import org.openhab.core.items.ItemNotFoundException;
25 import org.openhab.core.items.ItemRegistry;
26 import org.openhab.core.library.types.StringType;
27 import org.openhab.core.thing.profiles.ProfileCallback;
28 import org.openhab.core.thing.profiles.ProfileContext;
29 import org.openhab.core.thing.profiles.ProfileTypeUID;
30 import org.openhab.core.thing.profiles.StateProfile;
31 import org.openhab.core.types.Command;
32 import org.openhab.core.types.State;
33 import org.openhab.core.types.TypeParser;
34 import org.openhab.transform.basicprofiles.internal.config.StateFilterProfileConfig;
35 import org.slf4j.Logger;
36 import org.slf4j.LoggerFactory;
39 * Accepts updates to state as long as conditions are met. Support for sending fixed state if conditions are *not*
42 * @author Arne Seime - Initial contribution
45 public class StateFilterProfile implements StateProfile {
47 private final Logger logger = LoggerFactory.getLogger(StateFilterProfile.class);
49 private final ItemRegistry itemRegistry;
50 private final ProfileCallback callback;
51 private List<Class<? extends State>> acceptedDataTypes;
53 private List<StateCondition> conditions = List.of();
55 private @Nullable State configMismatchState = null;
57 public StateFilterProfile(ProfileCallback callback, ProfileContext context, ItemRegistry itemRegistry) {
58 this.callback = callback;
59 acceptedDataTypes = context.getAcceptedDataTypes();
60 this.itemRegistry = itemRegistry;
62 StateFilterProfileConfig config = context.getConfiguration().as(StateFilterProfileConfig.class);
64 conditions = parseConditions(config.conditions, config.separator);
65 configMismatchState = parseState(config.mismatchState);
69 private List<StateCondition> parseConditions(@Nullable String config, String separator) {
74 List<StateCondition> parsedConditions = new ArrayList<>();
76 String[] expressions = config.split(separator);
77 for (String expression : expressions) {
78 String[] parts = expression.trim().split("\s");
79 if (parts.length == 3) {
80 String itemName = parts[0];
81 StateCondition.ComparisonType conditionType = StateCondition.ComparisonType
82 .valueOf(parts[1].toUpperCase(Locale.ROOT));
83 String value = parts[2];
84 parsedConditions.add(new StateCondition(itemName, conditionType, value));
86 logger.warn("Malformed condition expression: '{}'", expression);
90 return parsedConditions;
91 } catch (IllegalArgumentException e) {
92 logger.warn("Cannot parse condition {}. Expected format ITEM_NAME <EQ|NEQ> STATE_VALUE: '{}'", config,
99 public ProfileTypeUID getProfileTypeUID() {
100 return STATE_FILTER_UID;
104 public void onStateUpdateFromItem(State state) {
109 public void onCommandFromItem(Command command) {
110 callback.handleCommand(command);
114 public void onCommandFromHandler(Command command) {
115 callback.sendCommand(command);
119 public void onStateUpdateFromHandler(State state) {
120 State resultState = checkCondition(state);
121 if (resultState != null) {
122 logger.debug("Received state update from handler: {}, forwarded as {}", state, resultState);
123 callback.sendUpdate(resultState);
125 logger.debug("Received state update from handler: {}, not forwarded to item", state);
130 private State checkCondition(State state) {
131 if (!conditions.isEmpty()) {
132 boolean allConditionsMet = true;
133 for (StateCondition condition : conditions) {
134 logger.debug("Evaluting condition: {}", condition);
136 Item item = itemRegistry.getItem(condition.itemName);
137 String itemState = item.getState().toString();
139 if (!condition.matches(itemState)) {
140 allConditionsMet = false;
142 } catch (ItemNotFoundException e) {
144 "Cannot find item '{}' in registry - check your condition expression - skipping state update",
146 allConditionsMet = false;
150 if (allConditionsMet) {
153 return configMismatchState;
157 "No configuration defined for StateFilterProfile (check for log messages when instantiating profile) - skipping state update");
164 State parseState(@Nullable String stateString) {
165 // Quoted strings are parsed as StringType
166 if (stateString == null) {
168 } else if (stateString.startsWith("'") && stateString.endsWith("'")) {
169 return new StringType(stateString.substring(1, stateString.length() - 1));
171 return TypeParser.parseState(acceptedDataTypes, stateString);
175 class StateCondition {
178 ComparisonType comparisonType;
181 boolean quoted = false;
183 public StateCondition(String itemName, ComparisonType comparisonType, String value) {
184 this.itemName = itemName;
185 this.comparisonType = comparisonType;
187 this.quoted = value.startsWith("'") && value.endsWith("'");
189 this.value = value.substring(1, value.length() - 1);
193 public boolean matches(String state) {
194 switch (comparisonType) {
196 return state.equals(value);
198 return !state.equals(value);
201 logger.warn("Unknown condition type {}. Expected 'eq' or 'neq' - skipping state update",
208 enum ComparisonType {
214 public String toString() {
215 return "Condition{itemName='" + itemName + "', comparisonType=" + comparisonType + ", value='" + value