📖 API Reference v3.13.0
เริ่มต้นใช้งาน APIOTA

APIOTA คือแพลตฟอร์มจัดการ OTA (อัปเดตเฟิร์มแวร์ผ่านอินเทอร์เน็ต) สำหรับ ESP32/Arduino แบบครบวงจร — อัปโหลดเฟิร์มแวร์ครั้งเดียว อุปกรณ์ทุกตัวอัปเดตเอง พร้อมสั่งงาน/ดูสถานะ/ตั้งค่าจากระยะไกล คู่มือนี้รวบรวมการใช้งานทุกเมนูบน Dashboard

ภาพรวม 4 ขั้นแรก
  1. สมัคร & เข้าสู่ระบบ ที่ apiota.net → ได้แผน Free อัตโนมัติ
  2. สร้าง Provisioning Key (เมนู 🔑 Provisioning) — กุญแจให้อุปกรณ์ลงทะเบียนตัวเองอัตโนมัติ
  3. ฝังโค้ด APIOTA library ลง ESP32 (ดูหัวข้อ ESP32 Integration) → อุปกรณ์โผล่ในเมนู 📟 Devices เอง
  4. อัปโหลดเฟิร์มแวร์ (เมนู 💾 Firmware) → อุปกรณ์เช็คเจอเวอร์ชันใหม่และอัปเดตเอง
แผนผังเมนู Dashboard
กลุ่มเมนูใช้ทำอะไร
Main🏠 Dashboardภาพรวม: จำนวนอุปกรณ์ออนไลน์, OTA ล่าสุด, การใช้โควต้า
📁 Projectsจัดกลุ่มงานเป็นโปรเจกต์ แยกเฟิร์มแวร์/อุปกรณ์
📟 Devicesรายการอุปกรณ์ + รายละเอียด + 🖥 Console
💾 Firmwareอัปโหลด/จัดการไฟล์ .bin + ช่อง stable/beta/dev
Management🗂 Groupsจัดอุปกรณ์เป็นกลุ่ม สั่ง OTA ทีละกลุ่ม
⚙️ Device Configตั้งค่า key-value ถาวรให้อุปกรณ์ดึงไปใช้
🔔 Webhooksส่ง event ออกไปยังระบบอื่น (Discord/LINE/HTTP)
📋 OTA Logsประวัติทุกขั้นตอนการอัปเดต
🔑 Provisioningสร้าง ak_ key ให้อุปกรณ์ลงทะเบียนอัตโนมัติ
⏱ Device Lifetimeกำหนดอายุการรับ OTA ของอุปกรณ์
มุมขวาบน👤 Accountแผน, วันหมดอายุ, เติมคูปอง, API keys, โควต้า
📸 จุดใส่ภาพ: หน้า Dashboard ภาพรวม + แถบเมนูซ้าย

Projects — จัดกลุ่มงาน

โปรเจกต์ใช้แยกงานออกจากกัน เช่น "ป้ายไฟสาขา A" กับ "เซนเซอร์โรงงาน B" — เฟิร์มแวร์ อุปกรณ์ และคีย์ จะผูกกับโปรเจกต์ ทำให้ไม่ปนกัน

  1. เมนู 📁 Projects → ปุ่ม + New Project
  2. ตั้งชื่อ + คำอธิบาย → Save
  3. เวลาอัปโหลดเฟิร์มแวร์/สร้างคีย์ ให้เลือกโปรเจกต์นี้เพื่อจัดกลุ่ม
ℹ️ จำนวนโปรเจกต์จำกัดตามแพลน: Free 1 · Starter 5 · Pro 20 · Enterprise ไม่จำกัด — ถ้าเต็มจะสร้างเพิ่มไม่ได้จนกว่าจะอัปเกรดหรือลบของเก่า
📸 จุดใส่ภาพ: หน้า Projects + ฟอร์มสร้างโปรเจกต์

Provisioning Key — ลงทะเบียนอุปกรณ์อัตโนมัติ

