[DSL rule] Add conditions to DSL Rule file syntax (#5453)

Supports all conditions except the execution of script.

Signed-off-by: Laurent Garnier <lg.hc@free.fr>
This commit is contained in:
lolodomo
2026-04-12 09:17:15 +02:00
committed by GitHub
parent 03e96eca13
commit 2031d1e7e9
3 changed files with 405 additions and 141 deletions
@@ -18,6 +18,7 @@ import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
@@ -29,22 +30,30 @@ import org.eclipse.xtext.nodemodel.util.NodeModelUtils;
import org.eclipse.xtext.xbase.XExpression;
import org.eclipse.xtext.xbase.interpreter.IEvaluationContext;
import org.openhab.core.automation.Action;
import org.openhab.core.automation.Condition;
import org.openhab.core.automation.Rule;
import org.openhab.core.automation.RuleProvider;
import org.openhab.core.automation.Trigger;
import org.openhab.core.automation.internal.module.handler.ChannelEventTriggerHandler;
import org.openhab.core.automation.internal.module.handler.DateTimeTriggerHandler;
import org.openhab.core.automation.internal.module.handler.DayOfWeekConditionHandler;
import org.openhab.core.automation.internal.module.handler.EphemerisConditionHandler;
import org.openhab.core.automation.internal.module.handler.GenericCronTriggerHandler;
import org.openhab.core.automation.internal.module.handler.GroupCommandTriggerHandler;
import org.openhab.core.automation.internal.module.handler.GroupStateTriggerHandler;
import org.openhab.core.automation.internal.module.handler.IntervalConditionHandler;
import org.openhab.core.automation.internal.module.handler.ItemCommandTriggerHandler;
import org.openhab.core.automation.internal.module.handler.ItemStateConditionHandler;
import org.openhab.core.automation.internal.module.handler.ItemStateTriggerHandler;
import org.openhab.core.automation.internal.module.handler.SystemTriggerHandler;
import org.openhab.core.automation.internal.module.handler.ThingStatusConditionHandler;
import org.openhab.core.automation.internal.module.handler.ThingStatusTriggerHandler;
import org.openhab.core.automation.internal.module.handler.TimeOfDayConditionHandler;
import org.openhab.core.automation.internal.module.handler.TimeOfDayTriggerHandler;
import org.openhab.core.automation.module.script.internal.handler.AbstractScriptModuleHandler;
import org.openhab.core.automation.module.script.internal.handler.ScriptActionHandler;
import org.openhab.core.automation.util.ActionBuilder;
import org.openhab.core.automation.util.ConditionBuilder;
import org.openhab.core.automation.util.RuleBuilder;
import org.openhab.core.automation.util.TriggerBuilder;
import org.openhab.core.common.registry.ProviderChangeListener;
@@ -56,19 +65,27 @@ import org.openhab.core.model.rule.jvmmodel.RulesRefresher;
import org.openhab.core.model.rule.rules.ChangedEventTrigger;
import org.openhab.core.model.rule.rules.CommandEventTrigger;
import org.openhab.core.model.rule.rules.DateTimeTrigger;
import org.openhab.core.model.rule.rules.DayOfWeekCondition;
import org.openhab.core.model.rule.rules.EventEmittedTrigger;
import org.openhab.core.model.rule.rules.EventTrigger;
import org.openhab.core.model.rule.rules.GroupMemberChangedEventTrigger;
import org.openhab.core.model.rule.rules.GroupMemberCommandEventTrigger;
import org.openhab.core.model.rule.rules.GroupMemberUpdateEventTrigger;
import org.openhab.core.model.rule.rules.HolidayCondition;
import org.openhab.core.model.rule.rules.InDaysetCondition;
import org.openhab.core.model.rule.rules.IntervalCondition;
import org.openhab.core.model.rule.rules.ItemStateCondition;
import org.openhab.core.model.rule.rules.RuleModel;
import org.openhab.core.model.rule.rules.SystemOnShutdownTrigger;
import org.openhab.core.model.rule.rules.SystemOnStartupTrigger;
import org.openhab.core.model.rule.rules.SystemStartlevelTrigger;
import org.openhab.core.model.rule.rules.ThingStateChangedEventTrigger;
import org.openhab.core.model.rule.rules.ThingStateUpdateEventTrigger;
import org.openhab.core.model.rule.rules.ThingStatusCondition;
import org.openhab.core.model.rule.rules.TimeOfDayCondition;
import org.openhab.core.model.rule.rules.TimerTrigger;
import org.openhab.core.model.rule.rules.UpdateEventTrigger;
import org.openhab.core.model.rule.rules.WeekdayCondition;
import org.openhab.core.model.script.runtime.DSLScriptContextProvider;
import org.openhab.core.model.script.script.Script;
import org.openhab.core.service.ReadyMarker;
@@ -85,9 +102,9 @@ import org.slf4j.LoggerFactory;
/**
* This RuleProvider provides rules that are defined in DSL rule files.
* All rules consist out of a list of triggers and a single script action.
* No rule conditions are used as this concept does not exist for DSL rules.
*
* @author Kai Kreuzer - Initial contribution
* @author Laurent Garnier - Add support for conditions
*/
@NonNullByDefault
@Component(immediate = true, service = { DSLRuleProvider.class, RuleProvider.class, DSLScriptContextProvider.class })
@@ -274,13 +291,12 @@ public class DSLRuleProvider
// Create Triggers
triggerId = 0;
List<Trigger> triggers = new ArrayList<>();
for (EventTrigger t : rule.getEventtrigger()) {
Trigger trigger = mapTrigger(t);
if (trigger != null) {
triggers.add(trigger);
}
}
List<Trigger> triggers = rule.getEventtrigger().stream().map(this::mapTrigger).filter(Objects::nonNull)
.toList();
// Conditions
List<Condition> conditions = rule.getConditions().stream().map(this::mapCondition).filter(Objects::nonNull)
.toList();
// Create Action
String context = DSLScriptContextProvider.CONTEXT_IDENTIFIER + modelName + "-" + index + "\n";
@@ -296,7 +312,7 @@ public class DSLRuleProvider
Set<String> tags = ruleTags == null ? Set.of() : Set.copyOf(ruleTags);
return RuleBuilder.create(uid).withTags(tags).withName(name).withTriggers(triggers).withActions(actions)
.build();
.withConditions(conditions).build();
}
private String removeIndentation(String script) {
@@ -318,83 +334,80 @@ public class DSLRuleProvider
.collect(Collectors.joining("\n"));
}
private @Nullable Trigger mapTrigger(EventTrigger t) {
if (t instanceof SystemOnStartupTrigger) {
Configuration cfg = new Configuration();
cfg.put(SystemTriggerHandler.CFG_STARTLEVEL, 40);
return TriggerBuilder.create().withId(Integer.toString(triggerId++))
.withTypeUID(SystemTriggerHandler.STARTLEVEL_MODULE_TYPE_ID).withConfiguration(cfg).build();
} else if (t instanceof SystemStartlevelTrigger slTrigger) {
Configuration cfg = new Configuration();
cfg.put(SystemTriggerHandler.CFG_STARTLEVEL, slTrigger.getLevel());
return TriggerBuilder.create().withId(Integer.toString(triggerId++))
.withTypeUID(SystemTriggerHandler.STARTLEVEL_MODULE_TYPE_ID).withConfiguration(cfg).build();
} else if (t instanceof SystemOnShutdownTrigger) {
logger.warn("System shutdown rule triggers are no longer supported!");
return null;
} else if (t instanceof CommandEventTrigger ceTrigger) {
Configuration cfg = new Configuration();
cfg.put(ItemCommandTriggerHandler.CFG_ITEMNAME, ceTrigger.getItem());
if (ceTrigger.getCommand() != null) {
cfg.put(ItemCommandTriggerHandler.CFG_COMMAND, ceTrigger.getCommand().getValue());
private @Nullable Trigger mapTrigger(EventTrigger eventTrigger) {
Configuration cfg = new Configuration();
return switch (eventTrigger) {
case SystemOnStartupTrigger t -> {
cfg.put(SystemTriggerHandler.CFG_STARTLEVEL, 40);
yield TriggerBuilder.create().withId(Integer.toString(triggerId++))
.withTypeUID(SystemTriggerHandler.STARTLEVEL_MODULE_TYPE_ID).withConfiguration(cfg).build();
}
return TriggerBuilder.create().withId(Integer.toString(triggerId++))
.withTypeUID(ItemCommandTriggerHandler.MODULE_TYPE_ID).withConfiguration(cfg).build();
} else if (t instanceof GroupMemberCommandEventTrigger ceTrigger) {
Configuration cfg = new Configuration();
cfg.put(GroupCommandTriggerHandler.CFG_GROUPNAME, ceTrigger.getGroup());
if (ceTrigger.getCommand() != null) {
cfg.put(GroupCommandTriggerHandler.CFG_COMMAND, ceTrigger.getCommand().getValue());
case SystemStartlevelTrigger sllTrigger -> {
cfg.put(SystemTriggerHandler.CFG_STARTLEVEL, sllTrigger.getLevel());
yield TriggerBuilder.create().withId(Integer.toString(triggerId++))
.withTypeUID(SystemTriggerHandler.STARTLEVEL_MODULE_TYPE_ID).withConfiguration(cfg).build();
}
return TriggerBuilder.create().withId(Integer.toString(triggerId++))
.withTypeUID(GroupCommandTriggerHandler.MODULE_TYPE_ID).withConfiguration(cfg).build();
} else if (t instanceof UpdateEventTrigger ueTrigger) {
Configuration cfg = new Configuration();
cfg.put(ItemStateTriggerHandler.CFG_ITEMNAME, ueTrigger.getItem());
if (ueTrigger.getState() != null) {
cfg.put(ItemStateTriggerHandler.CFG_STATE, ueTrigger.getState().getValue());
case CommandEventTrigger ceTrigger -> {
cfg.put(ItemCommandTriggerHandler.CFG_ITEMNAME, ceTrigger.getItem());
if (ceTrigger.getCommand() != null) {
cfg.put(ItemCommandTriggerHandler.CFG_COMMAND, ceTrigger.getCommand().getValue());
}
yield TriggerBuilder.create().withId(Integer.toString(triggerId++))
.withTypeUID(ItemCommandTriggerHandler.MODULE_TYPE_ID).withConfiguration(cfg).build();
}
return TriggerBuilder.create().withId(Integer.toString(triggerId++))
.withTypeUID(ItemStateTriggerHandler.UPDATE_MODULE_TYPE_ID).withConfiguration(cfg).build();
} else if (t instanceof GroupMemberUpdateEventTrigger ueTrigger) {
Configuration cfg = new Configuration();
cfg.put(GroupStateTriggerHandler.CFG_GROUPNAME, ueTrigger.getGroup());
if (ueTrigger.getState() != null) {
cfg.put(GroupStateTriggerHandler.CFG_STATE, ueTrigger.getState().getValue());
case GroupMemberCommandEventTrigger ceTrigger -> {
cfg.put(GroupCommandTriggerHandler.CFG_GROUPNAME, ceTrigger.getGroup());
if (ceTrigger.getCommand() != null) {
cfg.put(GroupCommandTriggerHandler.CFG_COMMAND, ceTrigger.getCommand().getValue());
}
yield TriggerBuilder.create().withId(Integer.toString(triggerId++))
.withTypeUID(GroupCommandTriggerHandler.MODULE_TYPE_ID).withConfiguration(cfg).build();
}
return TriggerBuilder.create().withId(Integer.toString(triggerId++))
.withTypeUID(GroupStateTriggerHandler.UPDATE_MODULE_TYPE_ID).withConfiguration(cfg).build();
} else if (t instanceof ChangedEventTrigger ceTrigger) {
Configuration cfg = new Configuration();
cfg.put(ItemStateTriggerHandler.CFG_ITEMNAME, ceTrigger.getItem());
if (ceTrigger.getNewState() != null) {
cfg.put(ItemStateTriggerHandler.CFG_STATE, ceTrigger.getNewState().getValue());
case UpdateEventTrigger ueTrigger -> {
cfg.put(ItemStateTriggerHandler.CFG_ITEMNAME, ueTrigger.getItem());
if (ueTrigger.getState() != null) {
cfg.put(ItemStateTriggerHandler.CFG_STATE, ueTrigger.getState().getValue());
}
yield TriggerBuilder.create().withId(Integer.toString(triggerId++))
.withTypeUID(ItemStateTriggerHandler.UPDATE_MODULE_TYPE_ID).withConfiguration(cfg).build();
}
if (ceTrigger.getOldState() != null) {
cfg.put(ItemStateTriggerHandler.CFG_PREVIOUS_STATE, ceTrigger.getOldState().getValue());
case GroupMemberUpdateEventTrigger ueTrigger -> {
cfg.put(GroupStateTriggerHandler.CFG_GROUPNAME, ueTrigger.getGroup());
if (ueTrigger.getState() != null) {
cfg.put(GroupStateTriggerHandler.CFG_STATE, ueTrigger.getState().getValue());
}
yield TriggerBuilder.create().withId(Integer.toString(triggerId++))
.withTypeUID(GroupStateTriggerHandler.UPDATE_MODULE_TYPE_ID).withConfiguration(cfg).build();
}
return TriggerBuilder.create().withId(Integer.toString(triggerId++))
.withTypeUID(ItemStateTriggerHandler.CHANGE_MODULE_TYPE_ID).withConfiguration(cfg).build();
} else if (t instanceof GroupMemberChangedEventTrigger ceTrigger) {
Configuration cfg = new Configuration();
cfg.put(GroupStateTriggerHandler.CFG_GROUPNAME, ceTrigger.getGroup());
if (ceTrigger.getNewState() != null) {
cfg.put(GroupStateTriggerHandler.CFG_STATE, ceTrigger.getNewState().getValue());
case ChangedEventTrigger ceTrigger -> {
cfg.put(ItemStateTriggerHandler.CFG_ITEMNAME, ceTrigger.getItem());
if (ceTrigger.getNewState() != null) {
cfg.put(ItemStateTriggerHandler.CFG_STATE, ceTrigger.getNewState().getValue());
}
if (ceTrigger.getOldState() != null) {
cfg.put(ItemStateTriggerHandler.CFG_PREVIOUS_STATE, ceTrigger.getOldState().getValue());
}
yield TriggerBuilder.create().withId(Integer.toString(triggerId++))
.withTypeUID(ItemStateTriggerHandler.CHANGE_MODULE_TYPE_ID).withConfiguration(cfg).build();
}
if (ceTrigger.getOldState() != null) {
cfg.put(GroupStateTriggerHandler.CFG_PREVIOUS_STATE, ceTrigger.getOldState().getValue());
case GroupMemberChangedEventTrigger ceTrigger -> {
cfg.put(GroupStateTriggerHandler.CFG_GROUPNAME, ceTrigger.getGroup());
if (ceTrigger.getNewState() != null) {
cfg.put(GroupStateTriggerHandler.CFG_STATE, ceTrigger.getNewState().getValue());
}
if (ceTrigger.getOldState() != null) {
cfg.put(GroupStateTriggerHandler.CFG_PREVIOUS_STATE, ceTrigger.getOldState().getValue());
}
yield TriggerBuilder.create().withId(Integer.toString(triggerId++))
.withTypeUID(GroupStateTriggerHandler.CHANGE_MODULE_TYPE_ID).withConfiguration(cfg).build();
}
return TriggerBuilder.create().withId(Integer.toString(triggerId++))
.withTypeUID(GroupStateTriggerHandler.CHANGE_MODULE_TYPE_ID).withConfiguration(cfg).build();
} else if (t instanceof TimerTrigger tt) {
String triggerType;
Configuration cfg = new Configuration();
if (tt.getCron() != null) {
triggerType = GenericCronTriggerHandler.MODULE_TYPE_ID;
cfg.put(GenericCronTriggerHandler.CFG_CRON_EXPRESSION, tt.getCron());
} else {
triggerType = TimeOfDayTriggerHandler.MODULE_TYPE_ID;
String id = tt.getTime();
case TimerTrigger timeTrigger when timeTrigger.getCron() != null -> {
cfg.put(GenericCronTriggerHandler.CFG_CRON_EXPRESSION, timeTrigger.getCron());
yield TriggerBuilder.create().withId(Integer.toString(triggerId++))
.withTypeUID(GenericCronTriggerHandler.MODULE_TYPE_ID).withConfiguration(cfg).build();
}
case TimerTrigger timeTrigger when timeTrigger.getCron() == null -> {
String id = timeTrigger.getTime();
if ("noon".equals(id)) {
cfg.put(TimeOfDayTriggerHandler.CFG_TIME, "12:00");
} else if ("midnight".equals(id)) {
@@ -402,41 +415,127 @@ public class DSLRuleProvider
} else {
cfg.put(TimeOfDayTriggerHandler.CFG_TIME, id);
}
yield TriggerBuilder.create().withId(Integer.toString(triggerId++))
.withTypeUID(TimeOfDayTriggerHandler.MODULE_TYPE_ID).withConfiguration(cfg).build();
}
return TriggerBuilder.create().withId(Integer.toString(triggerId++)).withTypeUID(triggerType)
.withConfiguration(cfg).build();
} else if (t instanceof DateTimeTrigger tt) {
Configuration cfg = new Configuration();
cfg.put(DateTimeTriggerHandler.CONFIG_ITEM_NAME, tt.getItem());
cfg.put(DateTimeTriggerHandler.CONFIG_TIME_ONLY, tt.isTimeOnly());
cfg.put(DateTimeTriggerHandler.CONFIG_OFFSET, tt.getOffset());
return TriggerBuilder.create().withId(Integer.toString((triggerId++)))
.withTypeUID(DateTimeTriggerHandler.MODULE_TYPE_ID).withConfiguration(cfg).build();
} else if (t instanceof EventEmittedTrigger eeTrigger) {
Configuration cfg = new Configuration();
cfg.put(ChannelEventTriggerHandler.CFG_CHANNEL, eeTrigger.getChannel());
if (eeTrigger.getTrigger() != null) {
cfg.put(ChannelEventTriggerHandler.CFG_CHANNEL_EVENT, eeTrigger.getTrigger().getValue());
case DateTimeTrigger dtTrigger -> {
cfg.put(DateTimeTriggerHandler.CONFIG_ITEM_NAME, dtTrigger.getItem());
cfg.put(DateTimeTriggerHandler.CONFIG_TIME_ONLY, dtTrigger.isTimeOnly());
cfg.put(DateTimeTriggerHandler.CONFIG_OFFSET, dtTrigger.getOffset());
yield TriggerBuilder.create().withId(Integer.toString((triggerId++)))
.withTypeUID(DateTimeTriggerHandler.MODULE_TYPE_ID).withConfiguration(cfg).build();
}
return TriggerBuilder.create().withId(Integer.toString(triggerId++))
.withTypeUID(ChannelEventTriggerHandler.MODULE_TYPE_ID).withConfiguration(cfg).build();
} else if (t instanceof ThingStateUpdateEventTrigger tsuTrigger) {
Configuration cfg = new Configuration();
cfg.put(ThingStatusTriggerHandler.CFG_THING_UID, tsuTrigger.getThing());
cfg.put(ThingStatusTriggerHandler.CFG_STATUS, tsuTrigger.getState());
return TriggerBuilder.create().withId(Integer.toString(triggerId++))
.withTypeUID(ThingStatusTriggerHandler.UPDATE_MODULE_TYPE_ID).withConfiguration(cfg).build();
} else if (t instanceof ThingStateChangedEventTrigger tscTrigger) {
Configuration cfg = new Configuration();
cfg.put(ThingStatusTriggerHandler.CFG_THING_UID, tscTrigger.getThing());
cfg.put(ThingStatusTriggerHandler.CFG_STATUS, tscTrigger.getNewState());
cfg.put(ThingStatusTriggerHandler.CFG_PREVIOUS_STATUS, tscTrigger.getOldState());
return TriggerBuilder.create().withId(Integer.toString(triggerId++))
.withTypeUID(ThingStatusTriggerHandler.CHANGE_MODULE_TYPE_ID).withConfiguration(cfg).build();
} else {
logger.warn("Unknown trigger type '{}' - ignoring it.", t.getClass().getSimpleName());
return null;
}
case EventEmittedTrigger eeTrigger -> {
cfg.put(ChannelEventTriggerHandler.CFG_CHANNEL, eeTrigger.getChannel());
if (eeTrigger.getTrigger() != null) {
cfg.put(ChannelEventTriggerHandler.CFG_CHANNEL_EVENT, eeTrigger.getTrigger().getValue());
}
yield TriggerBuilder.create().withId(Integer.toString(triggerId++))
.withTypeUID(ChannelEventTriggerHandler.MODULE_TYPE_ID).withConfiguration(cfg).build();
}
case ThingStateUpdateEventTrigger tsuTrigger -> {
cfg.put(ThingStatusTriggerHandler.CFG_THING_UID, tsuTrigger.getThing());
cfg.put(ThingStatusTriggerHandler.CFG_STATUS, tsuTrigger.getState());
yield TriggerBuilder.create().withId(Integer.toString(triggerId++))
.withTypeUID(ThingStatusTriggerHandler.UPDATE_MODULE_TYPE_ID).withConfiguration(cfg).build();
}
case ThingStateChangedEventTrigger tscTrigger -> {
cfg.put(ThingStatusTriggerHandler.CFG_THING_UID, tscTrigger.getThing());
cfg.put(ThingStatusTriggerHandler.CFG_STATUS, tscTrigger.getNewState());
cfg.put(ThingStatusTriggerHandler.CFG_PREVIOUS_STATUS, tscTrigger.getOldState());
yield TriggerBuilder.create().withId(Integer.toString(triggerId++))
.withTypeUID(ThingStatusTriggerHandler.CHANGE_MODULE_TYPE_ID).withConfiguration(cfg).build();
}
case SystemOnShutdownTrigger t -> {
logger.warn("System shutdown rule triggers are no longer supported!");
yield null;
}
default -> {
logger.warn("Unknown trigger type '{}' - ignoring it.", eventTrigger.getClass().getSimpleName());
yield null;
}
};
}
private @Nullable Condition mapCondition(org.openhab.core.model.rule.rules.Condition condition) {
Configuration cfg = new Configuration();
return switch (condition) {
case TimeOfDayCondition todCond -> {
cfg.put(TimeOfDayConditionHandler.CFG_START_TIME, todCond.getStart());
cfg.put(TimeOfDayConditionHandler.CFG_END_TIME, todCond.getEnd());
yield ConditionBuilder.create().withId(Integer.toString(triggerId++))
.withTypeUID(TimeOfDayConditionHandler.MODULE_TYPE_ID).withConfiguration(cfg).build();
}
case DayOfWeekCondition dowCond -> {
List<String> days = new ArrayList<>();
dowCond.getWeekDays().forEach(day -> {
String d = switch (day) {
case "Monday" -> "MON";
case "Tuesday" -> "TUE";
case "Wednesday" -> "WED";
case "Thursday" -> "THU";
case "Friday" -> "FRI";
case "Saturday" -> "SAT";
case "Sunday" -> "SUN";
default -> null;
};
if (d != null) {
days.add(d);
}
});
cfg.put(DayOfWeekConditionHandler.CFG_DAYS, days);
yield ConditionBuilder.create().withId(Integer.toString(triggerId++))
.withTypeUID(DayOfWeekConditionHandler.MODULE_TYPE_ID).withConfiguration(cfg).build();
}
case WeekdayCondition weekdayCond -> {
String offset = weekdayCond.getOffset();
cfg.put("offset", offset == null ? 0 : Integer.valueOf(offset));
yield ConditionBuilder.create().withId(Integer.toString(triggerId++))
.withTypeUID("weekday".equals(weekdayCond.getType())
? EphemerisConditionHandler.WEEKDAY_MODULE_TYPE_ID
: EphemerisConditionHandler.WEEKEND_MODULE_TYPE_ID)
.withConfiguration(cfg).build();
}
case HolidayCondition holidayCond -> {
String offset = holidayCond.getOffset();
cfg.put("offset", offset == null ? 0 : Integer.valueOf(offset));
yield ConditionBuilder.create().withId(Integer.toString(triggerId++))
.withTypeUID(holidayCond.isNegation() ? EphemerisConditionHandler.NOT_HOLIDAY_MODULE_TYPE_ID
: EphemerisConditionHandler.HOLIDAY_MODULE_TYPE_ID)
.withConfiguration(cfg).build();
}
case InDaysetCondition daysetCond -> {
String offset = daysetCond.getOffset();
cfg.put("dayset", daysetCond.getDayset());
cfg.put("offset", offset == null ? 0 : Integer.valueOf(offset));
yield ConditionBuilder.create().withId(Integer.toString(triggerId++))
.withTypeUID(EphemerisConditionHandler.DAYSET_MODULE_TYPE_ID).withConfiguration(cfg).build();
}
case IntervalCondition intervalCond -> {
cfg.put(IntervalConditionHandler.CFG_MIN_INTERVAL, intervalCond.getInterval());
yield ConditionBuilder.create().withId(Integer.toString(triggerId++))
.withTypeUID(IntervalConditionHandler.MODULE_TYPE_ID).withConfiguration(cfg).build();
}
case ThingStatusCondition tsCond -> {
cfg.put(ThingStatusConditionHandler.CFG_THING_UID, tsCond.getThing());
cfg.put(ThingStatusConditionHandler.CFG_OPERATOR, tsCond.isNegation() ? "!=" : "=");
cfg.put(ThingStatusConditionHandler.CFG_STATUS, tsCond.getStatus());
yield ConditionBuilder.create().withId(Integer.toString(triggerId++))
.withTypeUID(ThingStatusConditionHandler.THING_STATUS_CONDITION).withConfiguration(cfg).build();
}
case ItemStateCondition isCond -> {
String operator = isCond.getOperator();
cfg.put(ItemStateConditionHandler.ITEM_NAME, isCond.getItem());
cfg.put(ItemStateConditionHandler.OPERATOR, operator == null ? "=" : operator);
cfg.put(ItemStateConditionHandler.STATE, isCond.getState().getValue());
yield ConditionBuilder.create().withId(Integer.toString(triggerId++))
.withTypeUID(ItemStateConditionHandler.ITEM_STATE_CONDITION).withConfiguration(cfg).build();
}
default -> {
logger.warn("Unknown condition type '{}' - ignoring it.", condition.getClass().getSimpleName());
yield null;
}
};
}
@Override
@@ -14,6 +14,7 @@ VariableDeclaration:
Rule:
'rule' name=(STRING|ID) ('[' tags+=(ID|STRING) (',' tags+=(ID|STRING))* ']')?
'when' eventtrigger+=EventTrigger ('or' eventtrigger+=EventTrigger)*
('but' 'only' 'if' conditions+=Condition ('and' conditions+=Condition)*)?
'then' script=XExpressionInClosure
'end'
;
@@ -38,7 +39,7 @@ CommandEventTrigger:
;
GroupMemberCommandEventTrigger:
'Member of' group=ItemName 'received command' (command=ValidCommand)?
'Member of' group=ItemName 'received command' (command=ValidCommand)?
;
UpdateEventTrigger:
@@ -46,11 +47,11 @@ UpdateEventTrigger:
;
GroupMemberUpdateEventTrigger:
'Member of' group=ItemName 'received update' (state=ValidState)?
'Member of' group=ItemName 'received update' (state=ValidState)?
;
ChangedEventTrigger:
'Item' item=ItemName 'changed' ('from' oldState=ValidState)? ('to' newState=ValidState)?
'Item' item=ItemName 'changed' ('from' oldState=ValidState)? ('to' newState=ValidState)?
;
GroupMemberChangedEventTrigger:
@@ -61,19 +62,18 @@ EventEmittedTrigger:
'Channel' channel=(STRING|ID) 'triggered' (trigger=ValidTrigger)?
;
TimerTrigger:
'Time' 'cron' cron=STRING |
'Time' 'is' time=('midnight' | 'noon' | TIME)
;
DateTimeTrigger:
'Time' 'is' item=ItemName (timeOnly?='timeOnly')? ('offset' '='? offset=SIGNED_INT)?
'Time' 'is' item=ItemName (timeOnly?='timeOnly')? ('offset' '='? offset=SIGNED_INT)?
;
SystemTrigger:
SystemOnStartupTrigger |
SystemStartlevelTrigger |
SystemStartlevelTrigger |
SystemOnShutdownTrigger
;
@@ -83,8 +83,8 @@ SystemOnStartupTrigger:
;
SystemStartlevelTrigger:
{SystemStartlevelTrigger}
'System' 'reached start level' level=INT
{SystemStartlevelTrigger}
'System' 'reached start level' level=INT
;
SystemOnShutdownTrigger:
@@ -93,11 +93,54 @@ SystemOnShutdownTrigger:
;
ThingStateUpdateEventTrigger:
'Thing' thing=STRING 'received update' (state=ThingValidState)?
'Thing' thing=STRING 'received update' (state=ThingValidState)?
;
ThingStateChangedEventTrigger:
'Thing' thing=STRING 'changed' ('from' oldState=ThingValidState)? ('to' newState=ThingValidState)?
'Thing' thing=STRING 'changed' ('from' oldState=ThingValidState)? ('to' newState=ThingValidState)?
;
Condition:
TimeOfDayCondition |
DayOfWeekCondition |
WeekdayCondition |
HolidayCondition |
InDaysetCondition |
IntervalCondition |
ThingStatusCondition |
ItemStateCondition
;
TimeOfDayCondition:
'Time' 'is' 'between' start=TIME 'and' end=TIME
;
DayOfWeekCondition:
'Day' 'is' weekDays+=WeekDay ('or' weekDays+=WeekDay)*
;
WeekdayCondition:
'Day' ('offset' '='? offset=SIGNED_INT)? 'is' 'a' type=('weekday' | 'weekend')
;
HolidayCondition:
'Day' ('offset' '='? offset=SIGNED_INT)? 'is' (negation?='not')? 'a' 'holiday'
;
InDaysetCondition:
'Day' ('offset' '='? offset=SIGNED_INT)? 'is' 'in' dayset=(STRING|ID)
;
IntervalCondition:
interval=INT 'ms' 'between' 'executions'
;
ThingStatusCondition:
'Thing' thing=STRING 'is' (negation?='not')? status=ThingValidState
;
ItemStateCondition:
'Item' item=ItemName (operator=Operator)? state=ValidState
;
ItemName :
@@ -105,65 +148,67 @@ ItemName :
;
ValidState:
ValidStateId | ValidStateNumber | ValidStateString
ValidStateId | ValidStateNumber | ValidStateString
;
ValidStateId:
value=ID
value=ID
;
ValidStateNumber:
value=Number
value=Number
;
ValidStateString:
value=STRING
value=STRING
;
ValidCommand:
ValidCommandId | ValidCommandNumber | ValidCommandString
ValidCommandId | ValidCommandNumber | ValidCommandString
;
ValidCommandId:
value=ID
value=ID
;
ValidCommandNumber:
value=Number
value=Number
;
ValidCommandString:
value=STRING
value=STRING
;
ValidTrigger:
ValidTriggerId | ValidTriggerNumber | ValidTriggerString
ValidTriggerId | ValidTriggerNumber | ValidTriggerString
;
ValidTriggerId:
value=ID
value=ID
;
ValidTriggerNumber:
value=Number
value=Number
;
ValidTriggerString:
value=STRING
value=STRING
;
ThingValidState:
'UNINITIALIZED' |
'INITIALIZING' |
'UNKNOWN' |
'ONLINE' |
'OFFLINE' |
'REMOVING' |
'REMOVED'
'UNINITIALIZED' | 'INITIALIZING' | 'UNKNOWN' | 'ONLINE' | 'OFFLINE' | 'REMOVING' | 'REMOVED'
;
WeekDay:
'Monday' | 'Tuesday' | 'Wednesday' | 'Thursday' | 'Friday' | 'Saturday' | 'Sunday'
;
Operator:
'=' | '!=' | '>' | '>=' | '<' | '<='
;
terminal TIME:
(('0'..'1') ('0'..'9') ':' ('0'..'5') ('0'..'9')) | ('2' ('0'..'3') ':' ('0'..'5') ('0'..'9'))
(('0'..'1') ('0'..'9') ':' ('0'..'5') ('0'..'9')) | ('2' ('0'..'3') ':' ('0'..'5') ('0'..'9'))
;
SIGNED_INT:
@@ -15,6 +15,7 @@ package org.openhab.core.model.rule.runtime;
import static org.hamcrest.CoreMatchers.*;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.collection.IsIterableContainingInOrder.contains;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import java.io.ByteArrayInputStream;
@@ -22,6 +23,7 @@ import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.xtext.naming.QualifiedName;
@@ -32,13 +34,19 @@ import org.junit.jupiter.api.Test;
import org.openhab.core.automation.Rule;
import org.openhab.core.automation.RuleProvider;
import org.openhab.core.automation.internal.module.handler.ChannelEventTriggerHandler;
import org.openhab.core.automation.internal.module.handler.DayOfWeekConditionHandler;
import org.openhab.core.automation.internal.module.handler.EphemerisConditionHandler;
import org.openhab.core.automation.internal.module.handler.GenericCronTriggerHandler;
import org.openhab.core.automation.internal.module.handler.GroupCommandTriggerHandler;
import org.openhab.core.automation.internal.module.handler.GroupStateTriggerHandler;
import org.openhab.core.automation.internal.module.handler.IntervalConditionHandler;
import org.openhab.core.automation.internal.module.handler.ItemCommandTriggerHandler;
import org.openhab.core.automation.internal.module.handler.ItemStateConditionHandler;
import org.openhab.core.automation.internal.module.handler.ItemStateTriggerHandler;
import org.openhab.core.automation.internal.module.handler.SystemTriggerHandler;
import org.openhab.core.automation.internal.module.handler.ThingStatusConditionHandler;
import org.openhab.core.automation.internal.module.handler.ThingStatusTriggerHandler;
import org.openhab.core.automation.internal.module.handler.TimeOfDayConditionHandler;
import org.openhab.core.automation.internal.module.handler.TimeOfDayTriggerHandler;
import org.openhab.core.automation.module.script.internal.handler.AbstractScriptModuleHandler;
import org.openhab.core.automation.module.script.internal.handler.ScriptActionHandler;
@@ -122,6 +130,7 @@ public class DSLRuleProviderTest extends JavaOSGiTest {
assertThat(firstRule.getTags(), hasSize(2));
assertThat(firstRule.getTags(), hasItem("Test"));
assertThat(firstRule.getTags(), hasItem("my test"));
assertThat(firstRule.getConditions(), hasSize(0));
assertThat(firstRule.getTriggers().getFirst().getTypeUID(), is(SystemTriggerHandler.STARTLEVEL_MODULE_TYPE_ID));
assertThat(firstRule.getActions().getFirst().getTypeUID(), is(ScriptActionHandler.TYPE_ID));
assertThat(firstRule.getActions().getFirst().getConfiguration()
@@ -135,6 +144,7 @@ public class DSLRuleProviderTest extends JavaOSGiTest {
assertThat(secondRule.getTriggers().getFirst().getTypeUID(), is(ItemStateTriggerHandler.CHANGE_MODULE_TYPE_ID));
assertThat(secondRule.getTriggers().getFirst().getConfiguration().get(ItemStateTriggerHandler.CFG_ITEMNAME),
is("X"));
assertThat(secondRule.getConditions(), hasSize(0));
assertThat(secondRule.getActions().getFirst().getTypeUID(), is(ScriptActionHandler.TYPE_ID));
assertThat(secondRule.getActions().getFirst().getConfiguration()
.get(AbstractScriptModuleHandler.CONFIG_SCRIPT_TYPE), is(DSLScriptEngine.MIMETYPE_OPENHAB_DSL_RULE));
@@ -245,6 +255,116 @@ public class DSLRuleProviderTest extends JavaOSGiTest {
is("START"));
}
@Test
public void testAllConditions() {
Collection<Rule> rules = dslRuleProvider.getAll();
assertThat(rules.size(), is(0));
String model = "rule RuleWithAllConditions\n" + //
"when\n" + //
" Item X received command ON\n" + //
"but only if\n" + //
" Time is between 19:20 and 22:10 and\n" + //
" Day is Thursday or Tuesday or Sunday and\n" + //
" Day is a weekday and\n" + //
" Day is a weekend and\n" + //
" Day offset 3 is a holiday and\n" + //
" Day offset = -2 is not a holiday and\n" + //
" Day is in \"my-dayset\" and\n" + //
" Thing \"T1\" is ONLINE and\n" + //
" Thing \"T2\" is not UNKNOWN and\n" + //
" Item W = \"a value\" and\n" + //
" Item X ON and\n" + //
" Item Y != 0 and\n" + //
" Item Z > 5.25 and\n" + //
" 15000 ms between executions\n" + //
"then\n" + //
" logInfo('Test', 'Test')\n" + //
"end\n\n";
modelRepository.addOrRefreshModel(TESTMODEL_NAME,
new ByteArrayInputStream(model.getBytes(StandardCharsets.UTF_8)));
Collection<Rule> actualRules = dslRuleProvider.getAll();
assertThat(actualRules.size(), is(1));
Iterator<Rule> it = actualRules.iterator();
Rule rule = it.next();
assertThat(rule.getUID(), is("dslruletest-1"));
assertThat(rule.getName(), is("RuleWithAllConditions"));
assertThat(rule.getConditions().size(), is(14));
assertThat(rule.getConditions().getFirst().getTypeUID(), is(TimeOfDayConditionHandler.MODULE_TYPE_ID));
assertThat(rule.getConditions().getFirst().getConfiguration().get(TimeOfDayConditionHandler.CFG_START_TIME),
is("19:20"));
assertThat(rule.getConditions().getFirst().getConfiguration().get(TimeOfDayConditionHandler.CFG_END_TIME),
is("22:10"));
assertThat(rule.getConditions().get(1).getTypeUID(), is(DayOfWeekConditionHandler.MODULE_TYPE_ID));
Object days = rule.getConditions().get(1).getConfiguration().get(DayOfWeekConditionHandler.CFG_DAYS);
assertThat(days, instanceOf(List.class));
assertThat((List<String>) days, hasSize(3));
assertThat((List<String>) days, contains("THU", "TUE", "SUN"));
assertThat(rule.getConditions().get(2).getTypeUID(), is(EphemerisConditionHandler.WEEKDAY_MODULE_TYPE_ID));
assertThat(rule.getConditions().get(2).getConfiguration().get("offset"), is(new BigDecimal(0)));
assertThat(rule.getConditions().get(3).getTypeUID(), is(EphemerisConditionHandler.WEEKEND_MODULE_TYPE_ID));
assertThat(rule.getConditions().get(3).getConfiguration().get("offset"), is(new BigDecimal(0)));
assertThat(rule.getConditions().get(4).getTypeUID(), is(EphemerisConditionHandler.HOLIDAY_MODULE_TYPE_ID));
assertThat(rule.getConditions().get(4).getConfiguration().get("offset"), is(new BigDecimal(3)));
assertThat(rule.getConditions().get(5).getTypeUID(), is(EphemerisConditionHandler.NOT_HOLIDAY_MODULE_TYPE_ID));
assertThat(rule.getConditions().get(5).getConfiguration().get("offset"), is(new BigDecimal(-2)));
assertThat(rule.getConditions().get(6).getTypeUID(), is(EphemerisConditionHandler.DAYSET_MODULE_TYPE_ID));
assertThat(rule.getConditions().get(6).getConfiguration().get("dayset"), is("my-dayset"));
assertThat(rule.getConditions().get(6).getConfiguration().get("offset"), is(new BigDecimal(0)));
assertThat(rule.getConditions().get(7).getTypeUID(), is(ThingStatusConditionHandler.THING_STATUS_CONDITION));
assertThat(rule.getConditions().get(7).getConfiguration().get(ThingStatusConditionHandler.CFG_THING_UID),
is("T1"));
assertThat(rule.getConditions().get(7).getConfiguration().get(ThingStatusConditionHandler.CFG_OPERATOR),
is("="));
assertThat(rule.getConditions().get(7).getConfiguration().get(ThingStatusConditionHandler.CFG_STATUS),
is("ONLINE"));
assertThat(rule.getConditions().get(8).getTypeUID(), is(ThingStatusConditionHandler.THING_STATUS_CONDITION));
assertThat(rule.getConditions().get(8).getConfiguration().get(ThingStatusConditionHandler.CFG_THING_UID),
is("T2"));
assertThat(rule.getConditions().get(8).getConfiguration().get(ThingStatusConditionHandler.CFG_OPERATOR),
is("!="));
assertThat(rule.getConditions().get(8).getConfiguration().get(ThingStatusConditionHandler.CFG_STATUS),
is("UNKNOWN"));
assertThat(rule.getConditions().get(9).getTypeUID(), is(ItemStateConditionHandler.ITEM_STATE_CONDITION));
assertThat(rule.getConditions().get(9).getConfiguration().get(ItemStateConditionHandler.ITEM_NAME), is("W"));
assertThat(rule.getConditions().get(9).getConfiguration().get(ItemStateConditionHandler.OPERATOR), is("="));
assertThat(rule.getConditions().get(9).getConfiguration().get(ItemStateConditionHandler.STATE), is("a value"));
assertThat(rule.getConditions().get(10).getTypeUID(), is(ItemStateConditionHandler.ITEM_STATE_CONDITION));
assertThat(rule.getConditions().get(10).getConfiguration().get(ItemStateConditionHandler.ITEM_NAME), is("X"));
assertThat(rule.getConditions().get(10).getConfiguration().get(ItemStateConditionHandler.OPERATOR), is("="));
assertThat(rule.getConditions().get(10).getConfiguration().get(ItemStateConditionHandler.STATE), is("ON"));
assertThat(rule.getConditions().get(11).getTypeUID(), is(ItemStateConditionHandler.ITEM_STATE_CONDITION));
assertThat(rule.getConditions().get(11).getConfiguration().get(ItemStateConditionHandler.ITEM_NAME), is("Y"));
assertThat(rule.getConditions().get(11).getConfiguration().get(ItemStateConditionHandler.OPERATOR), is("!="));
assertThat(rule.getConditions().get(11).getConfiguration().get(ItemStateConditionHandler.STATE), is("0"));
assertThat(rule.getConditions().get(12).getTypeUID(), is(ItemStateConditionHandler.ITEM_STATE_CONDITION));
assertThat(rule.getConditions().get(12).getConfiguration().get(ItemStateConditionHandler.ITEM_NAME), is("Z"));
assertThat(rule.getConditions().get(12).getConfiguration().get(ItemStateConditionHandler.OPERATOR), is(">"));
assertThat(rule.getConditions().get(12).getConfiguration().get(ItemStateConditionHandler.STATE), is("5.25"));
assertThat(rule.getConditions().get(13).getTypeUID(), is(IntervalConditionHandler.MODULE_TYPE_ID));
assertThat(rule.getConditions().get(13).getConfiguration().get(IntervalConditionHandler.CFG_MIN_INTERVAL),
is(new BigDecimal(15000)));
}
@Test
public void testVars() {
Collection<Rule> rules = dslRuleProvider.getAll();