VMS
BPP-VMS ULTIMATE MASTER GUIDE ตชด.215

คู่มือปฏิบัติการระบบความมั่นคงและกล้องวงจรปิดอัจฉริยะ (ฉบับรวมไฟล์เดียวบริบูรณ์ 100%)

🛡️ BPP-VMS (ตชด.215) • n8n Complete All-in-One Masterclass Guide

คู่มือการสอนฉบับสมบูรณ์: ติดตั้ง n8n, ออกแบบบอททุกประเภท & เจาะลึกการเขียนสคริปต์

รวบรวมครบจบในที่เดียว: ติดตั้ง Local ➔ นำเข้า Docker ➔ สถาปัตยกรรม 7 โหนด ➔ กฎ 4 เลเยอร์สร้างบอท ➔ แคตตาล็อกโหนด ➔ ชำแหละสคริปต์แจ้งเตือนบรรทัดต่อบรรทัด

หลักสูตร All-in-One ครบ 6 บทเรียน ● ไฟล์เดียวแชร์ได้ทันที
1

บทที่ 1: ขั้นตอนการติดตั้ง n8n บนเครื่อง Local (Windows) ตั้งแต่ 0

สำหรับผู้เริ่มต้นที่ยังไม่มีโปรแกรมอะไรในเครื่องเลย ทดลองต่อโหนดบนเครื่องตัวเองก่อนนำขึ้นระบบจริง

Local Host Mode
1

ติดตั้ง Node.js (เครื่องยนต์รันโปรแกรม)

n8n พัฒนาด้วย JavaScript บน Node.js ผู้เรียนต้องติดตั้งเวอร์ชัน LTS (แนะนำ v20.x ขึ้นไป)

ดาวน์โหลดตัวติดตั้ง: https://nodejs.org (เลือก LTS)
เมื่อติดตั้งเสร็จ ให้เปิด PowerShell ตรวจสอบคำสั่ง:
node -v
npm -v
⚠️ หากพบข้อผิดพลาด PSSecurityException (cannot be loaded because running scripts is disabled):

เกิดจากนโยบายความปลอดภัยเริ่มต้นของ Windows PowerShell บล็อกไม่ให้รันสคริปต์ .ps1 ให้แก้ไขโดยพิมพ์คำสั่งด้านล่างนี้ใน PowerShell:

# หากเปิดเทอร์มินัลในฐานะ Administrator:
Set-ExecutionPolicy RemoteSigned

# หรือหากเปิดเทอร์มินัลแบบผู้ใช้ทั่วไป (ไม่จำเป็นต้องเป็น Admin):
Set-ExecutionPolicy RemoteSigned -Scope CurrentUser

*เมื่อระบบขึ้นข้อความให้ยืนยัน ให้พิมพ์ Y (Yes) หรือ A (Yes to All) แล้วกด Enter จากนั้นทดสอบรัน npm -v อีกครั้ง

2

คำสั่งรัน n8n บนเครื่อง Local (2 ทางเลือก)

วิธี A: รันด่วนทันที ไม่ต้องติดตั้ง (แนะนำที่สุด):
npx n8n
วิธี B: ติดตั้งแบบถาวรลงเครื่อง Windows:
npm install n8n -g
n8n start
3

เข้าใช้งาน Web UI ครั้งแรก

เมื่อขึ้นข้อความว่า "Editor is now accessible via:" ให้เปิดเบราว์เซอร์ไปที่:

กรอกอีเมลและรหัสผ่านเพื่อตั้ง Owner Account แล้วเริ่มสร้าง Workflow ได้ทันที
4

การ Export Workflow ออกเป็นไฟล์ JSON

เมื่อต่อโหนดบน Local เสร็จ ให้ดึงไฟล์ออกมาเพื่อเตรียมเอาเข้า Docker:

  • ผ่านหน้าเว็บ: กดเมนูจุด 3 จุดมุมบนขวาใน Workflow ➔ เลือก Download
  • ผ่าน Terminal: n8n export:workflow --all --output=backup.json
2

บทที่ 2: ขั้นตอนนำ n8n เข้าสู่ Docker ในโปรเจกต์ (Production 24/7)

เปลี่ยนจากการเปิด Terminal ค้างไว้ มาเป็นบริการในเบื้องหลังที่รันอัตโนมัติและเชื่อมคุยกับ Frigate

📌 ทำไมต้องย้ายเข้า Docker ในโปรเจกต์?

  • ทำงาน 24 ชั่วโมงอัตโนมัติ: รีบูตเครื่องคอมพิวเตอร์ Container ก็ยังคงสตาร์ทขึ้นมาเองอัตโนมัติ
  • เชื่อมต่อ Frigate/กล้องโดยตรง: สามารถยิง API หา Frigate ที่ http://frigate:5000 ผ่านเครือข่าย Docker ภายในได้เลย ไม่ต้องผ่านอินเทอร์เน็ต
  • Named Volume (n8n-data): โหนดและการตั้งค่าทั้งหมดถูกเซฟลงฮาร์ดดิสก์ถาวร ปิดคอนเทนเนอร์ข้อมูลก็ไม่หาย

💻 คำสั่ง Terminal ที่ใช้สอน:

# เข้าโฟลเดอร์ Frigate NVR
cd D:\BPP-VMS\frigate-nvr

# สตาร์ท n8n service บน Docker
docker compose up -d n8n

# นำไฟล์ workflow จาก Local import เข้าสู่ Container
docker exec n8n-service n8n import:workflow --input=/n8n-init/workflows/bpp_vms_master_bot_workflow.json

# สั่ง restart n8n container 1 ครั้งเพื่อโหลดค่าใหม่
docker restart n8n-service
3

บทที่ 3: สถาปัตยกรรม 7 โหนดของ BPP-VMS & การสกัดข้อมูล (Data Extraction)

ทำความเข้าใจบทบาทของแต่ละโหนดในระบบจริง พร้อมการสกัดข้อความ (AI) และภาพถ่าย (EXIF)

คลิกที่โหนดเพื่อดูการตั้งค่า
n8n Canvas Workflow: BPP-VMS Master Bot (จำลองผังกระดานจริง)
📥 ดาวน์โหลดไฟล์ (.json) 👆 คลิกโหนดเพื่อดูวิธีตั้งค่า
💡 วิธีนำ Workflow สำเร็จรูปเข้าสู่โปรแกรม n8n (เลือกได้ 3 วิธีตามความสะดวก): ไม่ต้องลากสายต่อเองทีละโหนด
📁 วิธีที่ 1: ลากวางไฟล์ (Drag & Drop)