Provisioning Key (ขึ้นต้น ak_) คือกุญแจที่ฝังในเฟิร์มแวร์ ทำให้อุปกรณ์ "สมัครตัวเอง" ตอนเปิดเครื่องครั้งแรก โดยไม่ต้องตั้งค่าทีละตัว — เหมาะกับการ flash อุปกรณ์จำนวนมากด้วยไฟล์เดียว

  1. เมนู 🔑 Provisioning+ New Key → ตั้งชื่อ + เลือก Group/Project (ถ้าต้องการ)
  2. คัดลอกค่า ak_xxxxxxxxxxxx ที่ได้
  3. ใส่ในโค้ด: APIOTA.begin("ak_xxxxxxxxxxxx", "1.0.0", "MyDevice")
  4. อุปกรณ์เปิดครั้งแรก → ลงทะเบียนเอง → โผล่ในเมนู Devices
ℹ️ ตัวนับ: แต่ละคีย์โชว์ Devices Used (จำนวนอุปกรณ์ที่สมัครผ่านคีย์นี้) และ Firmware Builds
⚠️ ความปลอดภัย: ak_ key เปรียบเหมือนรหัสผ่าน — อย่าเปิดเผยใน repo สาธารณะ ถ้าหลุดให้ลบคีย์แล้วสร้างใหม่
📸 จุดใส่ภาพ: ตาราง Provisioning Keys + ฟอร์มสร้างคีย์

Devices — จัดการอุปกรณ์

อุปกรณ์จะโผล่ที่นี่อัตโนมัติเมื่อ provision สำเร็จ แต่ละแถวบอกสถานะออนไลน์ (last seen), เวอร์ชันเฟิร์มแวร์, และปุ่มจัดการ

สถานะอุปกรณ์
สถานะความหมาย
activeทำงานปกติ รับ OTA ได้
lockedล็อกชั่วคราว — ไม่รับ OTA/คำสั่ง
inactiveปิดใช้งาน
verifiedอนุมัติแล้ว (หลัง approve อุปกรณ์ที่รอตรวจ)
สิ่งที่ทำได้ในหน้า Devices
  1. คลิกอุปกรณ์ → ดูรายละเอียด (เวอร์ชัน, IP, last seen, โปรเจกต์/กลุ่ม)
  2. ปุ่ม 🖥 Console → เปิด Device Console สั่งงาน/ดู telemetry (ดูหัวข้อ Device Console)
  3. ปุ่มจัดการสถานะ: Lock / Unlock / Approve / Inactive
  4. ลบอุปกรณ์ที่ไม่ใช้แล้วเพื่อคืนโควต้า
ℹ️ จำนวนอุปกรณ์จำกัดตามแพลน: Free 5 · Starter 50 · Pro 300 · Enterprise 2000 — ถ้าเต็มจะเพิ่มอุปกรณ์ไม่ได้จนกว่าจะอัปเกรดหรือลบของเก่า
📸 จุดใส่ภาพ: ตารางรายการ Devices + หน้ารายละเอียด

Device Groups — จัดอุปกรณ์เป็นกลุ่ม

รวมอุปกรณ์หลายตัวเป็นกลุ่ม เพื่อสั่ง OTA หรือจัดการพร้อมกัน เช่น กลุ่ม "สาขากรุงเทพ" แยกจาก "สาขาเชียงใหม่"

  1. เมนู 🗂 Groups+ New Group → ตั้งชื่อ
  2. เพิ่มอุปกรณ์เข้ากลุ่ม (หรือผูกตั้งแต่ตอนสร้าง Provisioning Key)
  3. ปล่อยเฟิร์มแวร์/ตั้งค่าให้ทั้งกลุ่มทีเดียว
📸 จุดใส่ภาพ: หน้า Groups + การเพิ่มอุปกรณ์เข้ากลุ่ม

Firmware & OTA — อัปโหลดและอัปเดต

หัวใจของ APIOTA: อัปโหลดไฟล์ .bin ครั้งเดียว อุปกรณ์ทุกตัวที่ตรงเงื่อนไขจะอัปเดตเอง

วิธีอัปโหลดเฟิร์มแวร์
  1. เมนู 💾 Firmware+ Upload Firmware
  2. เลือกไฟล์ .bin + กรอก Version (เช่น 1.2.0)
  3. เลือก Channel: stable (ทุกเครื่อง) / beta / dev (เฉพาะเครื่องทดสอบ)
  4. เลือก Project (ถ้ามี) → Upload
OTA ทำงานยังไง

อุปกรณ์เรียก check-update เป็นช่วงๆ → ถ้าเจอเวอร์ชันใหม่กว่า จะดาวน์โหลด, ตรวจลายเซ็น RSA + SHA256, แล้ว flash + reboot อัตโนมัติ — ทุกขั้นถูกบันทึกใน OTA Logs

