opcua-service/main/java/de/opcua/app/model/NodeAction.java
2026-05-11 19:40:18 +02:00

196 lines
7.0 KiB
Java

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<String> conditionNodeIds;
private Map<String, String> 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<String> getConditionNodeIds() { return conditionNodeIds; }
public void setConditionNodeIds(List<String> conditionNodeIds) {
this.conditionNodeIds = conditionNodeIds != null ? new ArrayList<>(conditionNodeIds) : new ArrayList<>();
}
public Map<String, String> getNodeAliases() { return nodeAliases; }
public void setNodeAliases(Map<String, String> nodeAliases) {
this.nodeAliases = nodeAliases != null ? new LinkedHashMap<>(nodeAliases) : new LinkedHashMap<>();
}
@Override
public String toString() {
return actionName + " (" + triggerType + ")";
}
}