-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmain.ts
154 lines (126 loc) · 4.53 KB
/
main.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
import mqtt from "npm:mqtt";
import jexl from "npm:@digifi/jexl";
import { parse } from "jsr:@std/yaml";
import Logger from "https://deno.land/x/logger/logger.ts";
const logger = new Logger();
const settings = parse(await Deno.readTextFile(Deno.args[0] || "./config/hrules.yml"));
const client = mqtt.connect(settings.mqtt.host, settings.mqtt.options);
const definitions = await Deno.readTextFile("./config/hrules.rules");
const Utils = {
now() {
const weekdays = ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"];
const currentDate = new Date();
return {
weekday: weekdays[currentDate.getDay()],
minutes: 60 * currentDate.getHours() + currentDate.getMinutes(),
};
},
withinHoursExpression(timeRange: string): boolean {
const now = Utils.now();
for (const hourRange of timeRange.split(",")) {
const [startTime, endTime] = hourRange.split("-").map(time => {
const [hours, minutes] = time.split(":");
return Number(hours) * 60 + Number(minutes);
});
if (now.minutes >= startTime && now.minutes <= endTime) {
return true;
}
}
return false;
},
withinTimeExpression(timeExpression: string) {
const regex = /((?<weekday>(?:[A-Z][a-z]-?)+)\s?)?(?<hoursExpression>.+)/;
let isWithinRange = false;
for (const part of timeExpression.split(";")) {
const trimmedPart = part.trim();
const match = regex.exec(trimmedPart);
if (!match) continue;
if (!match.groups) continue;
const { weekday, hoursExpression } = match.groups;
if (weekday) {
const specifiedWeekdays = weekday.split("-");
if (specifiedWeekdays.includes(Utils.now().weekday)) {
isWithinRange = Utils.withinHoursExpression(hoursExpression);
}
} else {
isWithinRange = Utils.withinHoursExpression(hoursExpression);
}
}
return isWithinRange;
},
parseReplyExpression(str: string, originalPayload: string | Record<string, unknown>) {
const [topic, ...responsePayload] = str.split(" ") ?? [];
const payload = new Function("const [payload] = arguments; return " + responsePayload.join(" "))(originalPayload);
return [topic.trim(), typeof payload === "string" ? payload : JSON.stringify(payload)];
},
};
const triggers = new Map<
string,
((context: string | Record<string, unknown>) => Promise<(() => void) | undefined>)[]
>();
async function main() {
logger.info("[Init] Connected to MQTT broker");
let i = 1;
for (const line of definitions.trim().split("\n")) {
const definition = line.split("->").map(x => x.trim()) as [string, string, string];
if (definition.length < 3) {
continue;
}
if (definition[0].startsWith("#")) {
continue;
}
const [inputExpression, expression, replyExpression] = definition;
const [topic, timeExpression] = inputExpression.split("|").map(x => x.trim());
const fn = async (context: string | Record<string, unknown>) => {
const passTimeExpression = timeExpression ? Utils.withinTimeExpression(timeExpression) : true;
if (!passTimeExpression) return;
const passEvalExpression = await jexl.eval(expression, {
payload: context,
global: {
ts: new Date().getTime(),
},
});
if (!passEvalExpression) return;
logger.info(`[Sub] Incoming ${topic}`);
return () => {
try {
const [replyTopic, replyPayload] = Utils.parseReplyExpression(replyExpression, context);
client.publish(replyTopic, replyPayload);
logger.info(`[Pub] ${topic} -> ${replyTopic}`);
} catch (e) {
logger.error(e);
}
};
};
if (triggers.has(topic)) {
const actions = triggers.get(topic);
if (actions) {
actions.push(fn);
triggers.set(topic, actions);
}
} else {
triggers.set(topic, [fn]);
client.subscribe(topic);
logger.info(`[Init] Subcribed to ${topic}`);
}
i++;
}
client.on("message", async (topic, message) => {
const actions = triggers.get(topic);
if (!actions) return;
for (const action of actions) {
if (!action) continue;
let payload = message;
try {
payload = JSON.parse(payload.toString());
} catch (e) {
logger.error(e);
}
const callback = await action(payload);
if (callback) {
await callback();
}
}
});
}
client.on("connect", main);