🔒 ความปลอดภัย: ทุกไฟล์เซ็นด้วย RSA และตรวจ SHA256 ก่อน flash — กันเฟิร์มแวร์ปลอม · รองรับ resume ดาวน์โหลด (HTTP Range) ถ้าเน็ตหลุดกลางคัน
เวอร์ชันต้องเรียงให้ถูก (Semantic Version)

ใช้รูปแบบ MAJOR.MINOR.PATCH เช่น 1.0.9 → 1.1.0 — อุปกรณ์อัปเดตเฉพาะเมื่อเวอร์ชันบนเซิร์ฟเวอร์ "ใหม่กว่า" ที่ตัวมันมี

ℹ️ ขนาดไฟล์: แต่ละไฟล์เฟิร์มแวร์จำกัดขนาดไม่เกิน 4 MB ต่อครั้ง — และพื้นที่จัดเก็บรวมมีโควต้าตามแพลน
📸 จุดใส่ภาพ: หน้า Firmware + ฟอร์มอัปโหลด + ตารางเวอร์ชัน

OTA Logs — ติดตามการอัปเดต

บันทึกทุกขั้นตอน OTA ของอุปกรณ์ทุกตัว ใช้ไล่ดูว่าใครอัปเดตสำเร็จ/ล้มเหลวตรงไหน

8 สถานะของ OTA

pending → checking → downloading → verifying → flashing → rebooting → done (หรือ failed)

กลุ่มสถานะหมายถึง
done (เขียว)อัปเดตสำเร็จครบขั้นตอน
downloading/flashing (ฟ้า)กำลังทำงาน
failed (แดง)ล้มเหลว — ดู message ประกอบ
  1. กรองตามอุปกรณ์/สถานะ/ช่วงเวลา
  2. คลิกแถวเพื่อดู from_version → to_version + ข้อความ error
  3. ปุ่ม Clear ล้าง log เก่า
📸 จุดใส่ภาพ: ตาราง OTA Logs + badge สีสถานะ

Device Config — ตั้งค่าทางไกล (ค่าถาวร)

เก็บค่า key-value ให้อุปกรณ์ดึงไปใช้ เช่น setpoint อุณหภูมิ, รอบการทำงาน — แก้บน Dashboard แล้วอุปกรณ์ดึงค่าใหม่โดยไม่ต้องอัปเดตเฟิร์มแวร์ ค่าจะอยู่ถาวร (ต่างจาก Console ที่เป็นคำสั่งครั้งเดียว)

  1. เมนู ⚙️ Device Config → เลือกอุปกรณ์มุมขวาบน
  2. กด + Add Key → ใส่ชื่อคีย์ + ค่า (เช่น temp = 50)
  3. กด Save Config
  4. อุปกรณ์ดึงค่าผ่าน GET /api/device/config (library ทำให้อัตโนมัติ)

ฝั่งอุปกรณ์อ่านค่า:

HTTPClient http;
http.begin(String(OTA_SERVER) + "/api/device/config");
http.addHeader("X-Device-ID",     DEVICE_ID);
http.addHeader("X-Device-Secret", DEVICE_SECRET);
if (http.GET() == 200) {
  DynamicJsonDocument doc(1024);
  deserializeJson(doc, http.getString());
  JsonObject config = doc["config"];
  int temp = config["temp"] | 0;   // ค่า default หลัง |
}
http.end();
ℹ️ จำนวนคีย์จำกัดตามแพลน: Free 3 · Starter 15 · Pro 50 · Enterprise ไม่จำกัด
📸 จุดใส่ภาพ: หน้า Device Config + ตาราง key-value

Webhooks — ส่ง event ออกไประบบอื่น

ให้ APIOTA ยิงแจ้งเตือนไปยัง URL ที่คุณกำหนด เมื่อเกิดเหตุการณ์ เช่น OTA สำเร็จ/ล้มเหลว หรืออุปกรณ์ออฟไลน์ — ต่อเข้ากับ Discord, LINE Notify, หรือระบบของคุณเองได้

  1. เมนู 🔔 Webhooks+ Add Webhook
  2. ใส่ Target URL + เลือก event ที่ต้องการ
  3. ทดสอบด้วยปุ่ม Test → ตรวจว่าปลายทางได้รับ payload JSON
