Skip to content

Instantly share code, notes, and snippets.

@Love4yzp
Last active October 20, 2025 08:03
Show Gist options
  • Select an option

  • Save Love4yzp/2fccdfa6a2d8e64e2740cd566b9b991c to your computer and use it in GitHub Desktop.

Select an option

Save Love4yzp/2fccdfa6a2d8e64e2740cd566b9b991c to your computer and use it in GitHub Desktop.
NodeRED-Jetson Gun Detection Alert
[
{
"id": "827a3420678b76d2",
"type": "tab",
"label": "Frigate Gun Detection",
"disabled": false,
"info": ""
},
{
"id": "config_frigate_url",
"type": "inject",
"z": "827a3420678b76d2",
"name": "Config Frigate URL",
"props": [
{
"p": "frigateUrl",
"v": "http://192.168.101.126:5000",
"vt": "str"
}
],
"repeat": "",
"crontab": "",
"once": true,
"onceDelay": 0.1,
"topic": "",
"x": 130,
"y": 120,
"wires": [
[
"config_saver"
]
]
},
{
"id": "config_wechat_webhook",
"type": "inject",
"z": "827a3420678b76d2",
"name": "Config Webhook",
"props": [
{
"p": "webhookUrl",
"v": "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=xxxx35b8-1198-486a-aaec-xxxxb122fd35",
"vt": "str"
}
],
"repeat": "",
"crontab": "",
"once": true,
"onceDelay": 0.1,
"topic": "",
"x": 130,
"y": 160,
"wires": [
[
"config_saver"
]
]
},
{
"id": "config_saver",
"type": "function",
"z": "827a3420678b76d2",
"name": "Save Config to Global",
"func": "// 获取旧状态\nlet oldFrigate = global.get(\"frigateUrl\");\nlet oldwebhook = global.get(\"webhookUrl\");\n\n// 更新全局变量\nif (msg.frigateUrl) {\n global.set(\"frigateUrl\", msg.frigateUrl);\n}\nif (msg.webhookUrl) {\n global.set(\"webhookUrl\", msg.webhookUrl);\n}\n\n// 读取当前状态\nlet frigate = global.get(\"frigateUrl\");\nlet webhook = global.get(\"webhookUrl\");\n\n// 生成状态文本\nlet statusParts = [];\nif (frigate) statusParts.push(\"Frigate ✅\");\nelse statusParts.push(\"Frigate ❌\");\n\nif (webhook) statusParts.push(\"webhook ✅\");\nelse statusParts.push(\"webhook ❌\");\n\nlet statusText = statusParts.join(\" | \");\n\n// 更新节点状态(绿色:都OK;黄色:部分缺;红色:都没配)\nlet color = \"red\";\nif (frigate && webhook) color = \"green\";\nelse if (frigate || webhook) color = \"yellow\";\n\nnode.status({ fill: color, shape: \"dot\", text: statusText });\n\n// 方便下游查看\nmsg.frigateUrl = frigate;\nmsg.webhookUrl = webhook;\nmsg.statusText = statusText;\n\nreturn msg;",
"outputs": 1,
"timeout": "",
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 420,
"y": 140,
"wires": [
[]
]
},
{
"id": "709efb48944e7b98",
"type": "mqtt in",
"z": "827a3420678b76d2",
"name": "Frigate Reviews",
"topic": "frigate/reviews",
"qos": "0",
"datatype": "auto-detect",
"broker": "0f948328c1975515",
"nl": false,
"rap": false,
"inputs": 0,
"x": 80,
"y": 240,
"wires": [
[
"28fa2bd8baf1f87b",
"c840be7a3c85bf4d",
"fab3af49ea18453d"
]
]
},
{
"id": "28fa2bd8baf1f87b",
"type": "function",
"z": "827a3420678b76d2",
"name": "Extract Gun Event",
"func": "let after = msg.payload.after;\n\nif (!after || !after.data || !after.data.objects.includes(\"gun\")) {\n return null;\n}\n\nconst frigateUrl = global.get(\"frigateUrl\") || \"http://172.17.0.1:5000\";\nnode.log(\"Using FrigateUrl: \"+frigateUrl);\nlet count = flow.get(\"gun_count\") || 0;\ncount++;\nflow.set(\"gun_count\", count);\n\nlet history = flow.get(\"gun_history\") || [];\nhistory.unshift({\n camera: after.camera,\n start: new Date(after.start_time * 1000).toLocaleString(),\n end: after.end_time ? new Date(after.end_time * 1000).toLocaleString() : \"---\",\n thumb: frigateUrl + after.thumb_path.replace(\"/media/frigate\", \"\")\n});\nhistory = history.slice(0, 10);\nflow.set(\"gun_history\", history);\n\nmsg.payload = {\n camera: after.camera,\n start_time: new Date(after.start_time * 1000).toLocaleString(),\n end_time: after.end_time ? new Date(after.end_time * 1000).toLocaleString() : \"---\",\n thumb: frigateUrl + after.thumb_path.replace(\"/media/frigate\", \"\"),\n url: frigateUrl + after.thumb_path.replace(\"/media/frigate\", \"\"),\n count: count,\n};\n\nreturn msg;",
"outputs": 1,
"timeout": "",
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 330,
"y": 300,
"wires": [
[
"6861bbcee3c4a09f",
"bc22b4eb2e47f012",
"c05e97b33779bd4a"
]
]
},
{
"id": "319c9221c9a18dfc",
"type": "ui-template",
"z": "827a3420678b76d2",
"group": "g1h2i3j4k5l6m7n8",
"page": "",
"ui": "",
"name": "Gun Event Card",
"order": 2,
"width": "0",
"height": "0",
"format": "<template>\n <div v-if=\"msg && msg.payload\">\n <div v-for=\"(cam, name) in msg.payload\" :key=\"name\" style=\"margin-bottom:16px; padding:12px; border-radius:12px; box-shadow:0 2px 8px rgba(0,0,0,0.1); background:#fff;\">\n <h3 style=\"color:#0094CE;\">摄像头:{{ name }} </h3>\n <p style=\"font-size:14px; margin:0;\">\n <strong>开始:</strong>{{ cam.start }}<br>\n <strong>结束:</strong>{{ cam.end }}\n </p>\n <div :href=\"cam.thumb\" target=\"_blank\">\n <img :src=\"cam.thumb\" style=\"max-width:100%; border-radius:8px; cursor: zoom-in;\" />\n </div>\n </div>\n </div>\n <div v-else>\n <p>等待枪支检测数据...</p>\n </div>\n</template>\n\n<script>\nexport default {\n props: ['msg']\n}\n</script>\n",
"storeOutMessages": true,
"passthru": true,
"templateScope": "local",
"className": "",
"x": 1020,
"y": 160,
"wires": [
[]
]
},
{
"id": "824bcb218744ed40",
"type": "ui-text",
"z": "827a3420678b76d2",
"group": "g1h2i3j4k5l6m7n8",
"order": 3,
"width": "0",
"height": "0",
"name": "Gun Count",
"label": "枪支检测次数",
"format": "<span style='background:#ff4d4f; color:#fff; padding:4px 12px; border-radius:12px; font-weight:bold;'>{{msg.payload.count}}</span>",
"layout": "",
"style": false,
"font": "",
"fontSize": "",
"color": "#000000",
"wrapText": false,
"className": "",
"x": 1010,
"y": 240,
"wires": []
},
{
"id": "6861bbcee3c4a09f",
"type": "function",
"z": "827a3420678b76d2",
"name": "Gun Count Pass",
"func": "msg.payload = msg.payload.count;\nreturn msg;",
"outputs": 1,
"timeout": "",
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 620,
"y": 240,
"wires": [
[
"824bcb218744ed40"
]
]
},
{
"id": "ab0188e1c37351c1",
"type": "ui-table",
"z": "827a3420678b76d2",
"group": "g1h2i3j4k5l6m7n8",
"name": "History Table",
"label": "",
"order": 4,
"width": "0",
"height": "0",
"maxrows": "5",
"autocols": false,
"showSearch": true,
"deselect": true,
"selectionType": "none",
"columns": [
{
"title": "摄像头",
"key": "camera",
"keyType": "key",
"type": "text",
"width": ""
},
{
"title": "开始",
"key": "start",
"keyType": "key",
"type": "text",
"width": ""
},
{
"title": "结束",
"key": "end",
"keyType": "key",
"width": ""
},
{
"title": "截图",
"key": "thumb",
"keyType": "key",
"type": "image",
"width": "",
"align": "start"
}
],
"mobileBreakpoint": "sm",
"mobileBreakpointType": "defaults",
"action": "append",
"x": 1010,
"y": 360,
"wires": [
[]
]
},
{
"id": "bc22b4eb2e47f012",
"type": "function",
"z": "827a3420678b76d2",
"name": "get history from flow",
"func": "msg.payload = flow.get(\"gun_history\") || [];\nreturn msg;",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 640,
"y": 360,
"wires": [
[
"ab0188e1c37351c1"
]
]
},
{
"id": "c05e97b33779bd4a",
"type": "function",
"z": "827a3420678b76d2",
"name": "prepare msg for alert",
"func": "msg.payload = {\n \"msgtype\": \"text\",\n \"text\": {\n \"content\": msg.payload.camera + \"检测到枪支\"\n }\n}\nreturn msg;",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 640,
"y": 300,
"wires": [
[
"568e88e840e7b711"
]
]
},
{
"id": "c840be7a3c85bf4d",
"type": "function",
"z": "827a3420678b76d2",
"name": "to cameras",
"func": "let after = msg.payload.after;\nif (!after || !after.data || !after.data.objects.includes(\"gun\")) return null;\n\nconst frigateUrl = global.get(\"frigateUrl\") || \"http://172.17.0.1:5000\";\n\n// 读取全局摄像头数据\nlet cameras = flow.get(\"camera_data\") || {};\n\ncameras[after.camera] = {\n start: new Date(after.start_time*1000).toLocaleString(),\n end: after.end_time ? new Date(after.end_time*1000).toLocaleString() : \"---\",\n thumb: frigateUrl + after.thumb_path.replace(\"/media/frigate\",\"\")\n};\n\n// 存回 Flow\nflow.set(\"camera_data\", cameras);\n\n// 传给 UI 模板渲染\nmsg.payload = cameras;\nreturn msg;\n",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 610,
"y": 160,
"wires": [
[
"319c9221c9a18dfc"
]
]
},
{
"id": "c2c2f98f485df73b",
"type": "ui-button",
"z": "827a3420678b76d2",
"group": "g1h2i3j4k5l6m7n8",
"name": "clear",
"label": "Clear All Data",
"order": 0,
"width": 0,
"height": 0,
"emulateClick": false,
"tooltip": "",
"color": "",
"bgcolor": "",
"className": "",
"icon": "",
"iconPosition": "left",
"payload": "{}",
"payloadType": "str",
"topic": "topic",
"topicType": "msg",
"buttonColor": "",
"textColor": "",
"iconColor": "",
"enableClick": true,
"enablePointerdown": false,
"pointerdownPayload": "",
"pointerdownPayloadType": "str",
"enablePointerup": false,
"pointerupPayload": "",
"pointerupPayloadType": "str",
"x": 110,
"y": 480,
"wires": [
[
"1f42f64aaa7aab78"
]
]
},
{
"id": "1f42f64aaa7aab78",
"type": "function",
"z": "827a3420678b76d2",
"name": "clean all data in flow",
"func": "flow.set(\"camera_data\", {});\nflow.set(\"gun_count\", 0);\nflow.set(\"gun_history\", []);\nreturn msg;",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 320,
"y": 480,
"wires": [
[
"22afdedff5a7e7fd",
"beabb7e2f483249f",
"44568dce857793da"
]
]
},
{
"id": "22afdedff5a7e7fd",
"type": "function",
"z": "827a3420678b76d2",
"name": "clean history",
"func": "msg.payload = []\nreturn msg;",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 610,
"y": 520,
"wires": [
[
"ab0188e1c37351c1"
]
]
},
{
"id": "beabb7e2f483249f",
"type": "function",
"z": "827a3420678b76d2",
"name": "clean counter",
"func": "msg.payload = 0\nreturn msg;",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 620,
"y": 480,
"wires": [
[
"824bcb218744ed40"
]
]
},
{
"id": "44568dce857793da",
"type": "function",
"z": "827a3420678b76d2",
"name": "clean snapshot",
"func": "msg.payload = {}\nreturn msg;",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 620,
"y": 440,
"wires": [
[
"319c9221c9a18dfc"
]
]
},
{
"id": "568e88e840e7b711",
"type": "function",
"z": "827a3420678b76d2",
"name": "Send to WeChat",
"func": "const webhook = global.get(\"wechatWebhook\") || \"https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=xxxx35b8-1198-486a-aaec-xxxxb122fd35\";\n\nmsg.url = webhook;\nmsg.method = \"POST\";\nmsg.headers = {\n \"Content-Type\": \"application/json\"\n};\n\nreturn msg;",
"outputs": 1,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 850,
"y": 300,
"wires": [
[
"http_request_node"
]
]
},
{
"id": "http_request_node",
"type": "http request",
"z": "827a3420678b76d2",
"name": "HTTP Request",
"method": "use",
"ret": "txt",
"paytoqs": "ignore",
"url": "",
"tls": "",
"persist": false,
"proxy": "",
"insecureHTTPParser": false,
"authType": "",
"senderr": false,
"x": 1050,
"y": 300,
"wires": [
[]
]
},
{
"id": "fab3af49ea18453d",
"type": "debug",
"z": "827a3420678b76d2",
"name": "debug 1",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "false",
"statusVal": "",
"statusType": "auto",
"x": 280,
"y": 400,
"wires": []
},
{
"id": "0f948328c1975515",
"type": "mqtt-broker",
"name": "",
"broker": "172.17.0.1",
"port": "1883",
"clientid": "",
"autoConnect": true,
"usetls": false,
"protocolVersion": "4",
"keepalive": "15",
"cleansession": true,
"autoUnsubscribe": true,
"birthTopic": "",
"birthQos": "0",
"birthPayload": "",
"birthMsg": {},
"closeTopic": "",
"closePayload": "",
"closeMsg": {},
"willTopic": "",
"willQos": "0",
"willPayload": "",
"willMsg": {},
"userProps": "",
"sessionExpiry": ""
},
{
"id": "g1h2i3j4k5l6m7n8",
"type": "ui-group",
"name": "Frigate Events",
"page": "h1i2j3k4l5m6n7o8",
"width": "10",
"height": "17",
"order": 1,
"showTitle": true,
"className": "",
"visible": "true",
"disabled": "false",
"groupType": "default"
},
{
"id": "h1i2j3k4l5m6n7o8",
"type": "ui-page",
"name": "Frigate Page",
"ui": "f121584d21d465f1",
"path": "/frigate",
"icon": "",
"layout": "grid",
"theme": "6666b6af5668e7b2",
"breakpoints": [
{
"name": "Default",
"px": "0",
"cols": "3"
},
{
"name": "Tablet",
"px": "576",
"cols": "6"
},
{
"name": "Small Desktop",
"px": "768",
"cols": "9"
},
{
"name": "Desktop",
"px": "1024",
"cols": "12"
}
],
"order": 1,
"className": "",
"visible": "true",
"disabled": "false"
},
{
"id": "f121584d21d465f1",
"type": "ui-base",
"name": "My Dashboard",
"path": "/dashboard",
"headerContent": "page",
"titleBarStyle": "default",
"showReconnectNotification": true,
"notificationDisplayTime": 5,
"showDisconnectNotification": true,
"allowInstall": true
},
{
"id": "6666b6af5668e7b2",
"type": "ui-theme",
"name": "Default Theme",
"colors": {
"surface": "#ffffff",
"primary": "#0094CE",
"bgPage": "#eeeeee",
"groupBg": "#ffffff",
"groupOutline": "#cccccc"
},
"sizes": {
"density": "default",
"pagePadding": "12px",
"groupGap": "12px",
"groupBorderRadius": "4px",
"widgetGap": "12px"
}
},
{
"id": "43cb0335dbddd3e7",
"type": "global-config",
"env": [],
"modules": {
"@flowfuse/node-red-dashboard": "1.29.0"
}
}
]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment