RuleWay
RuleWay is a lightweight embeddable rule engine for Dart applications, device automation, and edge Linux devices. It is designed for workloads where rules are driven by device attributes, attribute changes, rule-level time windows, and explicit action handlers.
Features
- Device attribute state and previous-state tracking.
device_exprandattr_changetriggers.- Multi-device rules through
targetsandtarget_index. durationandcooldownwindows.- Rule-level multi-segment
time_windowswith weekday repeat. time,date, and six-field second-levelcrontriggers.- Global actions and per-device
device_actionswith action parameters. - Action registration with built-in
emit_alarm. - Optional SQLite rule storage using compact JSON columns.
Usage
import 'package:ruleway/ruleway.dart';
Future<void> main() async {
final engine = RuleEngine(
timeZone: 'Asia/Shanghai',
rules: [
Rule(
targets: ['AC01'],
triggers: [
Trigger(
type: TriggerType.deviceExpr,
targetIndex: 0,
attr: 'temperature',
op: '>',
value: 30,
),
],
actions: [
ActionConfig(type: 'emit_alarm', params: {'reason': 'too hot'}),
],
),
],
);
engine.onAlarm.listen((alarm) => print(alarm.toJson()));
await engine.postAttrUpdate('AC01', {'temperature': '31'});
engine.dispose();
}
postAttrUpdate() and tick() return futures because registered actions may be
asynchronous. Await them when deterministic action completion is required.
Event Context and Actions
Attach request, session, or source metadata to an attribute update without storing it as device state:
engine.registerAction('notify', (action, context) async {
final requestId = context.context['request_id'];
await sendNotification(requestId, action.params);
});
await engine.postAttrUpdate(
'AC01',
{'temperature': 31},
context: {'request_id': 'req-42'},
);
For rules with duration > 0, RuleWay preserves the context from the event that
started the duration window. Calling tick() continues evaluating eligible
duration rules even when no new attribute event arrives. attr_change rules
still require a new edge event and are not completed by a timer tick.
Rule JSON
{
"rules": [
{
"id": 1,
"enabled": true,
"duration": 0,
"cooldown": 5,
"targets": [{"id": "AC01"}, {"id": "Door01"}],
"time_windows": [
{"start": "08:00:00", "end": "09:00:00", "week_repeat": [0, 1, 2, 3, 4]},
{"start": "20:00:00", "end": "21:00:00", "week_repeat": [0, 1, 2, 3, 4]}
],
"triggers": [
{
"type": "attr_change",
"target_index": 0,
"attr": "has_person",
"from": 0,
"to": 1
}
],
"conditions": [
{
"type": "device_expr",
"target_index": 1,
"attr": "door_open",
"op": "==",
"value": true
}
],
"actions": [{"type": "emit_alarm", "reason": "person at open door"}],
"device_actions": [
{
"target_index": 1,
"actions": [
{"type": "set_property", "power": true},
{"type": "set_property", "brightness": 80}
]
}
]
}
]
}
week_repeat uses 0..6 for Monday..Sunday. Empty or missing means every day.
time_windows is optional; if omitted, the rule is always active.
All time_windows, time, date in_weekday, and cron calculations use the
timezone passed to RuleEngine(timeZone: ...); they never use the host machine's
local timezone.
device_actions is optional and runs after global actions.
SQLite Storage
final storage = RuleStorage('/tmp/ruleway.db');
storage.init();
storage.importJsonFile('rules.json', reload: true);
final rules = storage.loadRules();
CLI
dart run ruleway:ruleway_cli --timezone Asia/Shanghai rules.json
Input events as:
AC01.temperature=31
Door01.door_open=true
Libraries
- ruleway
- Lightweight embeddable rule engine for Dart and edge devices.