ℹ️ Intake: นอกจากส่งออก ยังมี endpoint รับข้อมูลเข้า (/api/webhook/...) ซึ่งนับเป็น 📡 Realtime
📸 จุดใส่ภาพ: หน้า Webhooks + ฟอร์มตั้งค่า

Device Lifetime — อายุการรับ OTA

กำหนดว่าอุปกรณ์จะรับ OTA ได้ถึงเมื่อไหร่ เหมาะกับงานเช่า/สัญญาบริการ — พอหมดอายุ อุปกรณ์จะหยุดรับอัปเดตและ library จะเรียก callback onExpired

  1. เมนู ⏱ Device Lifetime → เลือกอุปกรณ์
  2. ใช้ Quick Set (เช่น +30 วัน) หรือ Set Lifetime กำหนดวันเอง
  3. ระบบ ⚠️ แจ้งเตือนเมื่อใกล้หมดอายุ
APIOTA.onExpired([](APIOTAExpiry reason, const String& msg){
  // APEX_LIFT_TIME = หมดอายุการรับ OTA
  // แสดง "หมดอายุ" หรือหยุดทำงานบางส่วน
});
📸 จุดใส่ภาพ: หน้า Device Lifetime + Quick Set

Authentication

ESP32 ยืนยันตัวตนด้วย device_id และ device_secret ที่ได้รับตอน register · การลงทะเบียนอุปกรณ์ใหม่ใช้ Provisioning Key (ak_) ที่สร้างจาก Dashboard

Device Authentication

ใส่ device_id และ device_secret เป็น query parameter หรือ request body ในทุก API call

// ตัวอย่าง ESP32 Arduino
String url = "https://apiota.net/api/check-update";
url += "?device_id=" + String(DEVICE_ID);
url += "&version=" + String(CURRENT_VERSION);
url += "&device_secret=" + String(DEVICE_SECRET);
Provisioning Keys (ak_)

สำหรับ register อุปกรณ์ใหม่อัตโนมัติ ใช้ ak_ key ที่สร้างจาก Dashboard

// Provision device ด้วย ak_ key
POST /api/device/provision
{
  "api_key":     "ak_xxxxxxxxxxxx",
  "device_id":   "esp32-prod-001",
  "device_name": "MyDevice",
  "version":     "1.0.0",
  "chip_info":   "ESP32-D0WD"
}

API Endpoints

Endpoints ทั้งหมดที่ให้บริการ แบ่งตามหมวดหมู่

System
GET /health ตรวจสอบสถานะ server

ตรวจสอบว่า server ทำงานปกติ ไม่ต้องการ authentication

Response 200
{
  "status": "ok",
  "version": "3.13.0",
  "time": "2026-07-01T10:30:00Z",
  "db": "sqlite",
  "uptime": 86400
}
OTA Updates
GET /api/check-update ตรวจสอบเฟิร์มแวร์ใหม่

อุปกรณ์ใช้ endpoint นี้เพื่อตรวจว่ามีเฟิร์มแวร์เวอร์ชันใหม่หรือไม่ ESP32 ควร poll ทุก 5–15 นาที

Query Parameters
ParameterTypeRequiredDescription
device_idstringrequiredDevice ID ที่ลงทะเบียนไว้
versionstringrequiredFirmware version ปัจจุบัน เช่น 1.0.9
device_secretstringrequiredSecret key ของอุปกรณ์
Response
// มี update ใหม่
{
  "update": true,
  "version": "1.1.0",
  "url": "https://apiota.net/api/firmware/fw-1.1.0.bin",
  "sha256": "a3f8b2c1...",
  "size": 524288,
  "channel": "stable"
}

// ไม่มี update
{ "update": false }
POST /api/user/firmware/upload อัปโหลดเฟิร์มแวร์

อัปโหลดไฟล์ .bin เฟิร์มแวร์ ส่งเป็น multipart/form-data (ต้องมี JWT Bearer token)

Form Data Parameters
FieldTypeRequiredDescription
firmwarefile (.bin)requiredไฟล์เฟิร์มแวร์ .bin
versionstringrequiredVersion string เช่น 1.2.0
channelstringoptionalstable / beta / dev (default: stable)
project_idstringoptionalโปรเจกต์ที่เชื่อมโยง
# curl ตัวอย่าง
curl -X POST https://apiota.net/api/user/firmware/upload \
  -H "Authorization: Bearer <JWT>" \
  -F "firmware=@firmware-1.2.0.bin" \
  -F "version=1.2.0" \
  -F "channel=stable"
