]> git.basschouten.com Git - openhab-addons.git/blob
7e0c8f99472171fe7878ea660ee627362b9d6a8c
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2024 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
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
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.transform.basicprofiles.internal.profiles;
14
15 import static org.openhab.transform.basicprofiles.internal.factory.BasicProfilesFactory.STATE_FILTER_UID;
16
17 import java.util.ArrayList;
18 import java.util.List;
19 import java.util.Locale;
20
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;
37
38 /**
39  * Accepts updates to state as long as conditions are met. Support for sending fixed state if conditions are *not*
40  * met.
41  *
42  * @author Arne Seime - Initial contribution
43  */
44 @NonNullByDefault
45 public class StateFilterProfile implements StateProfile {
46
47     private final Logger logger = LoggerFactory.getLogger(StateFilterProfile.class);
48
49     private final ItemRegistry itemRegistry;
50     private final ProfileCallback callback;
51     private List<Class<? extends State>> acceptedDataTypes;
52
53     private List<StateCondition> conditions = List.of();
54
55     private @Nullable State configMismatchState = null;
56
57     public StateFilterProfile(ProfileCallback callback, ProfileContext context, ItemRegistry itemRegistry) {
58         this.callback = callback;
59         acceptedDataTypes = context.getAcceptedDataTypes();
60         this.itemRegistry = itemRegistry;
61
62         StateFilterProfileConfig config = context.getConfiguration().as(StateFilterProfileConfig.class);
63         if (config != null) {
64             conditions = parseConditions(config.conditions, config.separator);
65             configMismatchState = parseState(config.mismatchState);
66         }
67     }
68
69     private List<StateCondition> parseConditions(@Nullable String config, String separator) {
70         if (config == null) {
71             return List.of();
72         }
73
74         List<StateCondition> parsedConditions = new ArrayList<>();
75         try {
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));
85                 } else {
86                     logger.warn("Malformed condition expression: '{}'", expression);
87                 }
88             }
89
90             return parsedConditions;
91         } catch (IllegalArgumentException e) {
92             logger.warn("Cannot parse condition {}. Expected format ITEM_NAME <EQ|NEQ> STATE_VALUE: '{}'", config,
93                     e.getMessage());
94             return List.of();
95         }
96     }
97
98     @Override
99     public ProfileTypeUID getProfileTypeUID() {
100         return STATE_FILTER_UID;
101     }
102
103     @Override
104     public void onStateUpdateFromItem(State state) {
105         // do nothing
106     }
107
108     @Override
109     public void onCommandFromItem(Command command) {
110         callback.handleCommand(command);
111     }
112
113     @Override
114     public void onCommandFromHandler(Command command) {
115         callback.sendCommand(command);
116     }
117
118     @Override
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);
124         } else {
125             logger.debug("Received state update from handler: {}, not forwarded to item", state);
126         }
127     }
128
129     @Nullable
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);
135                 try {
136                     Item item = itemRegistry.getItem(condition.itemName);
137                     String itemState = item.getState().toString();
138
139                     if (!condition.matches(itemState)) {
140                         allConditionsMet = false;
141                     }
142                 } catch (ItemNotFoundException e) {
143                     logger.warn(
144                             "Cannot find item '{}' in registry - check your condition expression - skipping state update",
145                             condition.itemName);
146                     allConditionsMet = false;
147                 }
148
149             }
150             if (allConditionsMet) {
151                 return state;
152             } else {
153                 return configMismatchState;
154             }
155         } else {
156             logger.warn(
157                     "No configuration defined for StateFilterProfile (check for log messages when instantiating profile) - skipping state update");
158         }
159
160         return null;
161     }
162
163     @Nullable
164     State parseState(@Nullable String stateString) {
165         // Quoted strings are parsed as StringType
166         if (stateString == null) {
167             return null;
168         } else if (stateString.startsWith("'") && stateString.endsWith("'")) {
169             return new StringType(stateString.substring(1, stateString.length() - 1));
170         } else {
171             return TypeParser.parseState(acceptedDataTypes, stateString);
172         }
173     }
174
175     class StateCondition {
176         String itemName;
177
178         ComparisonType comparisonType;
179         String value;
180
181         boolean quoted = false;
182
183         public StateCondition(String itemName, ComparisonType comparisonType, String value) {
184             this.itemName = itemName;
185             this.comparisonType = comparisonType;
186             this.value = value;
187             this.quoted = value.startsWith("'") && value.endsWith("'");
188             if (quoted) {
189                 this.value = value.substring(1, value.length() - 1);
190             }
191         }
192
193         public boolean matches(String state) {
194             switch (comparisonType) {
195                 case EQ:
196                     return state.equals(value);
197                 case NEQ: {
198                     return !state.equals(value);
199                 }
200                 default:
201                     logger.warn("Unknown condition type {}. Expected 'eq' or 'neq' - skipping state update",
202                             comparisonType);
203                     return false;
204
205             }
206         }
207
208         enum ComparisonType {
209             EQ,
210             NEQ
211         }
212
213         @Override
214         public String toString() {
215             return "Condition{itemName='" + itemName + "', comparisonType=" + comparisonType + ", value='" + value
216                     + "'}'";
217         }
218     }
219 }