How It Works
ตั้งแต่มีคนนั่งลง จนป้ายบนโต๊ะเปลี่ยนเป็นสีชมพูและจอขึ้นว่าโต๊ะไม่ว่าง ใช้เวลาราว 1 นาที หน้านี้ตามสัญญาณไปทีละ hop: เซนเซอร์แปลงโลกจริงเป็นไฟฟ้า, ESP32-S3 อ่านผ่านสายแบบต่าง ๆ, ส่งต่อทางวิทยุ, gateway ตัดสิน แล้วสั่ง servo และขึ้น cloud
ทุกรูปและตัวเลขจำลองตามแบบที่วางไว้ ยังไม่ใช่ค่าที่วัดจากเครื่องจริง · AI บนบอร์ดหรือบน cloud ยังรอทีมตัดสินใน P1
นั่งลงแล้วเกิดอะไรขึ้น
Fig 1 · 8 ขั้นมีคนนั่งลง
เรดาร์ LD2410 ใต้ขอบโต๊ะส่งคลื่น 24 GHz ออกไปตลอดเวลา คลื่นสะท้อนจากตัวคนกลับมา และแม้คนจะนั่งนิ่ง การหายใจทำให้หน้าอกขยับไม่กี่มิลลิเมตร เรดาร์จึงรายงานว่า “มีเป้านิ่ง” ได้ ส่วน AMG8833 บนหัวกล้องเห็นพิกเซลอุ่นขึ้นจาก 25 เป็น 31 °C
บนสาย · on the wireLD2410 → UART 256000 baud · เฟรม F4 F3 F2 F1 … ~10 ครั้ง/วินาที · AMG8833 → I²C 0x69, 64 พิกเซล
จากโลกจริงเป็นตัวเลข
sense → signal → bus → dataระบบ embedded ทุกระบบทำ 4 อย่างวนไป: รับรู้ (sensor แปลงปริมาณจริงเป็นสัญญาณไฟฟ้า), อ่าน (MCU รับสัญญาณผ่าน interface ที่ตกลงกันไว้), คิด (โปรแกรมตัดสินจากข้อมูล) และ สั่ง (actuator เปลี่ยนโลกจริงกลับ) ตารางนี้คือทุกเส้นทางเข้าออกของโปรเจกต์
| ปริมาณจริง | ตัวแปลง (transducer) | เป็นสัญญาณแบบไหน | ต่อเข้า MCU ด้วย | ได้เป็น |
|---|---|---|---|---|
| การเคลื่อนไหวและลมหายใจ | LD2410 เรดาร์ 24 GHz | คลื่นสะท้อน → โมดูลประมวลผลเอง | UART 256000 baud + ขา OUT | มี/ไม่มีเป้า, ระยะ, ความแรง |
| ความร้อนจากตัวคน | AMG8833 thermopile 8 × 8 | แรงดันเล็ก ๆ ต่อพิกเซล → ADC ในชิป | I²C ที่อยู่ 0x69 | อุณหภูมิ 64 จุด |
| แสงที่สะท้อนจากโต๊ะ | OV2640 กล้อง | พิกเซลทีละไบต์แบบขนาน | DVP: 8 สายข้อมูล + PCLK, VSYNC, HREF | ภาพ JPEG → ตัวจำแนก |
| CO₂ ในห้อง | SCD41 (gateway) | วัดการดูดกลืนแสงอินฟราเรด (NDIR) | I²C 0x62 | ppm |
| ความสว่าง | BH1750 (gateway) | photodiode → ADC ในชิป | I²C 0x23 | lux |
| เสียงในห้อง | MAX4466 ไมค์ + op-amp (gateway) | แรงดันแอนะล็อกรอบ 1.65 V | ADC1 ของ ESP32-S3, 12 บิต | ระดับเสียง |
| มุมของป้าย (ขาออก) | TD-7120MG servo 270° | PWM 50 Hz พัลส์ 0.5–2.5 ms | LEDC ของ ESP32-S3 | ป้ายหันหน้าที่ต้องการ |
เซนเซอร์รู้ได้อย่างไรว่ามีคน
ขั้น 1 และ 7เรดาร์ mmWave: เห็นแม้คนนั่งนิ่ง
LD2410 ส่งคลื่น 24 GHz ที่ความถี่ไต่ขึ้นเรื่อย ๆ (chirp แบบ FMCW) คลื่นที่สะท้อนตัวคนกลับมาช้ากว่าเดิม t = 2R / c พอเอาคลื่นขาออกกับขาเข้ามาผสมกัน จะได้ความถี่ผลต่าง (beat) ที่แปรตามระยะ R โมดูลทำ FFT เองแล้วแบ่งระยะเป็นช่อง (gate) ละ 0.75 m
คนนั่งนิ่งก็ยังหายใจ หน้าอกขยับไม่กี่มิลลิเมตร ทำให้เฟสของคลื่นสะท้อนเปลี่ยนเป็นจังหวะ เรดาร์จึงรายงาน เป้านิ่ง ได้ ซึ่ง PIR ธรรมดาทำไม่ได้ · ข้อควรระวัง: เรดาร์ทะลุโต๊ะบาง ๆ และเห็นคนโต๊ะข้าง ๆ ต้องตั้ง gate สูงสุดให้ไม่เกินระยะเก้าอี้
Thermal: แยกคนออกจากของ
ทุกวัตถุแผ่รังสีอินฟราเรดตามอุณหภูมิ (กำลัง ∝ T⁴) แต่ละพิกเซลของ AMG8833 เป็น thermopile ที่ร้อนขึ้นตามรังสีที่ได้รับ แล้วชิปแปลงเป็นอุณหภูมิให้ · คนผ่านเสื้อผ้าราว 30 °C ส่วนกระเป๋าเท่าห้อง จึงตอบได้ว่าสิ่งที่อยู่บนโต๊ะ เป็นคนหรือเป็นของ
กล้อง: ใช้เมื่อจำเป็นเท่านั้น
กล้องถ่ายภาพเดียวตอน gateway ขอ คือเมื่อไม่เห็นคนครบ 2 นาที เพื่อตอบว่าโต๊ะโล่งหรือมีของ ภาพไม่ถูกเก็บ ใช้แล้วทิ้ง ประหยัดไฟและรักษาความเป็นส่วนตัว
- th_max_c10 · ร้อนสุด × 10
- 318
- th_hot_px · พิกเซลที่เกิน 30 °C
- 5
- แปลว่า
- มีวัตถุอุ่นขนาดคน
| เรดาร์ | Thermal | กล้อง | Gateway สรุปว่า |
|---|---|---|---|
| เห็น | อุ่น | — | มีคน |
| ไม่เห็น 2 นาที | ไม่อุ่น | items_only | ไม่อยู่ (ทิ้งของ) |
| ไม่เห็น 2 นาที | ไม่อุ่น | empty | ว่าง |
| เห็น | ไม่อุ่น | — | สงสัย: พัดลม/ม่านไหว รอกล้องยืนยัน |
สัญญาณบนสาย
UART · I²C · PWM · ADCสายระหว่างบอร์ดส่งได้แค่ไฟสูง (3.3 V = 1) กับไฟต่ำ (0 V = 0) ตามจังหวะ สิ่งที่ต่างกันในแต่ละแบบคือ ใครกำหนดจังหวะ และ ใช้กี่สาย
- UART ไม่มีสายนาฬิกา (asynchronous) ทั้งสองฝั่งตกลง baud rate กันไว้ แล้วใช้ start bit เป็นจุดเริ่มจับเวลา · TX ของฝั่งหนึ่งต่อเข้า RX ของอีกฝั่ง
- I²C มีสายนาฬิกา (synchronous) ที่ MCU เป็นคนตี อุปกรณ์หลายตัวใช้ 2 สายร่วมกัน แยกด้วยที่อยู่ สายเป็น open-drain จึงต้องมีตัวต้านทาน pull-up (โมดูลส่วนใหญ่ใส่มาแล้ว)
- PWM ส่งค่าด้วยความกว้างพัลส์ ฮาร์ดแวร์ LEDC ของ ESP32 สร้างให้เองโดยไม่กิน CPU
- ADC อ่านแรงดันแอนะล็อกตรง ๆ เป็นตัวเลข ต้องสุ่มอ่านถี่พอกับสิ่งที่อยากรู้ (เสียงพูดต้องหลายพันครั้งต่อวินาที)
ทุกบอร์ดต้องต่อ GND ร่วมกัน เพราะ “ไฟสูง” วัดเทียบกับ GND ถ้า GND ไม่ร่วม 1 กับ 0 จะเพี้ยน
ข้างใน ESP32-S3
ขั้น 2 · interrupt, task, queueInterrupt แทนการวนถาม
ถ้าโปรแกรมวนถามขา OUT ของเรดาร์ซ้ำ ๆ (polling) CPU จะเสียเวลาเปล่า และอาจพลาดถ้ากำลังทำอย่างอื่นอยู่ · interrupt ให้ฮาร์ดแวร์เรียกฟังก์ชัน ISR ทันทีที่ขาเปลี่ยน ISR ต้องสั้นที่สุด แค่ปลุก task แล้วออก งานหนักไปทำใน task
แบ่งงานเป็น task
FreeRTOS สลับหลาย task บน CPU ให้ดูเหมือนทำพร้อมกัน task ส่งข้อมูลให้กันผ่าน queue ไม่ใช้ตัวแปร global ร่วมกัน จึงไม่ชนกัน · ห้ามใช้ delay() ยาว ๆ เพราะจะบล็อกทุกอย่าง ใช้ vTaskDelay ที่ยกเวลาให้ task อื่นแทน
// เรดาร์ดึงขา OUT ขึ้นเมื่อเห็นคน: ปลุก task แทนการวนถาม
void IRAM_ATTR onRadarOut() {
BaseType_t woke = pdFALSE;
xSemaphoreGiveFromISR(radarSem, &woke); // สั้นที่สุด: แค่ส่งสัญญาณ
portYIELD_FROM_ISR(woke);
}
void sensorTask(void *) {
for (;;) {
xSemaphoreTake(radarSem, pdMS_TO_TICKS(100)); // ตื่นเมื่อ ISR ปลุก หรือทุก 100 ms
Reading r = readRadarFrame(); // UART
readThermal(r.pixels); // I²C 64 พิกเซล
xQueueSend(readings, &r, 0); // ให้ radio task รวมเป็นรายงาน
}
}| Task | ทำงานเมื่อ | ทำอะไร |
|---|---|---|
sensor | 10 ครั้ง/วินาที และเมื่อ ISR ปลุก | อ่านเฟรม UART ของเรดาร์ และ 64 พิกเซลจาก I²C แล้วใส่ queue |
vision | เมื่อ gateway ขอ (ไม่เห็นคน 2 นาที) | ถ่าย 1 ภาพ จำแนก empty / person / items_only · ภาพไม่ถูกเก็บ |
radio | ทุก 2 วินาที และทันทีเมื่อค่าเปลี่ยน | รวมค่าจาก queue เป็น node_report_t ส่ง ESP-NOW รอ ACK และ retry |
sign | เมื่อได้คำสั่ง SET_SIGN | ตั้ง LEDC duty ให้ servo หมุน แล้วหยุดจ่ายพัลส์หลัง 1.5 วินาที (timeout) |
ส่งทางวิทยุด้วย ESP-NOW
ขั้น 3 · 18 ไบต์ESP-NOW ใช้วิทยุ Wi-Fi ตัวเดิมของ ESP32 ส่งข้อความสั้นถึงบอร์ดอื่นตรง ๆ ด้วย MAC address ไม่ต้องต่อ router และไม่ต้องมี IP ข้อความหนึ่งยาวได้ถึง 250 ไบต์ ของเรามี 18
- ACK: ผู้รับตอบกลับในระดับฮาร์ดแวร์ทันที send callback บอกฝั่งส่งว่าสำเร็จไหม ไม่สำเร็จก็ส่งซ้ำ (สูงสุด 3 ครั้ง) · seq ช่วยให้ gateway ทิ้งข้อความซ้ำ
- ช่องสัญญาณต้องตรงกัน: gateway ต่อ Wi-Fi ห้องสมุดด้วย วิทยุตัวเดียวจึงอยู่ได้ช่องเดียว node ต้องส่งบนช่องเดียวกับ access point
- ทางกลับ: gateway สั่ง node ด้วยข้อความสั้นกว่า
{ver, node_id, seq, cmd = SET_SIGN, face}
ตัวอย่างไบต์ด้านขวาคือรายงานหนึ่งวินาทีหลังมีคนนั่งที่ D07 · โครงสร้างเต็มอยู่ใน Docs และ Build Plan ขั้น B2
__attribute__((packed)) กันไม่ให้ compiler แทรกช่องว่างระหว่างฟิลด์ ทั้งสองฝั่งจึงตีความไบต์ตรงกันGateway ตัดสินด้วย state machine
ขั้น 4 และ 7–8โต๊ะหนึ่งตัวอยู่ได้ทีละ สถานะ และเปลี่ยนสถานะเมื่อเงื่อนไขครบเท่านั้น gateway เก็บ state machine แยกต่อโต๊ะ พร้อมเวลาที่เห็นคนครั้งล่าสุด (จาก millis()) ทุกรายงานที่เข้ามาจะถูกเอามาเทียบกับเงื่อนไขในรูป
ทำไมตัดสินที่ gateway ไม่ใช่ที่ node: กติกาอยู่ที่เดียว แก้ทีเดียวได้ทุกโต๊ะ, cloud เห็นความจริงชุดเดียว และ node เหลือหน้าที่แค่วัดกับหมุนป้าย
หมุนป้ายด้วย servo
ขั้น 5 · PWM และไฟเลี้ยงServo คือระบบควบคุมแบบป้อนกลับในกล่องเล็ก ๆ
ข้างใน servo มีมอเตอร์ เฟือง และตัวต้านทานปรับค่าที่หมุนตามแกน วงจรข้างในเทียบมุมที่วัดได้กับมุมที่พัลส์สั่ง แล้วหมุนจนตรงกัน (closed loop) · เราแค่ส่งพัลส์ 500–2500 µs ทุก 20 ms ดูได้ในแท็บ PWM ด้านบน
ป้ายมี 3 หน้าห่างกัน 120° จึงต้องใช้ servo 270° · มีหมุดกันหมุนเกินที่ฐาน และหยุดส่งพัลส์หลังหมุนเสร็จ (timeout) servo จะได้ไม่เกร็งค้างจนร้อน
ทำไมต้องมีตัวเก็บประจุ 1000 µF
ตอน servo เริ่มหมุน มันดึงกระแสกระชากสูงชั่วขณะ (ระดับ 1–2 A) อแดปเตอร์ตอบสนองไม่ทัน แรงดัน 5 V จึงตกวูบ ถ้าตกต่ำพอ ESP32 จะ brownout แล้วรีเซ็ต
ΔV = I · Δt / C = 2 A × 0.1 ms / 1000 µF = 0.2 V
ตัวเก็บประจุที่วางชิดขั้ว servo จ่ายกระแสช่วงสั้น ๆ นั้นแทน แรงดันจึงตกแค่ราว 0.2 V แทนที่จะตกจนบอร์ดดับ (ตัวเลขเป็นค่าประมาณเพื่อดูขนาด) · servo รับไฟจาก terminal block ตรงจากอแดปเตอร์ ไม่ผ่านบอร์ด และใช้ GND ร่วมกัน
ขึ้น cloud แล้วกลับลงมาที่จอ
ขั้น 6 และ 8Gateway เป็นบอร์ดเดียวที่ต่ออินเทอร์เน็ต เมื่อสถานะโต๊ะเปลี่ยน มันส่งข้อมูล 1 แถวเป็น JSON ไปที่ REST API ของ Supabase ผ่าน HTTPS ซึ่งเข้ารหัสด้วย TLS (บอร์ดต้องรู้จัก root certificate ของ server) Supabase เก็บแถวลง Postgres แล้วใช้ Realtime ดันแถวใหม่ผ่าน WebSocket ไปที่หน้า dashboard ที่เปิดอยู่
LINE ก็ใช้ HTTPS เหมือนกัน: gateway POST ข้อความไปที่ LINE Messaging API พร้อม token แล้ว LINE ส่งต่อถึงโทรศัพท์ · key และ token อยู่ใน secrets.h ที่ไม่ขึ้น GitHub
ถ้าเน็ตหลุด gateway ยังตัดสินสถานะและสั่งป้ายได้ตามปกติ (ทุกอย่างในห้องใช้ ESP-NOW) แล้วเก็บแถวไว้ใน RAM ส่งตามทีหลังเมื่อเน็ตกลับมา (store-and-forward)
POST https://<project>.supabase.co/rest/v1/desk_events
apikey: <anon key จาก secrets.h>
Content-Type: application/json
{
"desk": "D07",
"state": "inUse",
"at": "2026-11-20T13:01:00Z",
"mmw_dist_cm": 62,
"th_max_c": 31.8,
"ai_class": null,
"seq": 1042
}| ช่วง | ใช้เวลา | เพราะ |
|---|---|---|
| เรดาร์เห็นคน → node อ่านค่า | ~0.1 s | เรดาร์ส่งเฟรม ~10 ครั้ง/วินาที |
| ESP-NOW node → gateway | < 1 ms | 18 ไบต์บนอากาศ (+ retry ถ้าไม่ได้ ACK) |
| กติกา “เห็นครบ 1 นาที” | 60 s | ตั้งใจรอ กันคนเดินผ่าน |
| SET_SIGN → servo หมุนเสร็จ | ≤ 0.8 s | servo หมุนราว 0.2 s ต่อ 60° · ไกลสุด 240° |
| HTTPS ขึ้น Supabase | ~0.3–1 s | TLS handshake กินเวลามากสุด |
| Realtime → dashboard | ~0.1–0.5 s | WebSocket ที่เปิดค้างไว้ |
| รวม | ~61–63 s | เกือบทั้งหมดคือการรอ 1 นาทีที่ตั้งใจไว้ |
ศัพท์ที่ใช้ในหน้านี้
Glossary- MCU
- microcontroller ชิปเดียวที่มี CPU, RAM, flash และขา I/O ในตัว · ที่นี่คือ ESP32-S3
- GPIO
- ขาดิจิทัลอเนกประสงค์ อ่านได้ว่าเป็น 0 หรือ 1 หรือสั่งให้เป็น 0/1
- ISR
- Interrupt Service Routine ฟังก์ชันสั้น ๆ ที่ฮาร์ดแวร์เรียกทันทีเมื่อขาเปลี่ยน แทนการวนถามซ้ำ (polling)
- FreeRTOS task
- งานย่อยที่ทำงานสลับกันบน CPU มีลำดับความสำคัญของตัวเอง ส่งข้อมูลกันผ่าน queue
- UART
- สายสื่อสารอนุกรมไม่มีนาฬิกา สองฝั่งต้องตั้ง baud rate ตรงกัน TX ต่อ RX ไขว้กัน
- I²C
- บัส 2 สาย (SDA, SCL) ต่อพ่วงหลายอุปกรณ์ แยกกันด้วยที่อยู่ 7 บิต ต้องมีตัวต้านทาน pull-up
- PWM
- สัญญาณเปิด-ปิดเป็นจังหวะ ส่งข้อมูลด้วยความกว้างของพัลส์ · servo อ่านความกว้างเป็นมุม
- ADC
- ตัวแปลงแรงดันแอนะล็อกเป็นตัวเลข 12 บิต = 0–4095
- ESP-NOW
- โปรโตคอลของ Espressif ส่งข้อความสั้นถึงบอร์ดอื่นตรง ๆ ด้วยวิทยุ Wi-Fi โดยไม่ต้องต่อ router
- Gateway
- บอร์ดกลางของห้อง รับข้อมูลจาก node ทุกตัว ตัดสินสถานะ และเป็นตัวเดียวที่ต่ออินเทอร์เน็ต
- TLS / HTTPS
- การเข้ารหัสระหว่างบอร์ดกับ server ทำให้คนในเครือข่ายเดียวกันอ่านหรือปลอมข้อมูลไม่ได้
- Realtime
- บริการของ Supabase ที่ดันแถวข้อมูลใหม่ไปยังหน้าเว็บผ่าน WebSocket ทันที หน้าเว็บไม่ต้องรีเฟรช