GET /api/firmware/:filename ดาวน์โหลดเฟิร์มแวร์

ดาวน์โหลดไฟล์เฟิร์มแวร์ binary โดยตรง ESP32 ใช้ URL นี้ใน OTA update process

Path Parameters
ParameterTypeDescription
filenamestringชื่อไฟล์ .bin ที่ได้รับจาก check-update
GET /api/firmware/fw-1.1.0-stable.bin
→ Returns: application/octet-stream
Devices
POST /api/device/provision ลงทะเบียนอุปกรณ์

ลงทะเบียนอุปกรณ์ใหม่ด้วย provisioning key ใช้ใน ESP32 ตอน boot ครั้งแรก

Request Body (JSON)
FieldTypeRequiredDescription
api_keystringrequiredProvisioning key (ak_...)
device_idstringrequiredDevice ID ไม่ซ้ำกัน
device_namestringrequiredชื่ออุปกรณ์
versionstringoptionalFirmware version ปัจจุบัน
chip_infostringoptionalข้อมูลชิป เช่น ESP32-D0WD
{
  "success": true,
  "device_id": "esp32-prod-001",
  "device_secret": "generated_by_server",
  "registered_at": "2026-05-17T10:30:00Z"
}
Logging
POST /api/device/ota-log บันทึก OTA status

ESP32 ส่ง status update ระหว่าง OTA process มี 8 สถานะ: pending → checking → downloading → verifying → flashing → rebooting → done / failed

Request Body (JSON)
FieldTypeRequiredDescription
device_idstringrequired
device_secretstringrequired
statusstringrequiredOTA status ปัจจุบัน
from_versionstringoptionalVersion เดิม
to_versionstringoptionalVersion ใหม่
messagestringoptionalข้อความเพิ่มเติม / error message
Device Config
GET /api/config ดึง device config

ดึง JSON config ที่ตั้งค่าไว้สำหรับอุปกรณ์นั้น

Query Parameters
ParameterTypeRequiredDescription
device_idstringrequired
device_secretstringrequired
{
  "config": {
    "interval": 5000,
    "log_level": "info",
    "retry_count": 3
  },
  "updated_at": "2026-05-17T08:00:00Z"
}

ESP32 Integration

ตัวอย่าง code สำหรับ Arduino / ESP-IDF เพื่อ integrate กับ APIOTA

📥 ดาวน์โหลด & ติดตั้งไลบรารี APIOTA

ไลบรารี APIOTA เป็น open-source บน GitHub — โหลดฟรี ติดตั้งใน Arduino IDE ได้เลย · ในรีโปมีตัวอย่างพร้อมใช้: BasicOTA, LILYGO_TDisplayS3 (จอ TFT), LILYGO_TSIM_A7670G (4G/LTE), ClearNVS (ล้างค่า)

⬇️ github.com/devjune-apiota/apiota-esp32

วิธีที่ 1 — Add .ZIP (ง่ายสุด): เปิดหน้า GitHub → ปุ่มเขียว Code ▾ → Download ZIP → ใน Arduino IDE: Sketch → Include Library → Add .ZIP Library… → เลือกไฟล์ ZIP ที่โหลดมา

วิธีที่ 2 — Git clone: clone เข้าโฟลเดอร์ Arduino/libraries/ โดยตรง (ตั้งชื่อโฟลเดอร์ว่า APIOTA):

cd ~/Documents/Arduino/libraries
git clone https://github.com/devjune-apiota/apiota-esp32.git APIOTA

เปิดตัวอย่าง: File → Examples → APIOTA → เลือก BasicOTA หรือรุ่นบอร์ดของคุณ → ใส่ Provision Key (ak_xxxx จาก Dashboard → Provisioning) + ssid/password → กด Upload · เสร็จแล้วดูโค้ด "OTA in 3 lines" ด้านล่าง

💡 อัปเดตไลบรารีเวอร์ชันใหม่: ดู Releases ในหน้า GitHub แล้วโหลด ZIP ใหม่มาทับ (หรือ git pull ในโฟลเดอร์ที่ clone ไว้)


APIOTA Library — OTA in 3 lines (แนะนำ)

