node-red-contrib-starlight 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/gwiazda.png +0 -0
- package/icons/gwiazda.png +0 -0
- package/icons/starlight.svg +4 -0
- package/nodes/gwiazda.png +0 -0
- package/nodes/rejestrator.html +78 -0
- package/nodes/rejestrator.js +231 -0
- package/nodes/starlight.svg +4 -0
- package/package.json +20 -0
package/gwiazda.png
ADDED
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="66" height="70" viewBox="0 0 66 70">
|
|
3
|
+
<path d="M32.697 2.98214L34.7147 9.92857C37.9934 21.2679 46.9468 30.1429 58.3862 33.3929L65.394 35.3929L58.3862 37.3929C46.9468 40.6429 37.9934 49.5179 34.7147 60.8571L32.697 67.8036L30.6793 60.8571C27.4006 49.5179 18.4472 40.6429 7.00779 37.3929L0 35.3929L7.00779 33.3929C18.4472 30.1429 27.4006 21.2679 30.6793 9.92857L32.697 2.98214Z" fill="white"/>
|
|
4
|
+
</svg>
|
|
Binary file
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
<script type="text/javascript">
|
|
2
|
+
RED.nodes.registerType("Starlight", {
|
|
3
|
+
category: "network",
|
|
4
|
+
color: "#8A5271",
|
|
5
|
+
defaults: {
|
|
6
|
+
name: { value: "" },
|
|
7
|
+
alarmType: { value: "all" },
|
|
8
|
+
channel: { value: "all" },
|
|
9
|
+
endpoint: { value: "/starlight" },
|
|
10
|
+
},
|
|
11
|
+
inputs: 1,
|
|
12
|
+
outputs: 1,
|
|
13
|
+
icon: "gwiazda.png",
|
|
14
|
+
label: function () {
|
|
15
|
+
return this.name || "Starlight";
|
|
16
|
+
},
|
|
17
|
+
});
|
|
18
|
+
</script>
|
|
19
|
+
|
|
20
|
+
<script type="text/html" data-template-name="Starlight">
|
|
21
|
+
<div class="form-row">
|
|
22
|
+
<label for="node-input-name"><i class="fa fa-tag"></i> Nazwa</label>
|
|
23
|
+
<input type="text" id="node-input-name" placeholder="Starlight" />
|
|
24
|
+
</div>
|
|
25
|
+
<div class="form-row">
|
|
26
|
+
<label for="node-input-endpoint">Endpoint</label>
|
|
27
|
+
<input
|
|
28
|
+
type="text"
|
|
29
|
+
id="node-input-endpoint"
|
|
30
|
+
placeholder="/starlight"
|
|
31
|
+
/>
|
|
32
|
+
</div>
|
|
33
|
+
<div class="form-row">
|
|
34
|
+
<label for="node-input-alarmType">Typ alarmu</label>
|
|
35
|
+
<select id="node-input-alarmType">
|
|
36
|
+
<option value="all">Wszystkie alarmy</option>
|
|
37
|
+
<option value="region_entrance">Wejście w obszar</option>
|
|
38
|
+
<option value="region_exiting">Wyjście z obszaru</option>
|
|
39
|
+
<option value="line_crossing">Przekroczenie linii</option>
|
|
40
|
+
<option value="object_left">Pozostawiony obiekt</option>
|
|
41
|
+
<option value="object_removed">Usunięty obiekt</option>
|
|
42
|
+
<option value="camera_tamper">Sabotaż kamery</option>
|
|
43
|
+
<option value="video_tamper">Sabotaż wideo</option>
|
|
44
|
+
<option value="motion">Detekcja ruchu</option>
|
|
45
|
+
</select>
|
|
46
|
+
</div>
|
|
47
|
+
<div class="form-row">
|
|
48
|
+
<label for="node-input-channel">Kanał</label>
|
|
49
|
+
<select id="node-input-channel">
|
|
50
|
+
<option value="all">Wszystkie</option>
|
|
51
|
+
<option value="CH1">CH1</option>
|
|
52
|
+
<option value="CH2">CH2</option>
|
|
53
|
+
<option value="CH3">CH3</option>
|
|
54
|
+
<option value="CH4">CH4</option>
|
|
55
|
+
<option value="CH5">CH5</option>
|
|
56
|
+
<option value="CH6">CH6</option>
|
|
57
|
+
<option value="CH7">CH7</option>
|
|
58
|
+
<option value="CH8">CH8</option>
|
|
59
|
+
<option value="CH9">CH9</option>
|
|
60
|
+
<option value="CH10">CH10</option>
|
|
61
|
+
<option value="CH11">CH11</option>
|
|
62
|
+
<option value="CH12">CH12</option>
|
|
63
|
+
<option value="CH13">CH13</option>
|
|
64
|
+
<option value="CH14">CH14</option>
|
|
65
|
+
<option value="CH15">CH15</option>
|
|
66
|
+
<option value="CH16">CH16</option>
|
|
67
|
+
</select>
|
|
68
|
+
</div>
|
|
69
|
+
</script>
|
|
70
|
+
|
|
71
|
+
<script type="text/html" data-help-name="Starlight">
|
|
72
|
+
<p>
|
|
73
|
+
Nasłuchuje na <code>/starlight</code> danych alarmowych i emituje
|
|
74
|
+
przefiltrowane komunikaty zawierające <code>time</code> i
|
|
75
|
+
<code>alarm</code>.
|
|
76
|
+
</p>
|
|
77
|
+
</script>
|
|
78
|
+
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
const endpointMap = new Map();
|
|
2
|
+
|
|
3
|
+
const normalizeItems = (payload) => {
|
|
4
|
+
if (Array.isArray(payload)) {
|
|
5
|
+
return payload;
|
|
6
|
+
}
|
|
7
|
+
if (payload && typeof payload === "object") {
|
|
8
|
+
return [payload];
|
|
9
|
+
}
|
|
10
|
+
return [];
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const toIsoTime = (value) => {
|
|
14
|
+
if (!value) {
|
|
15
|
+
return undefined;
|
|
16
|
+
}
|
|
17
|
+
if (typeof value === "string") {
|
|
18
|
+
return value;
|
|
19
|
+
}
|
|
20
|
+
if (typeof value === "number") {
|
|
21
|
+
const ms = value > 1e12 ? value : value * 1000;
|
|
22
|
+
const date = new Date(ms);
|
|
23
|
+
if (!Number.isNaN(date.getTime())) {
|
|
24
|
+
return date.toISOString();
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return undefined;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const mapSnapType = (typeValue) => {
|
|
31
|
+
if (typeValue === undefined || typeValue === null) {
|
|
32
|
+
return undefined;
|
|
33
|
+
}
|
|
34
|
+
const typeNum = Number(typeValue);
|
|
35
|
+
if (Number.isNaN(typeNum)) {
|
|
36
|
+
return undefined;
|
|
37
|
+
}
|
|
38
|
+
const typeMap = new Map([
|
|
39
|
+
[14, "region_exiting"],
|
|
40
|
+
]);
|
|
41
|
+
return typeMap.get(typeNum) || `type_${typeNum}`;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const unwrapPayload = (payload) => {
|
|
45
|
+
if (payload && payload.data) {
|
|
46
|
+
return payload;
|
|
47
|
+
}
|
|
48
|
+
if (payload && payload.body && payload.body.data) {
|
|
49
|
+
return payload.body;
|
|
50
|
+
}
|
|
51
|
+
if (payload && payload.payload && payload.payload.data) {
|
|
52
|
+
return payload.payload;
|
|
53
|
+
}
|
|
54
|
+
return payload;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const extractItems = (payload) => {
|
|
58
|
+
const root = unwrapPayload(payload);
|
|
59
|
+
const directItems = normalizeItems(root);
|
|
60
|
+
if (directItems.some((item) => item && item.int_alarm)) {
|
|
61
|
+
return directItems;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const alarmList =
|
|
65
|
+
root && root.data && Array.isArray(root.data.alarm_list)
|
|
66
|
+
? root.data.alarm_list
|
|
67
|
+
: [];
|
|
68
|
+
|
|
69
|
+
if (alarmList.length > 0) {
|
|
70
|
+
const items = [];
|
|
71
|
+
alarmList.forEach((entry) => {
|
|
72
|
+
const channelAlarm = entry && entry.channel_alarm;
|
|
73
|
+
if (Array.isArray(channelAlarm)) {
|
|
74
|
+
channelAlarm.forEach((alarmItem) => {
|
|
75
|
+
items.push(alarmItem);
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
if (items.length > 0) {
|
|
80
|
+
return items;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const snapInfo =
|
|
85
|
+
root &&
|
|
86
|
+
root.data &&
|
|
87
|
+
root.data.ai_snap_picture &&
|
|
88
|
+
root.data.ai_snap_picture.SnapedObjInfo;
|
|
89
|
+
|
|
90
|
+
const snapItems = Array.isArray(snapInfo)
|
|
91
|
+
? snapInfo
|
|
92
|
+
: snapInfo
|
|
93
|
+
? [snapInfo]
|
|
94
|
+
: [];
|
|
95
|
+
|
|
96
|
+
return snapItems.map((snap) => {
|
|
97
|
+
const channel =
|
|
98
|
+
snap.StrChn ||
|
|
99
|
+
snap.chn_alias ||
|
|
100
|
+
(typeof snap.Chn === "number" ? `CH${snap.Chn + 1}` : undefined);
|
|
101
|
+
|
|
102
|
+
return {
|
|
103
|
+
channel,
|
|
104
|
+
int_alarm: {
|
|
105
|
+
time: toIsoTime(snap.StartTime || snap.EndTime),
|
|
106
|
+
alarm_val: true,
|
|
107
|
+
int_subtype: mapSnapType(snap.Type),
|
|
108
|
+
},
|
|
109
|
+
};
|
|
110
|
+
});
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
const emitMatches = (items, node, sendFn) => {
|
|
114
|
+
let matched = 0;
|
|
115
|
+
|
|
116
|
+
items.forEach((item) => {
|
|
117
|
+
const channel = item && item.channel;
|
|
118
|
+
const alarm = item && item.int_alarm;
|
|
119
|
+
if (!alarm) {
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const subtype = alarm.int_subtype;
|
|
124
|
+
const time = alarm.time;
|
|
125
|
+
const alarmVal = alarm.alarm_val;
|
|
126
|
+
|
|
127
|
+
const channelOk =
|
|
128
|
+
node.channel === "all" || (channel && channel === node.channel);
|
|
129
|
+
const alarmOk =
|
|
130
|
+
node.alarmType === "all" || (subtype && subtype === node.alarmType);
|
|
131
|
+
if (!channelOk || !alarmOk) {
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
matched += 1;
|
|
136
|
+
const payloadOut = {
|
|
137
|
+
time,
|
|
138
|
+
alarm: alarmVal,
|
|
139
|
+
};
|
|
140
|
+
if (node.channel === "all" && channel) {
|
|
141
|
+
payloadOut.channel = channel;
|
|
142
|
+
}
|
|
143
|
+
if (node.alarmType === "all" && subtype) {
|
|
144
|
+
payloadOut.alarmType = subtype;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
sendFn({ payload: payloadOut });
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
return matched;
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
module.exports = function (RED) {
|
|
154
|
+
function StarlightNode(config) {
|
|
155
|
+
RED.nodes.createNode(this, config);
|
|
156
|
+
const node = this;
|
|
157
|
+
|
|
158
|
+
node.alarmType = config.alarmType || "all";
|
|
159
|
+
node.channel = config.channel || "all";
|
|
160
|
+
node.endpoint =
|
|
161
|
+
(config.endpoint || "/starlight").trim() || "/starlight";
|
|
162
|
+
if (!node.endpoint.startsWith("/")) {
|
|
163
|
+
node.endpoint = `/${node.endpoint}`;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (!endpointMap.has(node.endpoint)) {
|
|
167
|
+
endpointMap.set(node.endpoint, new Set());
|
|
168
|
+
|
|
169
|
+
const handlePayload = (payload, res) => {
|
|
170
|
+
const items = extractItems(payload);
|
|
171
|
+
let matched = 0;
|
|
172
|
+
|
|
173
|
+
endpointMap.get(node.endpoint).forEach((n) => {
|
|
174
|
+
matched += emitMatches(items, n, n.send.bind(n));
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
res
|
|
178
|
+
.status(200)
|
|
179
|
+
.json({ received: items.length, matched });
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
RED.httpNode.post(node.endpoint, (req, res) => {
|
|
183
|
+
if (req.body !== undefined) {
|
|
184
|
+
handlePayload(req.body, res);
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
let raw = "";
|
|
189
|
+
req.on("data", (chunk) => {
|
|
190
|
+
raw += chunk;
|
|
191
|
+
});
|
|
192
|
+
req.on("end", () => {
|
|
193
|
+
if (!raw) {
|
|
194
|
+
handlePayload([], res);
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
try {
|
|
199
|
+
const parsed = JSON.parse(raw);
|
|
200
|
+
handlePayload(parsed, res);
|
|
201
|
+
} catch (err) {
|
|
202
|
+
res.status(400).json({ error: "invalid_json" });
|
|
203
|
+
}
|
|
204
|
+
});
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
endpointMap.get(node.endpoint).add(node);
|
|
210
|
+
|
|
211
|
+
node.on("input", (msg, send, done) => {
|
|
212
|
+
const items = extractItems(msg.payload);
|
|
213
|
+
emitMatches(items, node, send);
|
|
214
|
+
if (done) {
|
|
215
|
+
done();
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
node.on("close", () => {
|
|
220
|
+
const set = endpointMap.get(node.endpoint);
|
|
221
|
+
if (set) {
|
|
222
|
+
set.delete(node);
|
|
223
|
+
if (set.size === 0) {
|
|
224
|
+
endpointMap.delete(node.endpoint);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
RED.nodes.registerType("Starlight", StarlightNode);
|
|
231
|
+
};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="66" height="70" viewBox="0 0 66 70">
|
|
3
|
+
<path d="M32.697 2.98214L34.7147 9.92857C37.9934 21.2679 46.9468 30.1429 58.3862 33.3929L65.394 35.3929L58.3862 37.3929C46.9468 40.6429 37.9934 49.5179 34.7147 60.8571L32.697 67.8036L30.6793 60.8571C27.4006 49.5179 18.4472 40.6429 7.00779 37.3929L0 35.3929L7.00779 33.3929C18.4472 30.1429 27.4006 21.2679 30.6793 9.92857L32.697 2.98214Z" fill="white"/>
|
|
4
|
+
</svg>
|
package/package.json
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "node-red-contrib-starlight",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Node-RED node listening on /starlight and filtering alarm payloads.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"node-red",
|
|
7
|
+
"node-red-contrib",
|
|
8
|
+
"starlight",
|
|
9
|
+
"alarm"
|
|
10
|
+
],
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"author": "",
|
|
13
|
+
"main": "nodes/rejestrator.js",
|
|
14
|
+
"node-red": {
|
|
15
|
+
"nodes": {
|
|
16
|
+
"rejestrator": "nodes/rejestrator.js"
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|