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 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
+