วิธีง่ายสุด: ติดตั้ง APIOTA library แล้วเรียกแค่ connectWiFi() + begin() — ไลบรารีจัดการ provision, OTA, RSA/SHA verify, TLS (ISRG Root X1 CA), command long-poll และ log ทาง Serial ให้ครบ

#include <APIOTA.h>
APIOTAClient APIOTA;

void setup() {
  Serial.begin(115200);
  APIOTA.connectWiFi("ssid", "password");
  APIOTA.begin("ak_xxxxxxxx", "1.0.0", "MyDevice");
}
void loop() { APIOTA.tick(); delay(1000); }
Optional Events (Callbacks)

ไลบรารี log ทุก step ทาง Serial ให้อยู่แล้ว — เพิ่ม callback เหล่านี้เมื่ออยากให้อุปกรณ์ "ทำอะไรเพิ่ม" ตอนเกิด event (คุมฮาร์ดแวร์/จอ) — วางใน setup() ก่อน APIOTA.begin()

Callbackทำงานเมื่อใช้ทำอะไร
onProvisioned(id, secret)provision สำเร็จเก็บ id / แสดงสถานะ
onProgress(state, pct, msg)สถานะ OTA เปลี่ยน (checking→downloading%→flashing→success)โชว์ progress บนจอ/LED
onCommand(cmd, payload, id)server ส่งคำสั่งมา (Dashboard/Telegram/webhook)คุมรีเลย์/ไฟ, reboot, คำสั่งเอง
onError(msg)เกิด errorlog / แจ้งเตือน
onExpired(reason, msg)server สั่งหยุด (Lifetime/Working หมดอายุ, เกิน plan, ถูก block)แสดง "หมดอายุ" / หยุดทำงาน
APIOTA.onCommand([](const String& cmd, const String& payload, uint32_t id){
  if (cmd == "led_on")  digitalWrite(2, HIGH);
  if (cmd == "led_off") digitalWrite(2, LOW);
});  // reboot / check_update ไลบรารีจัดการให้เอง — อย่าเรียก ackCommand() ซ้ำ

APIOTA.onExpired([](APIOTAExpiry reason, const String& msg){
  // APEX_LIFT_TIME / APEX_WORKING / APEX_PLAN_LIMIT / APEX_BLOCKED
  digitalWrite(2, HIGH);
});
Config / TLS helpers

setCheckInterval(sec) ตั้งรอบเช็ค OTA · setInsecure() ปิด TLS verify (เฉพาะเน็ตที่ block NTP) · ปกติไลบรารี verify cert ด้วย ISRG Root X1 CA + sync เวลา NTP อัตโนมัติ


Manual / Raw HTTP (ขั้นสูง — ไม่ใช้ library)
Device Configuration
// config.h — ตั้งค่าหลัก
#define DEVICE_ID       "esp32-prod-001"
#define DEVICE_SECRET   "your_device_secret"
#define CURRENT_VERSION "1.0.9"
#define OTA_SERVER      "https://apiota.net"
#define OTA_INTERVAL    300000  // 5 นาที (ms)
OTA Check Loop
void checkOTA() {
  HTTPClient http;
  String url = String(OTA_SERVER) + "/api/check-update";
  url += "?device_id="     + String(DEVICE_ID);
  url += "&version="       + String(CURRENT_VERSION);
  url += "&device_secret=" + String(DEVICE_SECRET);

  http.begin(url);
  int code = http.GET();

  if (code == 200) {
    DynamicJsonDocument doc(1024);
    deserializeJson(doc, http.getString());

    if (doc["update"] == true) {
      String fwUrl = doc["url"];
      sendOTALog("downloading", doc["version"]);
      performOTA(fwUrl);
    }
  }
  http.end();
}
Sending OTA Logs
void sendOTALog(String status, String toVer = "") {
  HTTPClient http;
  http.begin(String(OTA_SERVER) + "/api/device/ota-log");
  http.addHeader("Content-Type", "application/json");

  DynamicJsonDocument body(256);
  body["device_id"]     = DEVICE_ID;
  body["device_secret"] = DEVICE_SECRET;
  body["status"]        = status;
  body["from_version"]  = CURRENT_VERSION;
  if (toVer.length() > 0) body["to_version"] = toVer;

  String out;
  serializeJson(body, out);
  http.POST(out);
  http.end();
}
⚠️ ใช้ WiFiClientSecure และตรวจสอบ SHA256 ของเฟิร์มแวร์ก่อน flash เสมอ เพื่อป้องกันเฟิร์มแวร์ปลอม