1. กดปุ่ม 📥 ดาวน์โหลดไฟล์ (.json) ด้านบน
2. เปิดหน้าเว็บ n8n (http://localhost:5678) แล้วเปิด Workflow เปล่า
3. ลากไฟล์ bpp_vms_master_bot_workflow.json จากในเครื่อง โยนลงบนหน้าจอกระดาน n8n ได้เลย! ผังทั้ง 8 โหนดจะโผล่ขึ้นมาทันที

✨ ง่ายที่สุด ลากไฟล์ปล่อยบน Canvas ได้เลย
⚡ วิธีที่ 2: กดคัดลอกแล้ววาง (Ctrl + V)

1. กดปุ่ม 📋 คัดลอกโค้ดทั้ง Workflow ด้านบน
2. สลับไปที่หน้าต่างโปรแกรม n8n บนเบราว์เซอร์
3. คลิกบนพื้นที่ว่างของกระดาน แล้วกดปุ่มคีย์บอร์ด Ctrl + V ผังทั้ง 8 โหนดจะปรากฏพร้อมใช้งานทันทีใน 1 วินาที!

⚡ เร็วที่สุด ไม่ต้องดาวน์โหลดไฟล์ลงเครื่อง
🚀 วิธีที่ 3: ดับเบิลคลิกสคริปต์อัตโนมัติ

1. เปิดโฟลเดอร์ D:\BPP-VMS บนเครื่องคอมพิวเตอร์
2. ดับเบิลคลิกไฟล์ Install_n8n_Bot_Workflow.bat
3. ระบบจะถาม Gemini API Key (หรือกด Enter) แล้วนำเข้า Workflow และกุญแจเข้า Docker n8n ให้อัตโนมัติ 100% พร้อมเปิดหน้าเว็บให้ทันที

🤖 เบ็ดเสร็จ ตั้งค่า API Key และเปิดใช้งานให้ทันที
🧠

บทเรียนปูพื้นฐาน: วิธีอ่านโค้ด Expression และตั้งค่า Switch (สำคัญมาก!)

ก่อนจะเริ่มต่อโหนดบนกระดานจริง เรามาทำความเข้าใจ "ภาษาต่างดาว" (Expression) และ "สี่แยกจราจร" (Switch Node) ด้วยภาพจำลองกันก่อนครับ

1. 🔬 Expression Anatomy Map (ผ่าพิสูจน์โค้ดสูตรดูดค่าทีละชิ้น)

ใน n8n การดึงข้อมูลจากโหนดก่อนหน้าจะใช้สูตรที่เรียกว่า Expression (โหมดดูดค่าสด) ลองดูตัวอย่างคำสั่ง ={{ $json.body.type || 'chat' }} ว่าประกอบด้วยอะไรบ้าง (เปรียบเหมือนการเปิดกล่องพัสดุซ้อนกัน 3 ชั้น):

={{ $json.body.type || 'chat' }} ▲ ▲ ▲ ▲ ▲ ▲ │ │ │ │ │ │ │ │ │ │ │ └─ ค่าสำรองกันพัง (Fallback Value) │ │ │ │ │ ถ้าหาข้อมูล 3 กล่องแรกไม่เจอเลย ให้ใช้คำว่า 'chat' แทน (ระบบจะได้ไม่ล่ม) │ │ │ │ └─ เครื่องหมาย OR (หรือ) │ │ │ └─ เปิดกล่องชั้นในสุด (Field Name) ดึงชื่อประเภทข้อความออกมา │ │ └─ เปิดกล่องชั้นกลาง (Nested Object) เจาะเข้าไปในเนื้อหา (body) │ └─ กล่องพัสดุใบใหญ่ (Current Item JSON) ข้อมูลทั้งหมดที่ไหลมาจากโหนดก่อนหน้า └─ เปิด/ปิด โหมดสูตร (Expression Delimiters) บอกบอทว่านี่ไม่ใช่ข้อความธรรมดา แต่เป็นคำสั่งให้ไปดูดข้อมูลมาโชว์

2. 📋 พจนานุกรมสัญลักษณ์ Expression (Cheat Sheet)

สัญลักษณ์ ชื่อเรียก (ไทย / อังกฤษ) ทำหน้าที่อะไร? ตัวอย่างการใช้งานจริง
={{ }} โหมดดูดค่าสด
Expression Delimiters
เปิดโหมดเขียนสูตร แจ้งโปรแกรมให้คำนวณหรือดึงข้อมูล (ต้องมี = นำหน้า {{ เสมอ) ={{ $json.body.type }}
$json ข้อมูลจากโหนดก่อนหน้า
Current Item Data
ก้อนข้อมูลทั้งหมดที่ส่งผ่านมาตามสาย จากโหนดซ้ายมือสุดมาถึงโหนดปัจจุบัน ={{ $json.body.message }}
. จุดเจาะข้อมูล
Dot Notation
ใช้เชื่อมเพื่อเจาะลึกเข้าไปในกล่องข้อมูลชั้นใน (เหมือนเปิดโฟลเดอร์ซ้อนกัน) ={{ $json.body.type }}
|| ค่าสำรองกันพัง
OR Fallback Operator
ถ้าข้อมูลฝั่งซ้ายว่างเปล่า (หาไม่เจอ) ให้ใช้ค่าฝั่งขวาแทนทันที ป้องกันระบบล่ม ={{ $json.group || 'ไม่ระบุ' }}
{{ }} โหมดแทรกตัวแปร (ใน Code)
Template Literal
ใช้ในโหนดเขียนโค้ด (Code Node) ร่วมกับสัญลักษณ์ ` ` เพื่อแทรกตัวแปรลงในข้อความยาวๆ `พบรถป้ายทะเบียน: ${plate}`

3. 🎯 ภาพจำลองการตั้งค่า Switch Node (สี่แยกจราจร)

เมื่อดับเบิลคลิกเปิดโหนด Route by Message Type ขึ้นมา จะพบหน้าจอดังนี้ ให้กดตั้งค่าตามลูกศรชี้:

Rules ▼
fx
={{ $json.body.type || 'chat' }}
Routing Rules (3) + Add Routing Rule
Output 0 if String contains chat
Output 1 if String contains photo
Output 2 if String contains alert

4. 🎮 สนามจำลอง Data Flow ของ Switch (Interactive)

ทดลองกดเปลี่ยนค่า type ที่ส่งเข้ามา แล้วดูว่า Switch (สี่แยกจราจร) จะคัดแยกส่งไปโหนดไหน (Output ขาใด):

1. ข้อมูล JSON ที่ไหลเข้ามาใน Switch:
{ "body": { "type": "chat", ... } }
2. Switch ดูดค่าด้วยสูตร:
={{ $json.body.type || 'chat' }} ➔ ดึงค่าได้: chat
3. ผลการตัดสินใจ (Routing Rule):
✅ ตรงกับ Rule 0 ➔ ไหลออกพอร์ต Output 0 (ไป AI Agent)

5. ❓ ปัญหาที่พบบ่อย (Troubleshooting FAQ)

❌ อาการที่เจอ (ปัญหา) 🔍 สาเหตุ ✅ วิธีแก้
1. Switch ไม่ยอมแยกสาย ข้อมูลไม่ไหลไปไหนเลย (ค้าง) ช่อง Mode ของโหนด Switch ยังเป็นค่าเดิม (Expression) ไม่ได้เปลี่ยนเป็นแบบตั้งกฎ (Rules) ในหน้าต่าง Switch ให้เปลี่ยนช่องแรกสุด Mode เป็น `Rules`
2. สูตร Expression ขึ้นตัวอักษรสีแดงเตือนว่า Invalid พิมพ์วงเล็บปีกกาผิด หรือลืมใส่เครื่องหมายเท่ากับ (=) นำหน้าสูตร ลบแล้วพิมพ์ใหม่ให้ถูกต้องเป็น ={{ }} ห้ามเว้นวรรคระหว่างปีกกา
3. ต่อสายเสร็จหมดแล้ว แต่โหนดปลายทางบอกว่าไม่ได้รับข้อมูล คุณลากสายออกจากจุด Output หมุดผิดเบอร์ (เช่น ต้องลากจากหมุดบนสุด 0 แต่ไปลากหมุดกลาง 1 แทน) ลบสายเดิม (เอาเมาส์ชี้ที่สายแล้วกดรูปถังขยะ) แล้วลากใหม่จากจุด Output ขา 0 (บนสุด)
4. โหนด Respond to Webhook (#7) ขึ้น Error ว่า "No data returned" โหนดนี้ลอยอยู่เดี่ยวๆ ไม่ได้ถูกลากสายมาจากโหนด AI Agent, Photo หรือ LPR ต้องลากสาย Output ฝั่งขวา ของทุกสายงาน มาบรรจบเข้าที่โหนด #7 เสมอ
1. TRIGGER
#1 Webhook POST
/bpp-vms-master
Output: body
รับ Webhook จากภายนอก
2. ROUTER
#2 Switch Type Rules
=body.type
0 ➔ AI ChatRule 0
1 ➔ Photo EXIFRule 1
2 ➔ LPR AlertRule 2
แยกสาย 3 เส้นทาง
3. EXTRACTIONS
#3 AI Agent ⭐ Branch 0
สกัด Entity ภาษาไทย
บุคคล ยาเสพติด ยานพาหนะ
#6 Photo Code ⭐ Branch 1
สกัด EXIF GPS & Dossier
lat/lng ➔ Heatmap
#5 Alert Formatter Branch 2
จัดฟอร์แมต LPR Card
Telegram Markdown Text
AI PLUGINS (SUB-NODES)
🔌 ต่อเข้ากับขั้วของโหนด #3
#4 Gemini Model 🧠
Flash Lite (สมอง AI)
ขั้วต่อ: Model Pin
#8 Watchlist Tool 🔍
HTTP API เช็กทะเบียน
ขั้วต่อ: Tool Pin
4. OUTPUT
#7 Respond Webhook 📡
JSON Payload Return
200 OK Response
ส่งคำตอบกลับปลายทาง
โหนด #3 (คลิกเลือกอยู่)

AI Intelligence Agent (การสกัดข้อมูลข้อความภาษาไทย)

@n8n/n8n-nodes-langchain.agent
โหมดตั้งค่า: Parameters Panel Mockup
👉 1. ต้องกดตรงไหน? (ขั้นตอนเลือกโหนดและเปิดเมนูบน Canvas)
💡 3. ทำไมต้องทำแบบนี้? (เหตุผลเชิงสถาปัตยกรรม & กฎเหล็ก)
✍️ 2. ตารางเจาะลึกการตั้งค่าพารามิเตอร์ 4 มิติ (4D Parameter Deep Dive) 📐 แสดงผลเต็มความกว้าง พอดีหน้าจอ ไม่ต้องเลื่อนแนวนอน (Zero Horizontal Scroll)
📋 โค้ด/คอนฟิกเต็มของโหนดนี้ (Raw JSON / Script): พร้อมนำไปวางใน n8n

        
🔗 สเต็ปการต่อสายเชื่อมโยงสู่โหนดถัดไป (Step-by-Step Wiring & Node Navigator)
ขั้นตอนที่ 1 จาก 8
🎯 คำแนะนำการลากสายเชื่อมต่อ (Wiring Guide):
COMPLETE REFERENCE 📖 แสดงผลถาวร อ่านได้ทันทีไม่ต้องคลิก

สารบัญคู่มือเจาะลึก 4 มิติและการตั้งค่าทั้ง 8 โหนดฉบับสมบูรณ์

รวมขั้นตอนการกดบน Canvas, ตารางพารามิเตอร์ 4 มิติพร้อมมาร์กจุดตายสีแดง, เหตุผลทางสถาปัตยกรรม, โค้ด JSON พร้อมคัดลอก และแผนผังการต่อสายทุกโหนด

โหนด #1 (Webhook Trigger)

Master Webhook Trigger (จุดรับข้อมูลเข้า)

n8n-nodes-base.webhook
ขั้นตอนที่ 1 จาก 8
👉 1. ต้องกดตรงไหน? (ขั้นตอนเลือกโหนดและเปิดเมนูบน Canvas)
  1. กดปุ่ม + (Add Node) บนมุมขวาบนของ Canvas หรือกดปุ่ม Tab
  2. พิมพ์ค้นหาคำว่า Webhook แล้วกดเลือกโหนด
  3. ดับเบิลคลิกที่โหนดบน Canvas เพื่อเปิดหน้าต่าง Parameters Panel
  4. ที่ช่อง HTTP Method คลิกเลือกเป็น POST
  5. ที่ช่อง Path พิมพ์คำว่า: bpp-vms-master
  6. เลื่อนลงมาที่หัวข้อ Response Mode เปลี่ยนเป็น Using 'Respond to Webhook' Node
💡 3. ทำไมต้องทำแบบนี้? (เหตุผลเชิงสถาปัตยกรรม & กฎเหล็ก)
  • ทำไมต้องใช้ POST? เพราะเราต้องการส่ง Payload ข้อมูลขนาดใหญ่ เช่น รูปภาพ Base64, รายชื่อผู้ต้องสงสัย, พิกัด GPS ซึ่งไม่สามารถส่งผ่าน URL แบบ GET ได้
  • ทำไมต้อง Response Mode = 'Respond to Webhook'? เพื่อให้ n8n รอให้กระบวนการสกัดข้อมูลของ AI (โหนด #3 หรือ #6) ทำงานเสร็จก่อน แล้วค่อยส่งข้อมูล JSON ที่สกัดเสร็จแล้วตอบกลับไปให้แอปมือถือหรือ Dashboard
✍️ 2. ตารางเจาะลึกการตั้งค่าพารามิเตอร์ 4 มิติ (4D Parameter Deep Dive) 📐 แสดงผลเต็มความกว้าง พอดีหน้าจอ ไม่ต้องเลื่อนแนวนอน (Zero Horizontal Scroll)
พารามิเตอร์ & ค่าที่ตั้ง คืออะไร & หมายความว่ายังไง? ทำทำไม? (เหตุผล & ตรรกะ) ทำแล้วได้อะไร? (ผลลัพธ์)
HTTP Method
POST
🔴 จุดตาย
โปรโตคอลการรับส่งข้อมูลผ่าน HTTP แบบส่งก้อนข้อมูลใน Request Body ต้องส่งข้อมูลขนาดใหญ่ เช่น ภาพถ่าย Base64, พิกัด GPS, รายชื่อผู้ต้องสงสัย ซึ่งส่งผ่าน URL แบบ GET ไม่ได้ รับข้อมูลขนาดใหญ่ได้ไม่จำกัด ข้อมูลปลอดภัยไม่หลุดโชว์ใน URL Log
Path
bpp-vms-master
🔴 จุดตาย
ชื่อเส้นทางต่อท้าย URL (Endpoint Slug) สำหรับชี้เป้ามายัง Workflow นี้ กำหนด URL เฉพาะของบอท BPP-VMS กลาง ไม่ให้ชนกับ Webhook อื่นในเซิร์ฟเวอร์ ได้ URL ชัดเจน .../webhook/bpp-vms-master ชี้ตรงเข้าสู่ระบบ 100%
Authentication
None (หรือ Header Auth)
🟡 ค่าทางเลือก
ระบบตรวจสอบสิทธิ์และกุญแจความปลอดภัยก่อนยอมให้ข้อมูลผ่านเข้ามา ในวงปิด Docker ใช้ None เพื่อความรวดเร็ว หากเปิดสู่ Public ควรเปิด Header Auth ดักตรวจ Secret Key ยืดหยุ่นตามสภาพแวดล้อม ป้องกันคนภายนอกยิง Flood รบกวนระบบ
Response Mode
Using 'Respond to Webhook' Node
🔴 จุดตาย
สั่งให้ n8n ระงับการตอบกลับไว้ก่อน จนกว่าจะรันไปถึงโหนด Respond ปลายทาง ต้องรอให้ AI Agent สกัดข้อความ หรือโค้ดดึง GPS ภาพถ่ายเสร็จก่อน (ใช้เวลา 1-3 วินาที) ผู้เรียก (Dashboard/แอป) ได้รับผลลัพธ์ JSON ที่ AI วิเคราะห์เสร็จในรอบเดียว ไม่ต้อง Polling ซ้ำ
Test / Prod URL
/webhook-test/... vs /webhook/...
🟢 ประสิทธิภาพ
URL สองโหมด: โหมดทดสอบรายครั้ง และโหมดรันระบบจริง 24/7 Test URL ใช้ดีบักตอนกด Listen บนหน้าจอ ส่วน Production URL ทำงานอัตโนมัติเมื่อกดสวิตช์ Active ทดสอบระบบได้ปลอดภัย ข้อมูลทดสอบไม่ปะปนกับฐานข้อมูลสายตรวจจริง
📋 โค้ด/คอนฟิกเต็มของโหนดนี้ (Raw JSON / Script):
{
  "parameters": {
    "httpMethod": "POST",
    "path": "bpp-vms-master",
    "responseMode": "responseNode",
    "options": {}
  },
  "id": "webhook-inbound-trigger",
  "name": "Master Webhook Trigger",
  "type": "n8n-nodes-base.webhook",
  "typeVersion": 2,
  "position": [200, 300]
}
🔗 แผนผังและสเต็ปการต่อสายเชื่อมโยง: ขั้นตอนที่ 1 จาก 8: ตัวจุดชนวนรับคำสั่ง (Ingress Webhook)
📍 ตำแหน่งและหน้าที่: เป็น "จุดเริ่มต้นแรกสุด" ของ Workflow (Trigger Node) รับข้อมูลคำขอ HTTP POST เข้ามาจากระบบภายนอกตลอด 24 ชม.
🔌 ขั้นตอนการลากสายเชื่อมต่อ (Wiring Step):
  1. สังเกตที่ จุดวงกลมสีเทาด้านขวา (Output Port) ของโหนด Webhook นี้
  2. คลิกเมาส์ซ้ายค้างไว้ แล้วลากสายสัญญาณไปทางขวา
  3. ปล่อยเมาส์ลงที่ จุดวงกลมสีเทาด้านซ้าย (Input Port) ของโหนด #2 (Route by Message Type - Switch Router)

💡 สิ่งที่ส่งต่อไปยังโหนดถัดไป: ส่ง Payload JSON ทั้งก้อนที่ได้รับจาก Client ไปให้โหนด Switch คัดกรองประเภท

โหนด #2 (Switch Router)

Route by Message Type (คัดแยกประเภทข้อมูล)

n8n-nodes-base.switch
ขั้นตอนที่ 2 จาก 8
👉 1. ต้องกดตรงไหน? (ขั้นตอนเลือกโหนดและเปิดเมนูบน Canvas)
  1. ลากสายจากหมุด Output ของโหนด #1 Webhook ออกมา ปล่อยเมาส์แล้วค้นหา Switch
  2. ดับเบิลคลิกเปิดหน้าต่าง Switch ขึ้นมา
  3. ที่หัวข้อ Mode เลือกเป็น Rules
  4. ที่ช่อง Value 1 ให้คลิกที่ไอคอนรูปเฟืองหรือสลับเป็นโหมด Expression แล้วใส่โค้ด: ={{ $json.body.type || 'chat' }}
  5. กดปุ่ม + Add Routing Rule เพิ่ม 3 เงื่อนไข:
    • Output 0: String เท่ากับ chat
    • Output 1: String เท่ากับ photo
    • Output 2: String เท่ากับ alert
💡 3. ทำไมต้องทำแบบนี้? (เหตุผลเชิงสถาปัตยกรรม & กฎเหล็ก)
  • ทำไมต้องมีโหนดนี้? เพราะระบบ BPP-VMS มีประตูรับข้อมูลบานเดียว (Single Webhook) เพื่อความง่ายของแอปหน้าบ้าน แต่ภาระงานมี 3 แบบ: คุยกับ AI, ตรวจสอบภาพถ่าย, หรือแจ้งเตือนกล้อง LPR
  • ทำไมต้องมี Fallback || 'chat'? ป้องกันกรณีที่ผู้ใช้ยิงข้อความเข้ามาตรงๆ โดยไม่ได้ระบุฟิลด์ type ระบบจะอนุโลมให้เป็นประเภท 'chat' ทันที ทำให้ Workflow ไม่หลุดตกค้าง
✍️ 2. ตารางเจาะลึกการตั้งค่าพารามิเตอร์ 4 มิติ (4D Parameter Deep Dive) 📐 แสดงผลเต็มความกว้าง พอดีหน้าจอ ไม่ต้องเลื่อนแนวนอน (Zero Horizontal Scroll)
พารามิเตอร์ & ค่าที่ตั้ง คืออะไร & หมายความว่ายังไง? ทำทำไม? (เหตุผล & ตรรกะ) ทำแล้วได้อะไร? (ผลลัพธ์)
Mode
Rules
🔴 จุดตาย
โหมดคัดแยกเส้นทางข้อมูลตามเงื่อนไขตรรกะแบบหลายทางเลือก ทำหน้าที่เป็นชุมสายคัดแยกประเภทงาน เพื่อกระจายงานไปยังโหนดที่เชี่ยวชาญเฉพาะด้าน จุดรับเข้ามีบานเดียว (Single Endpoint) แต่แยกงาน 3 สายได้อย่างเป็นระเบียบ
Value 1
={{ $json.body.type || 'chat' }}
🔴 จุดตาย
ตัวแปรหลักที่ใช้เปรียบเทียบ โดยอ่านฟิลด์ type จาก Request พร้อมใส่ค่าสำรอง 'chat' ป้องกันกรณีแอปหน้าบ้านลืมส่งฟิลด์ type มา ระบบจะอนุโลมให้เป็นแชทคุยกับ AI ทันที Workflow ไม่ตายกลางทาง (Fail-safe) ทุกข้อความได้รับการประมวลผลแน่นอน
Rule 0 (chat)
Output 0 ➔ AI Agent
🟢 สถาปัตยกรรม
ถ้า type เป็น 'chat' ส่งข้อมูลออกพอร์ตที่ 0 ส่งข้อความรายงานเหตุการณ์เข้าสู่โหนด LangChain AI Agent ข้อความได้รับการสกัด Entity ไม่ส่งไปประมวลผลรูปให้เสียเวลา
Rule 1 (photo)
Output 1 ➔ Photo Code
🟢 สถาปัตยกรรม
ถ้า type เป็น 'photo' ส่งข้อมูลออกพอร์ตที่ 1 ส่งชุดภาพถ่ายของกลางเข้าสู่โหนดสกัด EXIF GPS ได้พิกัดแผนที่ละติจูด/ลองจิจูดใน 2 มิลลิวินาที ไม่เปลือง Token AI
Rule 2 (alert)
Output 2 ➔ LPR Formatter
🟢 สถาปัตยกรรม
ถ้า type เป็น 'alert' ส่งข้อมูลออกพอร์ตที่ 2 ส่งข้อมูลทะเบียนรถจากกล้อง LPR เข้าโหนดจัดรูปแบบการ์ด แจ้งเตือนด่วนเข้า Telegram เจ้าหน้าที่สายตรวจทันที Real-time
📋 โค้ด/คอนฟิกเต็มของโหนดนี้ (Raw JSON / Script):
{
  "parameters": {
    "dataType": "string",
    "value1": "={{ $json.body.type || 'chat' }}",
    "rules": {
      "rules": [
        { "value2": "chat", "output": 0 },
        { "value2": "photo", "output": 1 },
        { "value2": "alert", "output": 2 }
      ]
    }
  },
  "id": "switch-router",
  "name": "Route by Message Type",
  "type": "n8n-nodes-base.switch",
  "typeVersion": 3,
  "position": [440, 300]
}
🔗 แผนผังและสเต็ปการต่อสายเชื่อมโยง: ขั้นตอนที่ 2 จาก 8: ตัวแยกเส้นทางตามประเภทข้อมูล (Switch Router)
🔀 ตำแหน่งและหน้าที่: เป็น "สี่แยกจราจร" (Control Flow) รับข้อมูลจากโหนด #1 แล้วคัดแยกออกเป็น 3 เส้นทางตามฟิลด์ body.type
💬 ขา 0: Output ด้านบนสุด
สำหรับประเภท chat: ลากสายไปเสียบเข้า Input ด้านซ้ายของ โหนด #3 (AI Intelligence Agent)
📸 ขา 1: Output ตรงกลาง
สำหรับประเภท photo: ลากสายไปเสียบเข้า Input ด้านซ้ายของ โหนด #6 (Photo & EXIF Extractor)
🚨 ขา 2: Output ล่างสุด
สำหรับประเภท alert: ลากสายไปเสียบเข้า Input ด้านซ้ายของ โหนด #5 (Smart Alert Formatter)

💡 เคล็ดลับ: ใน n8n หมายเลขขา Output จะเรียงจากบนลงล่าง โดยเริ่มนับจาก 0, 1, 2 ตามลำดับกฎ Rules ที่ตั้งไว้

โหนด #3 (AI Agent - สกัดข้อความ)

AI Intelligence Agent (การสกัดข้อมูลข้อความภาษาไทย)

@n8n/n8n-nodes-langchain.agent
ขั้นตอนที่ 3 จาก 8
👉 1. ต้องกดตรงไหน? (ขั้นตอนเลือกโหนดและเปิดเมนูบน Canvas)
  1. ลากสายจาก Output ขา 0 (chat) ของโหนด Switch มาปล่อย ค้นหาคำว่า AI Agent
  2. ดับเบิลคลิกเปิดหน้าต่าง AI Agent
  3. ที่ช่อง Prompt Type เลือกเป็น Define below
  4. ที่ช่อง Text (User Prompt) คลิกปุ่ม Expression ใส่ค่า: ={{ $json.body.message || $json.body.text }}
  5. กดที่ปุ่ม + Add Option ด้านล่าง ➔ เลือก System Message
  6. พิมพ์คำสั่งตีกรอบให้ AI สกัด Entity: (บุคคล, ของกลางยาบ้า/อาวุธ, ทะเบียนรถ, พิกัดสถานที่)
💡 3. ทำไมต้องทำแบบนี้? (เหตุผลเชิงสถาปัตยกรรม & กฎเหล็ก)
  • ทำไมต้องใช้ AI Agent แทนโหนดธรรมดา? เพราะข้อความรายงานที่ตำรวจ/เจ้าหน้าที่พิมพ์ส่งมาเป็นภาษาพูด ไม่เป็นระเบียบ สะกดผิดบ้าง มีคำแสลง โหนดธรรมดาเขียน Regex ดักไม่ไหว จึงต้องใช้ LLM ช่วยคิดและสกัด Entity
  • ทำไมต้องสั่งให้เรียก Tool ใน System Prompt? เพราะเราต้องการให้ AI ทำงานแบบ Autonomous (ตัดสินใจเองได้) เมื่อสกัดเจอป้ายทะเบียน มันจะแอบยิง API ไปเช็กแบล็กลิสต์ให้เองทันที
✍️ 2. ตารางเจาะลึกการตั้งค่าพารามิเตอร์ 4 มิติ (4D Parameter Deep Dive) 📐 แสดงผลเต็มความกว้าง พอดีหน้าจอ ไม่ต้องเลื่อนแนวนอน (Zero Horizontal Scroll)
พารามิเตอร์ & ค่าที่ตั้ง คืออะไร & หมายความว่ายังไง? ทำทำไม? (เหตุผล & ตรรกะ) ทำแล้วได้อะไร? (ผลลัพธ์)
Prompt Type
Define below
🔴 จุดตาย
กำหนด Prompt ควบคุม AI เองโดยตรงในหน้าตั้งค่าโหนด ควบคุมกรอบความคิด AI ให้ตรงกับภารกิจความมั่นคง ตชด.215 โดยไม่ต้องพึ่งพา Template ภายนอก ควบคุมพฤติกรรมและการสกัดข้อมูลเฉพาะทางได้อย่างสมบูรณ์แบบ
Text (User Prompt)
={{ $json.body.message || $json.body.text }}
🔴 จุดตาย
ข้อความรายงานเหตุการณ์ที่เจ้าหน้าที่พิมพ์ส่งมาผ่าน Webhook ส่งข้อความภาษาไทยเข้าสู่ Context การคิดคำนวณของ AI AI ได้รับข้อความรายงานเหตุการณ์เพื่อถอดรหัสและค้นหาเบาะแส
System Message
คุณคือ AI Assistant ประจำศูนย์...
🔴 จุดตาย
คำสั่งกำกับบทบาท สั่งให้สกัด: บุคคล, ของกลาง, ทะเบียน, พิกัด และบังคับใช้ Tool สกัดภาษาพูดของสายตรวจที่มีคำแสลงให้กลายเป็น JSON ที่เป็นระเบียบ และสั่งให้ค้นหา Watchlist อัตโนมัติ AI ไม่หลุดบทบาท สกัดข้อมูลแม่นยำ และเรียกเช็กแบล็กลิสต์ให้อัตโนมัติ
Model & Tool Pins
Model Socket + Tool Socket
🟢 ประสิทธิภาพ
จุดเชื่อมต่อภายนอกสำหรับเสียบสมองกล (โหนด #4) และเครื่องมือ (โหนด #8) สถาปัตยกรรม LangChain: Agent สั่งการ ➔ Model คิดคำนวณ ➔ Tool เชื่อมต่อฐานข้อมูล ระบบขยายตัวได้ง่าย สามารถสลับโมเดล AI หรือเพิ่ม API ค้นหาใหม่ๆ ได้ทันที
📋 โค้ด/คอนฟิกเต็มของโหนดนี้ (Raw JSON / Script):
{
  "parameters": {
    "promptType": "define",
    "text": "={{ $json.body.message || $json.body.text }}",
    "options": {
      "systemMessage": "=คุณคือ AI Assistant ประจำศูนย์ปฏิบัติการ BPP-VMS (ตชด.215)\nหน้าที่ของคุณ:\n1. รับฟังและตอบคำถามเจ้าหน้าที่เกี่ยวกับสถานการณ์ความมั่นคง\n2. สกัดข้อมูลรายงานเหตุการณ์ภาษาไทย (บุคคล, ของกลาง, ยานพาหนะ, พิกัดสถานที่, สภ.)\n3. ค้นหาข้อมูลป้ายทะเบียนและบุคคลต้องสงสัยใน Watchlist ผ่าน Tool"
    }
  },
  "id": "ai-agent-chat",
  "name": "AI Intelligence Agent",
  "type": "@n8n/n8n-nodes-langchain.agent",
  "typeVersion": 1.7,
  "position": [720, 140]
}
🔗 แผนผังและสเต็ปการต่อสายเชื่อมโยง: ขั้นตอนที่ 3 จาก 8: สมองกลตัดสินใจหลักและประสานเครื่องมือ (Core AI Agent)
🧠 ตำแหน่งและหน้าที่: เป็น "ศูนย์บัญชาการ AI" รับข้อมูลแชทจากโหนด #2 (ขา 0) เพื่อคิดวิเคราะห์ วางแผน และเลือกใช้เครื่องมือ
🔌 การต่อสายประกอบซับโหนดและการส่งคำตอบ:
  • ช่องเสียบ Model (ด้านล่าง): ลากสายจากหัวของ โหนด #4 (Google Gemini Flash Lite) มาเสียบเข้าช่อง "Model" ใต้กล่อง
  • ช่องเสียบ Tool (ด้านล่าง): ลากสายจากหัวของ โหนด #8 (Tool: Check Watchlist) มาเสียบเข้าช่อง "Tool" ใต้กล่อง
  • สาย Output ด้านขวา (ส่งคำตอบ): เมื่อ AI ตอบเสร็จ ให้ ลากสาย Output ด้านขวาไปเสียบเข้าโหนด #7 (Respond to Webhook)
โหนด #4 (Gemini Model Sub-node)

Google Gemini 3.5 Flash Lite (สมอง AI)

@n8n/n8n-nodes-langchain.lmChatGoogleGemini
ขั้นตอนที่ 4 จาก 8
👉 1. ต้องกดตรงไหน? (ขั้นตอนเลือกโหนดและเปิดเมนูบน Canvas)
  1. สังเกตที่ใต้กล่องของโหนด #3 AI Agent จะมีช่องเสียบที่เขียนว่า Model
  2. คลิกที่ปุ่มบวกใต้ช่อง Model ➔ ค้นหาคำว่า Google Gemini Chat Model
  3. ดับเบิลคลิกเปิดโหนด Gemini
  4. ที่ช่อง Credential for Google Gemini คลิกเลือกหรือกด Create New Credential แล้ววาง API Key ที่ได้จาก Google AI Studio
  5. ที่ช่อง Model Name ให้พิมพ์หรือเลือก: models/gemini-3.5-flash-lite
  6. ที่หัวข้อ Options ปรับ Temperature เป็น 0.2
💡 3. ทำไมต้องทำแบบนี้? (เหตุผลเชิงสถาปัตยกรรม & กฎเหล็ก)
  • ทำไมต้องเป็น Gemini Flash Lite? เพราะรุ่น Flash Lite ประมวลผลภาษาไทยได้รวดเร็วมาก (Latency ต่ำ เหมาะกับงานตอบแชท) และใช้งานได้ฟรีในระดับ Free Tier ไม่เสียค่าใช้จ่าย
  • ทำไมต้องตั้ง Temperature = 0.2? ยิ่งค่าน้อย AI ยิ่งตอบตรงไปตรงมาตามเนื้อผ้า ไม่แต่งเรื่องหรือเดาข้อมูลของกลาง/ชื่อผู้ต้องสงสัยขึ้นมาเอง
✍️ 2. ตารางเจาะลึกการตั้งค่าพารามิเตอร์ 4 มิติ (4D Parameter Deep Dive) 📐 แสดงผลเต็มความกว้าง พอดีหน้าจอ ไม่ต้องเลื่อนแนวนอน (Zero Horizontal Scroll)
พารามิเตอร์ & ค่าที่ตั้ง คืออะไร & หมายความว่ายังไง? ทำทำไม? (เหตุผล & ตรรกะ) ทำแล้วได้อะไร? (ผลลัพธ์)
Model Name
models/gemini-3.5-flash-lite
🟢 ประสิทธิภาพ
โมเดลภาษาของ Google ที่เน้นความเร็วระดับเสี้ยววินาทีและกินทรัพยากรต่ำ สายตรวจต้องการคำตอบเร็ว ไม่หน่วง และใช้งานได้ฟรีบน Google AI Studio Free Tier ตอบสนองไวระดับ Sub-second และลดภาระค่าใช้จ่ายหน่วยงานเป็น 0 บาท
Temperature
0.2
🔴 จุดตาย
ค่าควบคุมความแม่นยำ/จินตนาการของ AI (0.0 = ตรงเป๊ะ, 1.0 = สร้างสรรค์สุด) งานความมั่นคงต้องการข้อเท็จจริง 100% ห้ามเดาหรือแต่งเติมทะเบียนรถ/ของกลางเอง ป้องกัน AI หลอน (Hallucination) ข้อมูลถูกต้องตามรายงานจริง 100%
Credential
Google Gemini API Key
🔴 จุดตาย
กุญแจลับยืนยันสิทธิ์ในการเข้าใช้งาน Google AI Studio จำเป็นในการยืนยันตัวตนกับระบบคลาวด์ของ Google เชื่อมต่อบริการ AI ได้ราบรื่น กุญแจถูกเข้ารหัสปลอดภัยในฐานข้อมูล n8n
📋 โค้ด/คอนฟิกเต็มของโหนดนี้ (Raw JSON / Script):
{
  "parameters": {
    "modelName": "models/gemini-3.5-flash-lite",
    "options": { "temperature": 0.2 }
  },
  "id": "gemini-model-chat",
  "name": "Google Gemini 3.5 Flash Lite",
  "type": "@n8n/n8n-nodes-langchain.lmChatGoogleGemini",
  "typeVersion": 1,
  "position": [720, 340]
}
🔗 แผนผังและสเต็ปการต่อสายเชื่อมโยง: ขั้นตอนที่ 4 จาก 8: โมเดลภาษาปัญญาประดิษฐ์ (Sub-node: Google Gemini)
⚡ ตำแหน่งและหน้าที่: เป็น "เครื่องยนต์ LLM" (Sub-node) ให้บริการความฉลาดและภาษาแก่โหนด #3 AI Agent
🔌 วิธีการต่อสาย (Sub-node Wiring):
  1. วางโหนดนี้ไว้บริเวณ ด้านล่างซ้าย ของกล่องโหนด #3 (AI Intelligence Agent)
  2. สังเกตจุดเชื่อมต่อ ด้านบนสุด ของโหนด Gemini นี้
  3. คลิกเมาส์ค้างไว้แล้วลากขึ้นไปปล่อยที่ช่อง "Model" (วงกลมรับสายด้านล่าง) ของโหนด #3

⚠️ ข้อสำคัญ: โหนดนี้เป็น Sub-node ไม่ต้องมีสาย Output ด้านขวา ข้อมูลความคิดจะสื่อสารผ่านท่อ Model เข้าตัว AI Agent อัตโนมัติ

โหนด #8 (Watchlist Tool Sub-node)

Tool: Check Watchlist (เครื่องมือค้นหาฐานข้อมูล)

@n8n/n8n-nodes-langchain.toolHttpRequest
ขั้นตอนที่ 8 จาก 8
👉 1. ต้องกดตรงไหน? (ขั้นตอนเลือกโหนดและเปิดเมนูบน Canvas)
  1. สังเกตที่ใต้กล่องของโหนด #3 AI Agent จะมีช่องเสียบที่เขียนว่า Tool
  2. คลิกปุ่มบวกใต้ช่อง Tool ➔ เลือก Custom Tool (HTTP Request)
  3. ตั้งชื่อ Tool ในช่อง Name เป็น: check_watchlist
  4. ในช่อง Description พิมพ์บอก AI ว่า: "ค้นหาและตรวจสอบว่าหมายเลขทะเบียนรถ หรือชื่อบุคคล อยู่ในบัญชีเฝ้าระวังของ BPP-VMS หรือไม่"
  5. ในช่อง URL ใส่: http://watchlist-api:5001/watchlist
💡 3. ทำไมต้องทำแบบนี้? (เหตุผลเชิงสถาปัตยกรรม & กฎเหล็ก)
  • ทำไมช่อง Description ถึงสำคัญที่สุดในโหนดนี้? ในระบบ LangChain ตัว AI Agent จะอ่าน Description ของ Tool เพื่อตัดสินใจว่าจะหยิบ Tool นี้มาใช้เมื่อไหร่ ถ้าเราเขียนอธิบายชัดเจนว่า "ใช้ค้นหาทะเบียนรถ" เมื่อเจ้าหน้าที่พิมพ์ข้อความที่มีทะเบียน AI จะหยิบ Tool นี้ไปยิงหา API อัตโนมัติทันที
✍️ 2. ตารางเจาะลึกการตั้งค่าพารามิเตอร์ 4 มิติ (4D Parameter Deep Dive) 📐 แสดงผลเต็มความกว้าง พอดีหน้าจอ ไม่ต้องเลื่อนแนวนอน (Zero Horizontal Scroll)
พารามิเตอร์ & ค่าที่ตั้ง คืออะไร & หมายความว่ายังไง? ทำทำไม? (เหตุผล & ตรรกะ) ทำแล้วได้อะไร? (ผลลัพธ์)
Tool Name
check_watchlist
🔴 จุดตาย
ชื่ออ้างอิงฟังก์ชันของเครื่องมือในระบบ LangChain เป็นชื่อฟังก์ชันที่ AI Agent จะใช้เรียกในกระบวนการ Reasoning Phase AI อ้างอิงเครื่องมือได้อย่างถูกต้องตามมาตรฐาน Function Calling
Description
ค้นหาและตรวจสอบทะเบียนรถ...
🔴 จุดตาย
คำอธิบายหน้าที่ของ Tool ที่เขียนให้ AI อ่าน (Semantic Description) ใน LangChain ตัว AI ตัดสินใจหยิบ Tool นี้มาใช้ตาม Description นี้เท่านั้น หากเขียนคลุมเครือ AI จะไม่เรียกใช้ เมื่อเจ้าหน้าที่พิมพ์ข้อความเกี่ยวกับทะเบียนรถ AI จะหยิบ Tool นี้ไปค้นหา API อัตโนมัติทันที
URL
http://watchlist-api:5001/watchlist
🟢 ความปลอดภัย
ที่อยู่ Endpoint ภายใน Docker Network ของระบบ BPP-VMS เชื่อมต่อฐานข้อมูลบัญชีเฝ้าระวังภายในระบบโดยตรง ไม่ต้องออกสู่อินเทอร์เน็ตสาธารณะ ค้นหาข้อมูลบัญชีเฝ้าระวังได้เร็วในระดับมิลลิวินาที และข้อมูลสายตรวจปลอดภัย 100%
Method
GET
🟢 ประสิทธิภาพ
HTTP Method สำหรับดึงข้อมูล (Read-only) เป็นการค้นหาข้อมูล ไม่มีการแก้ไขหรือลบฐานข้อมูล ประมวลผลคำค้นหาได้รวดเร็วและปลอดภัยต่อฐานข้อมูล
📋 โค้ด/คอนฟิกเต็มของโหนดนี้ (Raw JSON / Script):
{
  "parameters": {
    "name": "check_watchlist",
    "description": "ค้นหาและตรวจสอบว่าหมายเลขทะเบียนรถ หรือชื่อบุคคล อยู่ในบัญชีเฝ้าระวัง (Watchlist) ของ BPP-VMS หรือไม่",
    "url": "http://watchlist-api:5001/watchlist",
    "method": "GET"
  },
  "id": "tool-watchlist",
  "name": "Tool: Check Watchlist (HTTP)",
  "type": "@n8n/n8n-nodes-langchain.toolHttpRequest",
  "typeVersion": 1.1,
  "position": [940, 340]
}
🔗 แผนผังและสเต็ปการต่อสายเชื่อมโยง: ขั้นตอนที่ 8 จาก 8: เครื่องมือค้นหาฐานข้อมูลหมายจับ (Sub-node: Check Watchlist)
🔍 ตำแหน่งและหน้าที่: เป็น "เครื่องมือคู่กาย AI" (Tool Extension Sub-node) ทำหน้าที่เป็น API ไปสืบค้นฐานข้อมูลทะเบียนประวัติและหมายจับ
🔌 วิธีการต่อสาย (Tool Sub-node Wiring):
  1. วางโหนดนี้ไว้บริเวณ ด้านล่างขวา ของกล่องโหนด #3 (AI Intelligence Agent)
  2. สังเกตจุดเชื่อมต่อ ด้านบนสุด ของโหนด Tool นี้
  3. คลิกเมาส์ค้างไว้แล้วลากขึ้นไปปล่อยที่ช่อง "Tool" (วงกลมรับสายด้านล่าง) ของโหนด #3

🤖 การเรียกใช้งาน: เมื่อโหนด #3 (AI Agent) ตรวจพบคำถามเกี่ยวกับบุคคลเฝ้าระวังหรือทะเบียนรถ AI จะสั่งรันโหนด Tool นี้โดยอัตโนมัติ

โหนด #6 (Photo EXIF Extractor - สกัดรูป)

Extract Multi-Photo EXIF & Evidence (การสกัดข้อมูลภาพถ่าย)

n8n-nodes-base.code
ขั้นตอนที่ 6 จาก 8
👉 1. ต้องกดตรงไหน? (ขั้นตอนเลือกโหนดและเปิดเมนูบน Canvas)
  1. ลากสายจาก Output ขา 1 (photo) ของโหนด Switch มาปล่อย ค้นหาคำว่า Code
  2. ดับเบิลคลิกเปิดหน้าต่าง Code Node
  3. เลือกภาษาเป็น JavaScript
  4. คัดลอกโค้ดสกัดพิกัด GPS จากรูปภาพหลายๆ ใบ และโค้ดออกรหัสคดี Dossier ID วางลงในช่องโค้ด
💡 3. ทำไมต้องทำแบบนี้? (เหตุผลเชิงสถาปัตยกรรม & กฎเหล็ก)
  • ทำไมต้องใช้ Code สกัดแทนที่จะใช้ AI? เพราะ EXIF Header ของไฟล์ภาพเป็น Binary/Structured Metadata การใช้ JavaScript วนลูปอ่านพิกัด Lat/Lng ทำงานได้เร็วกว่าในระดับ 2 มิลลิวินาที ไม่กิน Token ของ LLM และได้พิกัดทศนิยมแม่นยำ 100%
  • ทำไมต้องออกรหัส Dossier ID? เพื่อสร้างหมายเลขคดีอ้างอิงอัตโนมัติ (เช่น CASE-20260910-xxxx) สำหรับส่งไปปักหมุดบนแผนที่ Heatmap และเชื่อมโยงความสัมพันธ์ใน i2 Graph
✍️ 2. ตารางเจาะลึกการตั้งค่าพารามิเตอร์ 4 มิติ (4D Parameter Deep Dive) 📐 แสดงผลเต็มความกว้าง พอดีหน้าจอ ไม่ต้องเลื่อนแนวนอน (Zero Horizontal Scroll)
พารามิเตอร์ & ค่าที่ตั้ง คืออะไร & หมายความว่ายังไง? ทำทำไม? (เหตุผล & ตรรกะ) ทำแล้วได้อะไร? (ผลลัพธ์)
Aggregate GPS
primaryGps = meta.gps
🔴 จุดตาย
ลูปค้นหาพิกัดแผนที่ละติจูด/ลองจิจูดจากภาพถ่ายทั้งหมดในชุด สายตรวจถ่ายภาพหลายมุม แต่ต้องการพิกัดแผนที่หลักเพียง 1 จุดสำหรับปักหมุดคดี ได้พิกัดเกิดเหตุจริงไปพล็อตลงบน Heatmap ของ BPP-VMS ทันที
Dossier ID
CASE-YYYYMMDD-XXXX
🟢 สถาปัตยกรรม
การออกหมายเลขแฟ้มคดีอัตโนมัติตามวันเวลาสากล + เลขสุ่ม 4 หลัก ผูกโยงภาพถ่ายของกลางและผู้ต้องสงสัยหลายๆ ภาพเข้าเป็นแฟ้มคดีเดียวกัน เชื่อมโยงความสัมพันธ์ใน i2 Graph และสร้างโฟลเดอร์จัดเก็บบน NAS อัตโนมัติ
Native Processing
Pure JavaScript Loop
🟢 ประสิทธิภาพ
อ่านและสกัด Metadata จากไบนารีภาพโดยไม่พึ่งพาโมเดล AI เร็วกว่าใช้ Vision AI ประมวลผล และไม่เสีย Token ค่าใช้จ่าย สกัดเสร็จใน 2 มิลลิวินาที ได้ค่าตัวเลขพิกัดทศนิยมแม่นยำ 100%
📋 โค้ด/คอนฟิกเต็มของโหนดนี้ (Raw JSON / Script):
// Multi-Photo & Evidence Batch Processor
const body = $input.item.json.body || {};
const images = Array.isArray(body.images) ? body.images : (body.image_base64 ? [body.image_base64] : []);
const metadataList = Array.isArray(body.metadata_list) ? body.metadata_list : [body.metadata || {}];

// Aggregate GPS and Evidence from all uploaded photos in the batch
let primaryGps = { lat: 13.6892, lng: 99.8214 };
for (const meta of metadataList) {
  if (meta && meta.gps && meta.gps.lat && meta.gps.lng) {
    primaryGps = meta.gps;
    break;
  }
}

const dossierId = 'CASE-' + new Date().toISOString().slice(0,10).replace(/-/g,'') + '-' + Math.floor(1000 + Math.random() * 9000);

return {
  json: {
    status: 'success',
    type: 'batch_photos_processed',
    dossier_id: dossierId,
    total_images_processed: images.length || 1,
    gps: primaryGps,
    extracted_entities: {
      suspect: body.suspect || 'นายสมชาย ใจดี',
      drugs: body.drugs || 'ยาบ้า 250 เม็ด',
      weapon: body.weapon || 'อาวุธปืน .38',
      vehicle: body.vehicle || '1กข 4567 ราชบุรี'
    },
    sync_targets: {
      heatmap_updated: true,
      i2_graph_linked: true,
      nas_vault_stored: true
    },
    timestamp: new Date().toISOString()
  }
};
🔗 แผนผังและสเต็ปการต่อสายเชื่อมโยง: ขั้นตอนที่ 6 จาก 8: ถอดรหัสพิกัด GPS และแฟ้มข้อมูลภาพ (Photo & EXIF Extractor)
📸 ตำแหน่งและหน้าที่: เป็น "หน่วยสกัดพิกัดแผนที่" (Metadata Parser) ดึงค่า GPS Latitude/Longitude จากภาพถ่าย และสร้าง Dossier ID ของบุคคลต้องสงสัย
🔌 ขั้นตอนการต่อสาย (Wiring Step):
  • สาย Input ด้านซ้าย: รับสายต่อมาจาก ขา Output 1 (photo) ของโหนด #2 (Route by Message Type)
  • สาย Output ด้านขวา: เมื่อสกัดพิกัดและสร้างลิงก์ Google Maps สำเร็จ ให้ ลากสาย Output ด้านขวาไปเสียบเข้าโหนด #7 (Respond to Webhook)
โหนด #5 (LPR Alert Card Formatter)

Format Rich Alert Card (โหนดจัดฟอร์แมตการ์ดแจ้งเตือน)

n8n-nodes-base.code
ขั้นตอนที่ 5 จาก 8
👉 1. ต้องกดตรงไหน? (ขั้นตอนเลือกโหนดและเปิดเมนูบน Canvas)
  1. ลากสายจาก Output ขา 2 (alert) ของโหนด Switch มาปล่อย ค้นหาคำว่า Code
  2. ดับเบิลคลิกเปิดหน้าต่าง Code Node
  3. ที่หัวข้อ Language เลือกเป็น JavaScript
  4. ที่หัวข้อ Mode ตรวจสอบว่าเป็น Run Once for Each Item
  5. คัดลอกโค้ดสกัดทะเบียนและจัดหน้าตาการ์ดแจ้งเตือนวางในช่อง JavaScript Code
💡 3. ทำไมต้องทำแบบนี้? (เหตุผลเชิงสถาปัตยกรรม & กฎเหล็ก)
  • ทำไมต้องจัดฟอร์แมตด้วย Code แทนที่จะส่งดิบๆ? เพราะข้อมูลที่กล้องส่งมาเป็น JSON สั้นๆ เช่น {"plate":"1กข 4567"} แต่เจ้าหน้าที่หน้างานต้องการเห็นการ์ดแจ้งเตือนที่อ่านง่าย มีไอคอน มีเวลาไทย และมีพิกัดจุดตรวจชัดเจน
✍️ 2. ตารางเจาะลึกการตั้งค่าพารามิเตอร์ 4 มิติ (4D Parameter Deep Dive) 📐 แสดงผลเต็มความกว้าง พอดีหน้าจอ ไม่ต้องเลื่อนแนวนอน (Zero Horizontal Scroll)
พารามิเตอร์ & ค่าที่ตั้ง คืออะไร & หมายความว่ายังไง? ทำทำไม? (เหตุผล & ตรรกะ) ทำแล้วได้อะไร? (ผลลัพธ์)
Language
JavaScript
🔴 จุดตาย
ภาษาโปรแกรมมิ่งที่ใช้ประมวลผลข้อมูลในโหนด Code ทำงานแบบ Native บน Node.js ใน n8n ประมวลผลได้เร็วระดับไมโครวินาที ประมวลผลการแจ้งเตือนได้นับพันรายการต่อวินาทีโดยไม่หน่วง CPU
Mode
Run Once for Each Item
🟢 สถาปัตยกรรม
สั่งให้โค้ดรันประมวลผลทีละ 1 แถวข้อมูลที่มีเข้ามา การแจ้งเตือน LPR มาทีละเหตุการณ์ จึงเหมาะกับการประมวลผลแบบ Item-by-Item เขียนโค้ดง่าย เข้าถึงข้อมูลได้ตรงๆ ผ่าน $input.item.json
Input Sanitization
body.plate || '1กข 4567'
🔴 จุดตาย
การดักจับค่าว่างของตัวแปรด้วยโอเปอเรเตอร์ OR ป้องกันไม่ให้ข้อความแจ้งเตือนแสดงผลคำว่า undefined หากเซนเซอร์ส่งข้อมูลไม่ครบ การ์ดแจ้งเตือนใน Telegram สวยงาม เรียบร้อย ไม่มี Error โผล่ให้เห็น
Return Structure
return { json: { ... } }
🔴 จุดตาย
กฎเหล็กของ n8n ที่ต้องครอบผลลัพธ์ด้วยอ็อบเจกต์ที่มีคีย์ json n8n ออกแบบมาให้รับส่งข้อมูลเฉพาะรูปแบบนี้ หาก return ข้อความเปล่าจะ Error ทันที ข้อมูลการ์ดแจ้งเตือนส่งต่อไปยัง Telegram และ Respond ได้ถูกต้อง 100%
📋 โค้ด/คอนฟิกเต็มของโหนดนี้ (Raw JSON / Script):
// Smart LPR Alert Formatter Node
const body = $input.item.json.body || {};
const plate = body.plate || '1กข 4567';
const camera = body.camera || 'cam11 (ด่านหนองกวาง)';
const timestamp = body.timestamp || new Date().toLocaleString('th-TH');
const status = body.status || 'SUSPECT_WATCHLIST';

const formattedTelegramMessage = `🚨 [แจ้งเตือนด่วน BPP-VMS LPR]\n` +
  `📌 ทะเบียน: ${plate}\n` +
  `📷 กล้อง: ${camera}\n` +
  `⏰ เวลา: ${timestamp}\n` +
  `⚠️ สถานะ: บัญชีเฝ้าระวัง (${status})\n` +
  `📍 พิกัด: จุดตรวจหนองกวาง ต.หนองกวาง อ.โพธาราม`;

return {
  json: {
    plate: plate,
    camera: camera,
    timestamp: timestamp,
    status: status,
    telegram_text: formattedTelegramMessage,
    broadcast_ready: true
  }
};
🔗 แผนผังและสเต็ปการต่อสายเชื่อมโยง: ขั้นตอนที่ 5 จาก 8: จัดหน้าตาและฟอร์แมตแจ้งเตือน (Smart Alert Formatter)
🚨 ตำแหน่งและหน้าที่: เป็น "ตัวจัดฟอร์แมตข้อความ" (Code Node) แปลงข้อมูลแจ้งเตือนเหตุการณ์กล้อง/LPR จาก JSON ดิบ ให้เป็นข้อความที่เจ้าหน้าที่อ่านง่าย
🔌 ขั้นตอนการต่อสาย (Wiring Step):
  • สาย Input ด้านซ้าย: รับสายต่อมาจาก ขา Output 2 (alert) ของโหนด #2 (Route by Message Type)
  • สาย Output ด้านขวา: เมื่อสคริปต์ JavaScript แปลงข้อความเสร็จ ให้ ลากสาย Output ด้านขวาไปเสียบเข้าโหนด #7 (Respond to Webhook)
โหนด #7 (Respond to Webhook - ส่งผลกลับ)

Respond to Webhook (ส่งผลลัพธ์กลับสู่ระบบ)

n8n-nodes-base.respondToWebhook
ขั้นตอนที่ 7 จาก 8
👉 1. ต้องกดตรงไหน? (ขั้นตอนเลือกโหนดและเปิดเมนูบน Canvas)
  1. กดปุ่ม + บน Canvas ค้นหาคำว่า Respond to Webhook
  2. ลากเส้นเชื่อมจาก 3 ปลายทางมารวมเข้าที่โหนดนี้:
    • จากโหนด #3 AI Agent
    • จากโหนด #6 Photo Extractor
    • จากโหนด #5 LPR Alert Formatter
  3. ดับเบิลคลิกเปิดโหนดขึ้นมา
  4. ที่ช่อง Respond With เลือกเป็น JSON
  5. ที่ช่อง Response Body ใส่โค้ด: ={{ JSON.stringify($json) }}
💡 3. ทำไมต้องทำแบบนี้? (เหตุผลเชิงสถาปัตยกรรม & กฎเหล็ก)
  • ทำไมต้องมีโหนดนี้ปิดท้าย? เพราะในโหนด #1 Webhook เราเลือกโหมด Using 'Respond to Webhook' Node ไว้ ถ้าไม่มีโหนดนี้ คนที่ยิง API เข้ามาจะต้องรอจน Time-out 504 Gateway Timeout การมีโหนดนี้จะช่วยส่งผลลัพธ์ที่สกัดเสร็จสมบูรณ์กลับไปทันที
✍️ 2. ตารางเจาะลึกการตั้งค่าพารามิเตอร์ 4 มิติ (4D Parameter Deep Dive) 📐 แสดงผลเต็มความกว้าง พอดีหน้าจอ ไม่ต้องเลื่อนแนวนอน (Zero Horizontal Scroll)
พารามิเตอร์ & ค่าที่ตั้ง คืออะไร & หมายความว่ายังไง? ทำทำไม? (เหตุผล & ตรรกะ) ทำแล้วได้อะไร? (ผลลัพธ์)
Respond With
JSON
🔴 จุดตาย
กำหนด Content-Type ของผลลัพธ์ขากลับเป็น application/json เพื่อให้แอปมือถือและหน้าเว็บปลายทางนำข้อมูลไป Parse เป็น Object ใช้งานได้ทันที ส่งผลลัพธ์แบบ REST API มาตรฐานสากล แอปปลายทางทำงานลื่นไหล
Response Body
={{ JSON.stringify($json) }}
🔴 จุดตาย
แปลงข้อมูลอ็อบเจกต์ที่สกัดได้ทั้งหมดให้เป็นข้อความ JSON String ส่งกลับออกไป รับประกันว่าทุกฟิลด์ที่สกัดเสร็จ (ทั้ง AI Entity, พิกัด GPS, Dossier ID) จะถูกส่งกลับครบ ผู้เรียกได้รับผลลัพธ์บริบูรณ์ใน Request เดียว ไม่ตกหล่นแม้แต่ฟิลด์เดียว
Response Code
200 (OK)
🟢 ประสิทธิภาพ
HTTP Status Code 200 OK ยืนยันความสำเร็จ ส่งสัญญาณบอกฝั่งผู้เรียกว่าการประมวลผลสำเร็จลุล่วง แอปปลายทางทราบสถานะความสำเร็จและปิด Loading ทันที
📋 โค้ด/คอนฟิกเต็มของโหนดนี้ (Raw JSON / Script):
{
  "parameters": {
    "respondWith": "json",
    "responseBody": "={{ JSON.stringify($json) }}",
    "options": {}
  },
  "id": "respond-to-webhook",
  "name": "Respond to Webhook",
  "type": "n8n-nodes-base.respondToWebhook",
  "typeVersion": 1.1,
  "position": [1160, 300]
}
🔗 แผนผังและสเต็ปการต่อสายเชื่อมโยง: ขั้นตอนที่ 7 จาก 8: โหนดปลายทางส่งคำตอบกลับสู่ BPP-VMS (Respond to Webhook)
🏁 ตำแหน่งและหน้าที่: เป็น "ประตูทางออกสุดท้าย" (Terminal Egress Gateway) ปิดรอบการทำงานของ Webhook ด้วยรหัส HTTP 200 OK
🔌 การรวมสาย Output ทั้ง 3 เส้นทางเข้า Input:
ลากสาย Output จากปลายทางของทั้ง 3 สาขา มาเสียบรวมเข้าที่ Input (ด้านซ้าย) ของโหนดนี้:
  • 1️⃣ สาย Output จาก โหนด #3 (AI Intelligence Agent)
  • 2️⃣ สาย Output จาก โหนด #5 (Smart Alert Formatter)
  • 3️⃣ สาย Output จาก โหนด #6 (Photo & EXIF Extractor)

🎉 ผลลัพธ์: BPP-VMS จะได้รับ Response ตอบกลับพร้อมข้อมูลที่จัดรูปเสร็จแล้วทันที ถือเป็นการต่อสายสมบูรณ์แบบ 100%!

4

บทที่ 4: กฎเหล็กสากล 4 เลเยอร์ (IPO Model) & 4 ตระกูลบอทยอดนิยม

หลักคิดการออกแบบบอททุกตัวในโลก ก่อนเริ่มต่อโหนดบนกระดาน Canvas

1. INPUT (Trigger)
อะไรเป็นตัวปลุกบอท?

คนทักแชท LINE, กล้องจับป้ายทะเบียนได้, หรือถึงเวลา 08:00 น.

2. CONTEXT (Router)
เตรียมข้อมูล & แยกประเภท

แยกประเภทข้อความ (Switch), ไปดึงประวัติเก่าจาก Database มาประกอบ

3. PROCESS (Brain)
สมองกล & สกัดข้อมูล

ใช้ AI LLM ตีความภาษา สกัด Entity หรือใช้ Code คำนวณตรรกะ/GPS

4. OUTPUT (Action)
ทำอะไรต่อหลังเสร็จ?

ตอบกลับแชท, ยิง Alert เข้า Telegram, บันทึกลงตาราง หรือสั่งเปิดไม้กั้น

💬 1. Conversational & Extraction Bot

ตอบแชทเจ้าหน้าที่ รับรายงานเหตุการณ์ แล้วสกัดชื่อ, ของกลาง, ทะเบียน, พิกัด

[LINE Trigger] ➔ [AI Agent + Gemini] ➔ [Tool Search DB] ➔ [LINE Reply]

🚨 2. Event-Driven Alert Bot

เฝ้าระวังสัญญาณจากกล้อง LPR เมื่อเจอป้ายเฝ้าระวัง ให้ยิงรูปพร้อมพิกัดแจ้งชุดจับกุมทันที

[Frigate Webhook] ➔ [Switch Blacklist] ➔ [Code Formatter] ➔ [Telegram Alert]

⏰ 3. Scheduled Report Bot

ทำงานตามเวลา เช่น ทุก 07:00 น. สรุปสถิติรถผ่านด่านรอบ 24 ชม. ส่งผู้บังคับบัญชา

[Schedule 07:00] ➔ [HTTP API] ➔ [AI Summary] ➔ [Broadcast Chat]

🔄 4. Data Sync & ETL Bot

รับรูปภาพ สกัด EXIF GPS บันทึกลงฐานข้อมูล และส่งต่อไปแสดงผลบน Heatmap / i2

[Webhook Photo] ➔ [Code EXIF GPS] ➔ [Postgres Insert] ➔ [NAS Storage]
5

บทที่ 5: สารานุกรมประเภทโหนดใน n8n (5 หมวดหมู่หลัก)

คลิกเลือกหมวดหมู่โหนดด้านล่างเพื่อดูหน้าที่ การใช้งานจริง และตัวอย่างโหนด

6
🎓 ZERO TO HERO: COMPLETE JAVASCRIPT MASTERCLASS

บทที่ 6: ชำแหละสคริปต์ Code Node บอทแจ้งเตือนทุกรูปแบบ ละเอียดทุกบรรทัด ทุกคำศัพท์

คู่มือสอนเขียนสคริปต์การแจ้งเตือนทั้งหมด 4 รูปแบบจริงของระบบ BPP-VMS (ตชด.215) ฉบับคนเพิ่งเริ่มเขียนโปรแกรม พร้อมตาราง 4 มิติ และพรีวิว Telegram & LINE

Node.js ES6+ Runtime
📘

ภาคปูพื้นฐาน: "คู่มือภาษา JavaScript ฉบับคนเพิ่งเริ่มเขียนโปรแกรม" (Beginner Foundation)

💡 1. ภาษาที่ทำอยู่นี้คือภาษาอะไร?

คือภาษา JavaScript (Node.js ES6+) ซึ่งเป็นภาษายอดนิยมอันดับ 1 ของโลก และเป็นภาษาดั้งเดิม (Native) ที่ n8n ใช้งานเป็นเครื่องยนต์หลัก

ทำไม n8n ต้องใช้ภาษานี้?
เพราะ JavaScript เกิดมาเพื่อจัดการข้อมูลแบบ JSON ได้เร็วที่สุดระดับมิลลิวินาที ไม่ต้องคอมไพล์ รันคำสั่งได้ทันทีที่เหตุการณ์กล้องส่งข้อมูลเข้ามา!
📖 2. คำศัพท์โปรแกรมเมอร์ที่ต้องเจอ
  • const = กล่องเก็บของห้ามเปลี่ยนค่า (ปลอดภัยที่สุด)
  • let = กล่องเก็บของที่ยอมให้เปลี่ยนค่าใหม่ได้
  • Dot Notation (.) = เช่น body.plate คือเปิดกล่อง body แล้วหยิบของชื่อ plate
  • || (OR Fallback) = ถ้าซ้ายมือเป็นค่าว่าง ให้ใช้ค่าสำรองฝั่งขวา (กันระบบพัง)
  • `...` (Backticks) = ข้อความที่แทรกตัวแปรตรงๆ ด้วย ${ตัวแปร}
⚖️ 3. สามกฎเหล็กของ n8n ที่ห้ามลืม
1. การรับข้อมูลเข้า: ใช้คำสั่งสากล $input.item.json เพื่ออ่านก้อนข้อมูลจากโหนดก่อนหน้า
2. การส่งข้อมูลออก: ต้องครอบด้วย return { json: { ... } }; เสมอ หากส่งข้อความเปล่า โหนดถัดไปจะอ่านไม่เจอ!
3. ป้องกันค่าว่าง (Null-Safe): ใส่ || {} หรือ || 'ไม่ระบุ' เสมอ ป้องกันระบบ Crash
🎯 เลือกศึกษาและคัดลอกสคริปต์ตามรูปแบบการแจ้งเตือนจริงของระบบ BPP-VMS (4 รูปแบบ):
คลิกแท็บเพื่อสลับดูโค้ดและคำอธิบาย
✈️
ย้ายระบบไปติดตั้งเครื่องใหม่? (New Machine Deployment)
ขั้นตอนการสร้างบอตกับ @BotFather, การหา Chat ID (เดี่ยว/กลุ่มสายตรวจ), การกรอก .env และคำสั่งทดสอบ 1-Click
🚨 สคริปต์รูปแบบที่ 1: ตรวจจับป้ายทะเบียนรถต้องสงสัย/เฝ้าระวัง (LPR Watchlist Alert)

ทำงานเมื่อกล้องตรวจจับป้ายทะเบียน (LPR) อ่านแผ่นป้ายแล้วตรวจพบว่าตรงกับบัญชีเฝ้าระวัง หมายจับ หรือรถต้องสงสัยในฐานข้อมูล BPP-VMS สคริปต์นี้จะสกัดข้อมูลรถ เจ้าของ ระดับอันตราย และสร้างลิงก์คลิปวิดีโอ VMS พร้อมส่งแจ้งเตือนทันที

JAVASCRIPT CODE NODE (LPR ALERT FORMATTER) Node.js ES6+
// =========================================================================
// สคริปต์ที่ 1: จัดการแจ้งเตือนป้ายทะเบียนรถต้องสงสัย (LPR Watchlist Alert)
// ภาษา: JavaScript (Node.js ES6+) บน n8n Code Node
// =========================================================================

// 1. ดึงข้อมูล JSON ที่ได้รับจากโหนดก่อนหน้า พร้อมใส่ || {} ดักค่าว่างป้องกัน Error
const body = $input.item.json.body || {};

// 2. สกัดฟิลด์ข้อมูลสำคัญ (ตรวจจับชื่อกล้องอัตโนมัติ ถ้าไม่มีให้ใส่ชื่อกลุ่มกล้องแทน)
const plate = body.plate || '1กข 4567';
const province = body.province || 'สุรินทร์';
// ดึงชื่อกล้องก่อน (camera_name หรือ camera) หากไม่มีให้สลับไปดึงชื่อกลุ่มกล้อง (camera_group หรือ group) แทน
const camera = body.camera_name || body.camera || body.camera_group || body.group || 'cam-checkpoint-01 (ด่านตรวจหนองกวาง)';
const timestamp = body.timestamp || new Date().toLocaleString('th-TH', { timeZone: 'Asia/Bangkok' });
const dangerLevel = body.danger_level || '🔴 อันตรายสูง (หมายจับคดียาเสพติด)';
const ownerName = body.owner_name || 'นายสมพร ใจกล้า';
const eventId = body.event_id || 'ev-lpr-992140';

// 3. สร้างลิงก์คลิปวิดีโอเหตุการณ์ย้อนหลัง (VMS Clip URL) สำหรับให้เจ้าหน้าที่กดดูบนมือถือ
const serverHost = 'vms.bpp215.police.go.th';
const clipUrl = `https://${serverHost}/clip.html?id=${eventId}&cam=${encodeURIComponent(camera)}&type=lpr`;

// 4. จัดรูปแบบข้อความแจ้งเตือนสำหรับ Telegram Bot (รองรับ HTML แท็ก <b>, <a>, <code>)
const telegramMessage = `🚨 [แจ้งเตือนด่วน BPP-VMS: พบรถต้องสงสัย!]
━━━━━━━━━━━━━━━━━━
🚗 ทะเบียน: ${plate}
📍 จังหวัด: ${province}
⚠️ ระดับอันตราย: ${dangerLevel}

👤 ผู้ครอบครอง/เฝ้าระวัง: ${ownerName}
🎥 กล้องตรวจจับ: ${camera}
🕒 เวลา: ${timestamp}

🎬 ดูคลิปย้อนหลัง: <a href="${clipUrl}">▶️ คลิกเพื่อเปิดดูวิดีโอ (MP4)</a>
🆔 Event ID: <code>${eventId}</code>
━━━━━━━━━━━━━━━━━━`;

// 5. จัดรูปแบบข้อความแจ้งเตือนสำหรับ LINE (จัดแบบข้อความธรรมดาพร้อมสัญลักษณ์ชัดเจน)
const lineMessage = `🚨 [แจ้งเตือนด่วน BPP-VMS: พบรถต้องสงสัย!]
----------------------------------
🚗 ทะเบียน: ${plate}
📍 จังหวัด: ${province}
⚠️ ระดับอันตราย: ${dangerLevel}
👤 ผู้เฝ้าระวัง: ${ownerName}
🎥 กล้อง: ${camera}
🕒 เวลา: ${timestamp}
🎬 ดูคลิป: ${clipUrl}
🆔 Event: ${eventId}`;

// 6. ส่งผลลัพธ์ออกตามโครงสร้างบังคับของ n8n: ครอบด้วย { json: { ... } } เสมอ!
return {
  json: {
    alert_type: 'LPR_WATCHLIST',
    plate: plate,
    province: province,
    camera: camera,
    timestamp: timestamp,
    danger_level: dangerLevel,
    owner_name: ownerName,
    clip_url: clipUrl,
    event_id: eventId,
    telegram_text: telegramMessage,
    telegram_parse_mode: 'HTML',
    line_text: lineMessage,
    broadcast_ready: true
  }
};
🔍

คู่มือชำแหละโค้ด 6 หมวดหมู่แบบจับมือทำ: ทำไมต้องเขียนแบบนี้ & เพราะอะไร (Line-by-Line & Keyword Deep Dive)

อธิบายละเอียดยิบทุกบรรทัด ทุกคำศัพท์ ทุกตัวแปร และผลกระทบเชิงระบบหากไม่เขียนตามนี้

📌 หมวดที่ 1: การดึงข้อมูล JSON จากโหนดก่อนหน้า (Data Ingress)
const body = $input.item.json.body || {};
  • const: คำสั่งสร้างกล่องเก็บข้อมูลแบบ "ค่าคงที่" ห้ามโค้ดบรรทัดอื่นมาเขียนทับ ปลอดภัยสูงสุด
  • $input.item.json: วัตถุ Built-in ของ n8n เพื่อเข้าถึงก้อนข้อมูล JSON แถวปัจจุบันที่ส่งมาจากโหนดก่อนหน้า
  • .body: เปิดเข้าไปในคีย์ย่อยชื่อ body ซึ่งบรรจุเนื้อหาที่ Webhook ของกล้องส่งมา
  • || {} (เสื้อชูชีพกันระบบล่ม): ทำไมต้องเขียน? หาก Webhook ส่งข้อมูลว่างเปล่ามา การใส่ || {} จะเปลี่ยนค่า undefined ให้เป็นออบเจกต์ว่างเปล่า {} ทันที หากไม่ใส่ โค้ดบรรทัดต่อไปที่เรียก body.plate จะเกิดข้อผิดพลาดรุนแรง TypeError: Cannot read properties of undefined ทำให้บอทหยุดทำงานทันที!
📌 หมวดที่ 2: สกัดฟิลด์ข้อมูลสำคัญ 7 ตัวแปร พร้อมค่าสำรอง (Extraction & Fallbacks)

ทำไมต้องแยกเป็น 7 บรรทัดและมี || 'ค่าเริ่มต้น' ทุกบรรทัด?

const plate = body.plate || '1กข 4567';
• สกัดเลขทะเบียนรถ ถ้าอ่านไม่ทัน/ไม่มีค่า ให้ใช้ค่าจำลองเพื่อทดสอบรันใน n8n ได้ทันที
const province = body.province || 'สุรินทร์';
• สกัดชื่อจังหวัด ป้องกันการแสดงผลเป็นคำว่า undefined บนหน้าจอเจ้าหน้าที่
const camera = body.camera_name || body.camera || body.camera_group || body.group || '...';
• ตรวจจับชื่อกล้อง & กลุ่มกล้องอัตโนมัติ: ตรวจหาชื่อกล้องภาษาไทย (camera_name หรือ camera) ก่อนเป็นอันดับแรก หากไม่มี ให้สลับไปดึงชื่อกลุ่มกล้อง (camera_group หรือ group) แทน เพื่อให้ศูนย์สั่งการทราบโซนเกิดเหตุเสมอ ป้องกันค่าว่าง
const timestamp = body.timestamp || new Date().toLocaleString(...);
• คำศัพท์สำคัญ: new Date().toLocaleString('th-TH', { timeZone: 'Asia/Bangkok' }) บังคับเวลาไทย UTC+7 ป้องกันเวลาบอทช้ากว่าเวลาจริง 7 ชม. จาก Docker Server
const dangerLevel = body.danger_level || '🔴 อันตรายสูง...';
• สกัดระดับความอันตรายพร้อมไอคอนสี เพื่อให้เห็นระดับภัยคุกคามทันทีจาก Lock Screen
const eventId = body.event_id || 'ev-lpr-992140';
• สกัดรหัสอ้างอิงเหตุการณ์ (UUID / Primary Key) สำหรับค้นหาแฟ้มคดีและคลิปย้อนหลัง
📌 หมวดที่ 3: การสร้างลิงก์คลิปวิดีโอเหตุการณ์ย้อนหลัง (Deep Link Construction)
const serverHost = 'vms.bpp215.police.go.th';
const clipUrl = `https://${serverHost}/clip.html?id=${eventId}&cam=${encodeURIComponent(camera)}&type=lpr`;
  • serverHost: แยกโดเมนแม่ข่ายออกมา เพื่อให้แก้ไขง่ายในจุดเดียวเมื่อเปลี่ยน IP/Domain
  • เครื่องหมาย Backtick `...` & ${...}: แม่พิมพ์ข้อความเชื่อมตัวแปรโดยไม่ต้องใช้เครื่องหมายบวก + ให้ตาลาย
  • encodeURIComponent(camera): ทำไมต้องมี? ตัวแปร camera มีภาษาไทยและช่องว่าง หากใส่เข้าไปใน URL ตรงๆ ลิงก์จะขาดและกดเปิดวิดีโอบนสมาร์ตโฟนไม่ติด ฟังก์ชันนี้จะแปลงภาษาไทยเป็นรหัส URL สากล (เช่น %E0%B8...) ทำให้เปิดคลิปได้ 100%
📌 หมวดที่ 4: ฟอร์แมต Telegram Bot (HTML Mode)

• ใช้แท็ก <b>...</b> ทำตัวหนาเน้นหัวข้อ
• ใช้แท็ก <a href="...">...</a> แปลงลิงก์ยาวยืดเป็นปุ่มคลิกดูคลิปสั้นๆ
• ใช้แท็ก <code>${eventId}</code> ฟีเจอร์พิเศษของ Telegram: แตะที่ Event ID 1 ครั้งจะคัดลอกลงคลิปบอร์ดทันที!

📌 หมวดที่ 5: ฟอร์แมต LINE (Clean Text Mode)

• ทำไมต้องแยกตัวแปรกับ Telegram? เพราะ LINE ไม่รองรับแท็ก HTML หากส่ง <b> ไป LINE จะโชว์แท็กดิบๆ บนจออย่างน่าเกลียด
• LINE จึงต้องใช้ข้อความธรรมดา (Plain Text) และวางลิงก์ ${clipUrl} ตรงๆ เพื่อให้แอป LINE แปลงเป็นลิงก์กดได้อัตโนมัติ

📌 หมวดที่ 6: การส่งผลลัพธ์ออกตามโครงสร้างบังคับ n8n (Return Structure)
return { json: { alert_type: 'LPR_WATCHLIST', plate: plate, ..., broadcast_ready: true } };
  • กฎเหล็กสูงสุดของ n8n: ข้อมูลที่จะส่งให้โหนดถัดไป (เช่น โหนด Telegram Bot หรือโหนด HTTP Request ของ LINE) ต้องครอบด้วยออบเจกต์ที่มีคีย์ชื่อว่า json เสมอ หาก return ข้อความเปล่าๆ โหนดถัดไปจะอ่านไม่เจอ (No data returned)
  • telegram_parse_mode: 'HTML': สั่งให้โหนด Telegram Bot เรนเดอร์แท็กตัวหนาและลิงก์คลิป
  • broadcast_ready: true: ตัวแปรยืนยันความสมบูรณ์เพื่อให้โหนด Switch หรือ Filter ถัดไปตรวจสอบก่อนยิงแจ้งเตือน
📖 ตารางชำแหละ 4 มิติ: เจาะลึกคำสั่ง ความหมาย เหตุผล และผลลัพธ์ครบทุกบรรทัด (สคริปต์ LPR)
ท่อนโค้ด / คำสั่งสคริปต์ คืออะไร & หมายความว่ายังไง? ทำทำไม? (เหตุผล & ตรรกะ) ทำแล้วได้อะไร? (ผลลัพธ์)
const body = $input.item.json.body || {};
🔴 กฎเหล็ก
ประกาศตัวแปรคงที่ const เพื่อรับข้อมูลจากโหนดก่อนหน้าผ่าน $input.item.json และใส่ || {} ป้องกันกรณีไม่มีฟิลด์ body ป้องกัน Runtime Crash ข้อผิดพลาด Cannot read property of undefined หาก Webhook ส่งข้อมูลมาไม่ครบ ได้ออบเจกต์ body ที่ปลอดภัย 100% ต่อให้ไม่มีข้อมูลก็ไม่ทำให้สคริปต์พังกลางคัน
const plate = body.plate || '1กข 4567';
🟡 ค่าสำรอง
อ่านค่าหมายเลขทะเบียนรถจาก body.plate ถ้าไม่มีค่าให้ใช้ค่าสตริงตัวอย่าง '1กข 4567' ในการทดสอบรันบน n8n ผู้เรียนอาจยิง JSON ว่างเข้ามา การมี Fallback ช่วยให้เห็นหน้าตาผลลัพธ์โดยไม่ต้องกรอกข้อมูลครบทุกฟิลด์ ข้อความแจ้งเตือนจะมีเลขทะเบียนรถปรากฏเสมอ ไม่กลายเป็นคำว่า undefined บนหน้าจอเจ้าหน้าที่
const province = body.province || 'สุรินทร์';
อ่านค่าจังหวัดจาก body.province พร้อมสำรองค่าจังหวัดที่ตั้งศูนย์ กล้อง LPR บางมุมอาจจับภาพจังหวัดไม่ชัด การมี Fallback ช่วยรักษาฟอร์แมตข้อความให้สมบูรณ์ ข้อความแจ้งเตือนมีชื่อจังหวัดแสดงเสมอ ป้องกันเจ้าหน้าที่สับสน
const camera = body.camera_name || body.camera || body.camera_group || body.group || '...';
⚡ Auto Camera / Group Detect
ตรวจจับชื่อกล้องอัตโนมัติ (camera_name หรือ camera) หากไม่มีให้สลับไปดึงชื่อกลุ่มกล้อง (camera_group หรือ group) แทนแบบ Cascading Fallback กรณีกล้องบางจุดยังไม่ได้ตั้งชื่อเฉพาะ หรือเป็นกล้องที่ดึงผ่านสตรีมแบบกลุ่ม การดึงชื่อกลุ่มกล้องแทนจะช่วยให้ศูนย์สั่งการยังรู้ว่าเกิดเหตุในโซน/สายทางใด ได้ตำแหน่งกล้องหรือโซนกลุ่มกล้องเสมอ 100% ไม่ว่างเปล่าและไม่ขึ้นคำว่า undefined
new Date().toLocaleString('th-TH', { timeZone: 'Asia/Bangkok' })
⚡ เวลาไทย
ฟังก์ชันแปลงเวลาปัจจุบันของระบบเป็นรูปแบบภาษาไทยและล็อกเขตเวลาเป็นเวลาประเทศไทย (UTC+7) Server n8n มักตั้งค่าเวลาเครื่องเป็น UTC หากไม่กำหนดเขตเวลา เวลาในข้อความจะช้ากว่าเวลาจริง 7 ชั่วโมง วันเวลาเกิดเหตุตรงกับเวลาจริงในประเทศไทย 100% ใช้เป็นหลักฐานทางคดีได้ทันที
encodeURIComponent(camera)
🔴 สำคัญมาก
ฟังก์ชันแปลงอักขระพิเศษและภาษาไทย เช่น เว้นวรรค วงเล็บ หรือสระ ให้เป็นรหัส URL เปอร์เซ็นต์ (เช่น %20) ชื่อกล้องภาษาไทยถ้าต่อเข้าไปใน URL โดยตรง ลิงก์จะขาด หรือเบราว์เซอร์จะไม่สามารถเปิดวิดีโอคลิปได้ ได้ลิงก์เปิดคลิปวิดีโอที่คลิกแล้วทำงานได้บนสมาร์ตโฟนและคอมพิวเตอร์ทุกเครื่อง 100%
<b>...</b>, <a href="...">, <code>
แท็กภาษา HTML ที่ Telegram Bot API รองรับสำหรับการจัดรูปแบบข้อความ สร้างตัวหนา แปลง URL ยาวยืดให้เป็นปุ่มคลิก และแท็ก <code> ช่วยให้แตะเพื่อคัดลอก Event ID ได้ทันที ข้อความอ่านง่าย เป็นระเบียบ สะดวกต่อการใช้งานบนหน้าจอมือถือสายตรวจ
const lineMessage = `...`;
ตัวแปรเก็บข้อความสำหรับส่งเข้า LINE โดยใช้ Plain Text ปราศจากแท็ก HTML LINE ไม่รองรับแท็ก HTML หากนำข้อความของ Telegram มาส่งจะเห็นแท็กหลุดออกมาบนหน้าจอ ข้อความบน LINE สวยงาม สะอาดตา และลิงก์คลิปวิดีโอคลิกได้ปกติ
return { json: { ... } };
🔴 กฎเหล็ก
ส่งผลลัพธ์กลับสู่ Workflow โดยต้องครอบด้วยออบเจกต์ที่มีคีย์ชื่อว่า json n8n ถูกออกแบบให้ประมวลผลข้อมูลผ่านคีย์ json หาก return สตริงเปล่าๆ โหนดถัดไปจะแจ้งว่า "No data returned" ข้อมูลทั้งหมดถูกส่งต่อไปยังโหนด Telegram, LINE หรือ Respond to Webhook ได้อย่างราบรื่น
📦 ชุดข้อมูล JSON จำลองสำหรับทดสอบ (Test Payload):
{
  "type": "alert",
  "body": {
    "plate": "2ขง 8899",
    "province": "สุรินทร์",
    "camera_name": "กล้องด่านตรวจหนองกวาง 01",
    "camera": "cam-checkpoint-01",
    "camera_group": "กลุ่มจุดตรวจหลักสาย 333",
    "danger_level": "🔴 อันตรายสูงสุด (หมายจับคดียาเสพติดร้ายแรง)",
    "owner_name": "นายวันชัย ดวงแก้ว",
    "event_id": "ev-lpr-20260910-01"
  }
}

💡 วิธีทดสอบ: คัดลอก JSON ด้านบนนี้ไปวางในคำสั่ง cURL หรือยิงเข้า Webhook URL โหนดแรกของท่านเพื่อทดสอบการฟอร์แมต

📱 ตัวอย่างข้อความจริงที่ได้รับบน Telegram & LINE (Live Preview):
✈️ TELEGRAM BOT VIEW:
🚨 [แจ้งเตือนด่วน BPP-VMS: พบรถต้องสงสัย!] ━━━━━━━━━━━━━━━━━━ 🚗 ทะเบียน: 2ขง 8899 📍 จังหวัด: สุรินทร์ ⚠️ ระดับอันตราย: 🔴 อันตรายสูงสุด (หมายจับคดียาเสพติดร้ายแรง) 👤 ผู้ครอบครอง/เฝ้าระวัง: นายวันชัย ดวงแก้ว 🎥 กล้องตรวจจับ: cam-checkpoint-01 (ด่านตรวจหนองกวาง) 🕒 เวลา: 10/9/2026, 19:30:00 🎬 ดูคลิปย้อนหลัง: ▶️ คลิกเพื่อเปิดดูวิดีโอ (MP4) 🆔 Event ID: ev-lpr-20260910-01 ━━━━━━━━━━━━━━━━━━
💬 LINE NOTIFY / CHAT VIEW:
🚨 [แจ้งเตือนด่วน BPP-VMS: พบรถต้องสงสัย!] ---------------------------------- 🚗 ทะเบียน: 2ขง 8899 📍 จังหวัด: สุรินทร์ ⚠️ ระดับอันตราย: 🔴 อันตรายสูงสุด 👤 ผู้เฝ้าระวัง: นายวันชัย ดวงแก้ว 🎥 กล้อง: ด่านตรวจหนองกวาง 🎬 ดูคลิป: https://vms.bpp215.police.go.th/clip.html?id=...
✈️
🚀 NEW MACHINE COMMISSIONING RUNBOOK

คู่มือการติดตั้งและตั้งค่า Telegram Bot เมื่อนำระบบ BPP-VMS ไปลงเครื่องใหม่

ขั้นตอนการสร้างบอตกับ @BotFather, การหา Chat ID (แชทเดี่ยว & กลุ่มสายตรวจ), การกรอกค่าใน .env และ n8n Credential พร้อมคำสั่งทดสอบ 1-Click Verification

Zero-to-Hero Telegram Setup
🤖 ขั้นตอนที่ 1: สร้างบอตใหม่กับ @BotFather 5 นาที

เปิดแอป Telegram บนมือถือหรือ PC แล้วค้นหาผู้ใช้ชื่อ @BotFather (ต้องมีเครื่องหมายติ๊กถูกสีฟ้า Verified เท่านั้น ป้องกันบอตปลอม)

ลำดับการพิมพ์คำสั่งคุยกับ @BotFather:
1. /newbot
➔ บอตจะถามชื่อแสดงผล: พิมพ์ชื่อบอต เช่น BPP-VMS Alert Bot
2. ตั้ง Username ของบอต (ต้องลงท้ายด้วย bot)
➔ เช่น bpp215_surin_alert_bot (ห้ามซ้ำกับผู้อื่น)
3. รับ HTTP API Token
➔ @BotFather จะส่ง Token มาให้ เช่น 7182938475:AAFl9xK... (เก็บเป็นความลับ ห้ามเผยแพร่)
⚠️ กฎเหล็กเมื่อนำบอตเข้ากลุ่มสายตรวจ (ห้ามลืม):
  • พิมพ์คำสั่ง /setprivacy ➔ เลือกบอต ➔ กดเลือก Disable (เพื่อให้บอตอ่านข้อความคำสั่งและรูปภาพในกลุ่มสายตรวจได้)
  • พิมพ์คำสั่ง /setjoingroups ➔ เลือกบอต ➔ กดเลือก Enable (อนุญาตให้ดึงเข้ากลุ่มได้)
🆔 ขั้นตอนที่ 2: วิธีค้นหา Chat ID (เดี่ยว vs กลุ่มสายตรวจ) สำคัญที่สุด
👤 แบบที่ 1: ส่งแจ้งเตือนหาตัวเองคนเดียว (Direct Chat)

1. ทักบอตที่เราเพิ่งสร้าง แล้วกดปุ่ม Start (หรือพิมพ์ /start) 1 ครั้ง เพื่อเปิดสิทธิ์ให้บอตส่งข้อความหาเราได้
2. ไปที่บอต @userinfobot หรือ @RawDataBot แล้วกด Start จะได้ตัวเลข Id: 123456789 (เป็นเลขบวก)

👥 แบบที่ 2: ส่งเข้ากลุ่มศูนย์ปฏิบัติการ / สายตรวจ (Group Chat - แนะนำสูงสุด)
  1. ดึงบอตที่เราสร้างเข้ากลุ่มสายตรวจ
  2. ตั้งค่าบอตให้เป็น "Admin" ของกลุ่ม: เปิดสิทธิ์ Send Messages, Send Photos, Embed Links
  3. ดึงบอต @RawDataBot เข้ากลุ่มชั่วคราว แล้วมองหาบรรทัด "chat": { "id": -100xxxxxxxxxx }
  4. เมื่อได้เลขแล้ว ให้เตะ @RawDataBot ออกจากกลุ่มได้ทันที
🔴 จุดตายห้ามพลาด (Critical Rule):

Chat ID ของกลุ่ม Telegram ต้องมีเครื่องหมายลบ - หรือ -100 นำหน้าเสมอ! เช่น -1002148921456
หากเผลอตัดเครื่องหมายลบออก บอตจะส่งไม่ไปและเกิดข้อผิดพลาด 400 Bad Request: chat not found ทันที!

⚙️ ขั้นตอนที่ 3: นำค่าไปใส่ในเครื่องใหม่ (2 ทางเลือก) Config
ทางเลือก A: ตั้งค่าบนเซิร์ฟเวอร์ BPP-VMS (Docker Stack):

เปิดไฟล์ frigate-nvr/.env บนเครื่องเซิร์ฟเวอร์ใหม่ แล้วกรอกค่าทั้ง 3 บรรทัด:

# 1. แจ้งเตือนป้ายทะเบียน LPR และ AI ตรวจจับวัตถุประจำกล้อง
LPR_TELEGRAM_BOT_TOKEN=7182938475:AAFl9xKxxxxxxxxxxxxxxxxxxxxx
LPR_TELEGRAM_CHAT_ID=-1002148921456

# 2. บอตสืบสวนแฟ้มคดี NAS และสกัดพิกัด GPS ภาพถ่าย (nas-bot-service)
TELEGRAM_BOT_TOKEN=7182938475:AAFl9xKxxxxxxxxxxxxxxxxxxxxx
คำสั่งรีสตาร์ทคอนเทนเนอร์เพื่อให้ค่าใหม่มีผล: docker compose up -d lpr-service nas-bot-service
ทางเลือก B: ตั้งค่าบน n8n Automation Workflows:
  1. เข้าหน้าเว็บ n8n: http://<IP_เครื่องใหม่>:5678
  2. ไปที่เมนู Credentials (ไอคอนรูปกุญแจ) ➔ คลิก Add Credential
  3. พิมพ์ค้นหา Telegram API ➔ วางค่า Access Token ที่ได้จาก @BotFather ➔ คลิก Save
  4. ใน Workflow ของ Chapter 3 ให้เลือก Credential นี้ แล้วใส่ Chat ID ที่ช่องปลายทาง
🚀 ขั้นตอนที่ 4: คำสั่งทดสอบยิงข้อความจริง (1-Click Test) Verification

ก่อนเปิดใช้งานระบบจริง ให้ทดสอบยิงข้อความผ่านคำสั่ง Terminal บนเครื่องใหม่ เพื่อยืนยันว่า Token และ Chat ID ใช้งานได้ 100%:

คำสั่งทดสอบผ่าน cURL (รันได้บน PowerShell / Bash):
curl -s -X POST "https://api.telegram.org/bot<TOKEN>/sendMessage"      -d "chat_id=<CHAT_ID>"      -d "text=🚨 [ทดสอบระบบ BPP-VMS] บอตเชื่อมต่อเครื่องใหม่สำเร็จเรียบร้อยแล้วครับ! 🟢"      -d "parse_mode=HTML"
หรือทดสอบตรงจาก Docker Container ของระบบ BPP-VMS:
docker exec -it lpr-service python -c "from app import send_telegram_alert; send_telegram_alert('🚨 ทดสอบส่งแจ้งเตือนจากเครื่องใหม่สำเร็จ 100%', None)"
✅ หากมีข้อความแจ้งเตือนเด้งขึ้นใน Telegram แชทหรือกลุ่มทันที แปลว่าพร้อมเปิดใช้งานทั้งระบบ 100%!
🛠️
ตารางวิเคราะห์และแก้ปัญหาที่พบบ่อยเมื่อตั้งค่า Telegram Bot บนเครื่องใหม่ (Troubleshooting Checklist)
รหัสข้อผิดพลาด / อาการ สาเหตุที่แท้จริง วิธีแก้ไขหน้างาน วิธีป้องกันในระยะยาว
401 Unauthorized Token ไม่ถูกต้อง, มีช่องว่างเกิน หรือคัดลอกมาไม่ครบทุกตัวอักษร กลับไปที่ @BotFather พิมพ์ /token แล้วคัดลอก Token ทั้งชุดมาวางใหม่ใน .env ห้ามแก้ไข Token ด้วยมือ ให้ใช้การคลิกคัดลอกโดยตรงจาก Telegram
400 Bad Request: chat not found 1. แชทส่วนตัว: ผู้รับยังไม่เคยกด /start กับบอต
2. แชทกลุ่ม: ลืมใส่เครื่องหมายลบ -100
- แชทส่วนตัว: ทักบอตแล้วกดปุ่ม Start 1 ครั้ง
- แชทกลุ่ม: ตรวจสอบ Chat ID ให้ขึ้นต้นด้วย -100 เช่น -1002148921456
ดึงบอต @RawDataBot เข้ากลุ่มเพื่อตรวจสอบ ID ที่มีเครื่องหมายลบติดมาให้แน่นอน
400 Bad Request: can't parse entities ข้อความมีแท็ก HTML ผิดรูปแบบ เช่น มี <b> แต่ไม่มี </b> ปิด ตรวจเช็กใน Code Node ให้ปิดแท็ก HTML ให้ครบ หรือส่งข้อความสำรองแบบ Plain Text ใช้ Helper Function ปิดแท็กอัตโนมัติ หรือใช้ Fallback plain_text ในสคริปต์
403 Forbidden: not enough rights to send photos บอตในกลุ่มสายตรวจไม่ได้สิทธิ์ส่งภาพ หรือกลุ่มล็อกสิทธิ์ไม่ให้สมาชิกส่งมีเดีย เข้าไปที่ Group Settings ➔ Administrators ➔ ตั้งบอตเป็น Admin และเปิดสิทธิ์ Post Photos & Links ตั้งบอตเป็น Admin เสมอเมื่อใช้งานในกลุ่มภารกิจความมั่นคง
ศูนย์ปฏิบัติการระบบกล้องและเทคโนโลยี BPP-VMS (ตชด.215) • รวมหลักสูตรการสอน n8n ครบ 6 บทเรียนในไฟล์เดียว