package de.opcua.app.model; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Objects; /** * Represents an action that can be executed when an OPC UA node value changes. * Supports trigger rules plus an optional JavaScript condition that is evaluated * with the selected/checked NodeIds before the main script is started. */ public class NodeAction { public enum TriggerType { ON_CHANGE, // Execute on any value change ON_TRUE, // Execute when value becomes true/non-zero ON_FALSE, // Execute when value becomes false/zero ON_EVEN, // Execute when value is even ON_ODD, // Execute when value is odd ON_VALUE, // Execute when value equals specific value ON_GREATER_THAN, // Execute when value > threshold ON_LESS_THAN, // Execute when value < threshold ON_INTERVAL // Execute on timer interval } private String nodeId; private String actionName; private TriggerType triggerType; private String script; // JavaScript code to execute private String triggerValue; // For ON_VALUE, ON_GREATER_THAN, ON_LESS_THAN private int intervalMs; // For ON_INTERVAL private boolean enabled; private String lastValue; // Track last value for change detection // Extended scripting / checkbox condition support private boolean conditionEnabled; private String conditionScript; private List conditionNodeIds; private Map nodeAliases; // nodeId -> variable alias used in JavaScript public NodeAction() { this.enabled = true; this.triggerType = TriggerType.ON_CHANGE; this.conditionEnabled = false; this.conditionScript = ""; this.conditionNodeIds = new ArrayList<>(); this.nodeAliases = new LinkedHashMap<>(); } public NodeAction(String nodeId, String actionName, TriggerType triggerType, String script) { this(); this.nodeId = nodeId; this.actionName = actionName; this.triggerType = triggerType; this.script = script; } /** * Check if this action should be triggered based on the new value. * The optional JavaScript condition is evaluated later in ActionService, * because it may need fresh values from additional NodeIds. */ public boolean shouldTrigger(String newValue) { if (!enabled) return false; try { switch (triggerType) { case ON_CHANGE: boolean changed = !Objects.equals(lastValue, newValue); lastValue = newValue; return changed; case ON_TRUE: return isTruthyValue(newValue); case ON_FALSE: return !isTruthyValue(newValue); case ON_EVEN: return isEven(newValue); case ON_ODD: return isOdd(newValue); case ON_VALUE: return Objects.equals(newValue, triggerValue); case ON_GREATER_THAN: return compareNumeric(newValue, triggerValue) > 0; case ON_LESS_THAN: return compareNumeric(newValue, triggerValue) < 0; case ON_INTERVAL: // Interval-based triggers are handled separately return false; default: return false; } } catch (Exception e) { System.err.println("Error checking trigger for action " + actionName + ": " + e.getMessage()); return false; } } private boolean isTruthyValue(String value) { if (value == null || value.trim().isEmpty()) return false; if ("true".equalsIgnoreCase(value.trim())) return true; if ("false".equalsIgnoreCase(value.trim())) return false; try { double num = Double.parseDouble(value.trim()); return num != 0.0; } catch (NumberFormatException e) { return true; } } private boolean isEven(String value) { try { long num = Long.parseLong(value.trim()); return num % 2 == 0; } catch (NumberFormatException e) { return false; } } private boolean isOdd(String value) { try { long num = Long.parseLong(value.trim()); return num % 2 != 0; } catch (NumberFormatException e) { return false; } } private int compareNumeric(String value1, String value2) { try { double num1 = Double.parseDouble(value1.trim()); double num2 = Double.parseDouble(value2.trim()); return Double.compare(num1, num2); } catch (NumberFormatException e) { return value1.compareTo(value2); } } public String getNodeId() { return nodeId; } public void setNodeId(String nodeId) { this.nodeId = nodeId; } public String getActionName() { return actionName; } public void setActionName(String actionName) { this.actionName = actionName; } public TriggerType getTriggerType() { return triggerType; } public void setTriggerType(TriggerType triggerType) { this.triggerType = triggerType; } public String getScript() { return script; } public void setScript(String script) { this.script = script; } public String getTriggerValue() { return triggerValue; } public void setTriggerValue(String triggerValue) { this.triggerValue = triggerValue; } public int getIntervalMs() { return intervalMs; } public void setIntervalMs(int intervalMs) { this.intervalMs = intervalMs; } public boolean isEnabled() { return enabled; } public void setEnabled(boolean enabled) { this.enabled = enabled; } public String getLastValue() { return lastValue; } public void setLastValue(String lastValue) { this.lastValue = lastValue; } public boolean isConditionEnabled() { return conditionEnabled; } public void setConditionEnabled(boolean conditionEnabled) { this.conditionEnabled = conditionEnabled; } public String getConditionScript() { return conditionScript; } public void setConditionScript(String conditionScript) { this.conditionScript = conditionScript; } public List getConditionNodeIds() { return conditionNodeIds; } public void setConditionNodeIds(List conditionNodeIds) { this.conditionNodeIds = conditionNodeIds != null ? new ArrayList<>(conditionNodeIds) : new ArrayList<>(); } public Map getNodeAliases() { return nodeAliases; } public void setNodeAliases(Map nodeAliases) { this.nodeAliases = nodeAliases != null ? new LinkedHashMap<>(nodeAliases) : new LinkedHashMap<>(); } @Override public String toString() { return actionName + " (" + triggerType + ")"; } }