Device Console — สั่งงาน & รับค่าแบบ Real-time

Device Console คือช่องสื่อสาร 2 ทางระหว่าง Dashboard กับอุปกรณ์: ↓ ส่งคำสั่ง (reboot, เปิด/ปิดไฟ, คำสั่งเอง) ไปยังอุปกรณ์ทันที และ ↑ รับ Telemetry (heap, RSSI, uptime, ค่าใดๆ) ที่อุปกรณ์รายงานกลับขึ้นมาโชว์เป็นการ์ด — คนละกลไกกับ Device Config (ที่เก็บค่า "ถาวร") แต่ใช้คู่กันได้

ℹ️ สำคัญ: Command เป็นแบบ one-shot (สั่งครั้งเดียว ทำเสร็จหาย) ส่วน Device Config เก็บค่าถาวร · ทั้งคู่ auth ด้วย X-Device-ID + X-Device-Secret เหมือนกัน · Send Command + Device Config = ทุกแผน (รวม Free — จำกัดด้วยโควต้า 📡 Realtime + Config Keys ต่อแผน) · Console + Telemetry = แผน Starter+ · Outbound Webhooks (แท็บ 📤 ในเมนู Webhooks) = Pro+
วิธีเปิด Device Console (สำหรับ User)

เข้า Dashboard → เมนู อุปกรณ์ (Devices) → คลิกที่อุปกรณ์ที่ต้องการ → กดปุ่ม 🖥 Console → หน้าต่าง Console จะเด้งขึ้นมา ประกอบด้วย 3 ส่วน:

ส่วนคืออะไรใช้ยังไง
การ์ดด้านบนค่า Telemetry ล่าสุดที่อุปกรณ์ส่งมา (heap_kb, rssi, uptime_min, …)กด + การ์ด เพื่อเลือกคีย์ที่อยากปักหมุดโชว์ · กด ↻ อัตโนมัติ ให้รีเฟรชเอง
ไทม์ไลน์กลางประวัติ 2 ทาง: ↓ คำสั่งที่ส่งออก · ↑ ค่าที่อุปกรณ์รายงานติ๊ก "ทุกจุด" เพื่อดูทุก event · กดถังขยะเพื่อล้าง
แถบล่างปุ่มลัด + ช่องพิมพ์คำสั่งเอง + ช่อง JSON payloadกดปุ่มลัด (reboot/check_update) หรือพิมพ์คำสั่งเอง + payload แล้วกด ▸ ส่ง
ส่งคำสั่งจาก Console (ฝั่งส่ง)

พิมพ์ชื่อคำสั่งในช่องซ้าย เช่น set_led และใส่ payload เป็น JSON ในช่องขวา เช่น {"value":1} แล้วกด ▸ ส่ง · ปุ่ม 📌 ใช้ปักคำสั่งที่ใช้บ่อยให้เป็นปุ่มลัด · เบื้องหลัง Dashboard จะยิง:

POST /api/user/devices/:device_id/command ส่งคำสั่งเข้าคิว (JWT)

ส่งคำสั่งเข้าคิว อุปกรณ์จะได้รับผ่าน long-poll · ต้องแผน Starter ขึ้นไป (คำสั่งจัดการสถานะ lock/unlock/approve ใช้ได้ทุกแผน)

// Request body
{
  "command": "set_led",
  "payload": { "value": 1 }
}
// Response → { command_id, queued: true }
ฝั่งรับบนอุปกรณ์ (ESP32) — แบบ Library (แนะนำ)

ถ้าใช้ APIOTA library แค่ใส่ callback onCommand — library จัดการ long-poll + ack ให้เอง (reboot/check_update ทำให้อัตโนมัติ ไม่ต้อง ack ซ้ำ)

APIOTA.onCommand([](const String& cmd, const String& payload, uint32_t id){
  DynamicJsonDocument p(128); deserializeJson(p, payload);
  if (cmd == "set_led")    digitalWrite(2, p["value"] | 0);
  if (cmd == "brightness") analogWrite(2, p["value"] | 0);
  // reboot / check_update → library ทำให้เอง
});
ฝั่งรับบนอุปกรณ์ (ESP32) — แบบ Raw HTTP (ไม่ใช้ library)

3 ขั้น: (1) long-poll รับคำสั่ง → (2) ทำงาน → (3) ack ผลกลับ · poll จะ block ~30 วินาที (ได้คำสั่งทันทีหรือคืน 204 เมื่อหมดเวลา) แล้ววนใหม่

// (1) long-poll รับคำสั่ง
HTTPClient http;
http.begin(String(OTA_SERVER) + "/api/device/poll");
http.addHeader("X-Device-ID",     DEVICE_ID);
http.addHeader("X-Device-Secret", DEVICE_SECRET);
http.setTimeout(35000);            // server hold ~30s
int code = http.GET();

if (code == 200) {                 // 204 = ไม่มีคำสั่ง
  DynamicJsonDocument doc(1024);
  deserializeJson(doc, http.getString());
  long id       = doc["command_id"];
  String cmd    = doc["command"].as<String>();
  JsonObject pl = doc["payload"];

  if (cmd == "set_led") digitalWrite(2, pl["value"] | 0);
  // ... จัดการคำสั่งอื่น ...

  ackCommand(id, "done", "ok");   // (3) ตอบผล
}
http.end();

// (3) ฟังก์ชัน ack
void ackCommand(long id, String status, String result) {
  HTTPClient h;
  h.begin(String(OTA_SERVER) + "/api/device/command-ack");
  h.addHeader("X-Device-ID",     DEVICE_ID);
  h.addHeader("X-Device-Secret", DEVICE_SECRET);
  h.addHeader("Content-Type",    "application/json");
  DynamicJsonDocument b(256);
  b["command_id"] = id;
  b["status"]     = status;   // "done" | "failed"
  b["result"]     = result;
  String out; serializeJson(b, out);
  h.POST(out); h.end();
}
ส่ง Telemetry ขึ้นการ์ด (ฝั่งอุปกรณ์ส่งขึ้น)

อุปกรณ์ส่งค่าใดๆ ใน data{} ขึ้นมา → ค่าจะโผล่เป็นการ์ดใน Console (กด + การ์ด เลือกคีย์) · ต้องแผน Starter+ (ไม่งั้นได้ 403) · ส่งถี่เกิน 60 ครั้ง/นาที จะโดน 429

POST /api/device/telemetry อุปกรณ์รายงานค่าขึ้น (Starter+)
HTTPClient http;
http.begin(String(OTA_SERVER) + "/api/device/telemetry");
http.addHeader("X-Device-ID",     DEVICE_ID);
http.addHeader("X-Device-Secret", DEVICE_SECRET);
http.addHeader("Content-Type",    "application/json");

DynamicJsonDocument doc(512);
// doc["lat"]=13.75; doc["lon"]=100.50;  // GPS (ถ้ามี)
JsonObject d = doc.createNestedObject("data");
d["heap_kb"]    = ESP.getFreeHeap() / 1024;
d["rssi"]       = WiFi.RSSI();
d["uptime_min"] = millis() / 60000;
d["ver"]        = "1.0.1";

String out; serializeJson(doc, out);
http.POST(out);   // 200 ok | 403 ต้อง Starter+ | 429 ถี่เกิน
http.end();
💡 Tip: วาง poll เป็นหัวใจของ loop() (มันรอ ~30s ในตัว) แล้วแทรกการดึง Device Config + ส่ง Telemetry เป็นช่วงๆ — ตัวอย่างเต็มใน loop เดียวดูได้ที่ไฟล์ examples/APIOTA_FullExample.ino

Error Codes

API ใช้ HTTP status codes มาตรฐาน response body มี error field อธิบายปัญหา

HTTP CodeErrorDescription
200OKสำเร็จ
400Bad Requestขาด parameter หรือ format ไม่ถูกต้อง
401Unauthorizeddevice_secret ไม่ถูกต้อง หรือ device_id ไม่พบ
403Forbiddenเกินขีดจำกัดของแผน หรืออุปกรณ์ถูกบล็อก
404Not Foundไม่พบ resource ที่ขอ
413Payload Too Largeไฟล์เฟิร์มแวร์ใหญ่เกิน limit ของแผน
429Rate Limitedส่ง request เร็วเกินไป รอ 60 วินาทีแล้วลองใหม่
500Server ErrorServer error ติดต่อ support
// ตัวอย่าง Error response
{
  "error": "DEVICE_NOT_FOUND",
  "message": "device_id not registered",
  "status": 401
}