1บทบาทและขอบเขตRole and scope#
PugEV รับข้อมูลอย่างเดียว ไม่ได้เป็นเจ้าของหรือเดินระบบสถานีPugEV only receives. We do not own or operate any charging station.
| เราใช้ | เราไม่ใช้ |
|---|---|
| versions · credentials · locations | sessions · cdrs · tokens |
| tariffs (ไม่บังคับ) | commands · chargingprofiles · hubclientinfo |
- อ่านอย่างเดียว ไม่มีการเขียนกลับเข้าระบบ CPO
- ไม่สั่งเริ่ม/หยุดชาร์จ ไม่จองหัว ไม่รับชำระเงิน
- ไม่ส่งข้อมูลที่ระบุตัวผู้ใช้ปลายทางให้ CPO
| We use | We do not use |
|---|---|
| versions · credentials · locations | sessions · cdrs · tokens |
| tariffs (optional) | commands · chargingprofiles · hubclientinfo |
- Read-only. Nothing is ever written back into the CPO’s system.
- No remote start/stop, no reservations, no payment.
- Nothing that identifies an end user is ever sent to the CPO.
2โครงสร้างข้อมูลThe data model#
สามชั้นซ้อนกัน จุดที่คนพลาดบ่อยคือสถานะอยู่ชั้นกลาง ไม่ใช่ชั้นล่างสุดThree nested levels. The thing people get wrong: status lives on the middle one.
Locationสถานี 1 แห่ง
idnameaddresscoordinatesopening_timeslast_updated
EVSE[]หัวชาร์จ 1 ตัว จอดได้ 1 คัน
uidevse_idstatuscapabilities
status อยู่ชั้นนี้
Connector[]หัวเสียบบนตัวเดียวกัน
idstandardpower_typemax_power_kwtariff_ids
ตู้ DC ที่มีทั้ง CCS2 และ CHAdeMO = 1 EVSE, 2 Connector เพราะเสียบได้ทีละหัว สถานะจึงมีค่าเดียว
Locationone site
idnameaddresscoordinatesopening_timeslast_updated
EVSE[]one charge point, one parking bay
uidevse_idstatuscapabilities
status lives here
Connector[]the plugs on that unit
idstandardpower_typemax_power_kwtariff_ids
A DC unit with both CCS2 and CHAdeMO is 1 EVSE and 2 connectors — only one can be in use at a time, so there is a single status.
3OCPI — เปิดสายOCPI — opening the link#
ไม่ใช่การขอ API key ฝ่ายเดียว — ทั้งสองฝั่งต้องมีเซิร์ฟเวอร์ และยืนยันตัวตนให้กันและกันNot a one-way API key — both sides run a server, and each authenticates to the other.
- นอกระบบCPO ส่ง TOKEN_A ให้เรา ใช้ลงทะเบียนได้ครั้งเดียว
- PugEV → CPOเราถามว่ารองรับเวอร์ชันไหน แล้วรับรายการ endpoint ทั้งหมดจากคำตอบ ไม่มี URL ไหน hardcode
GET /ocpi/versions Authorization: Token <TOKEN_A> 200 OK { "data": [ { "version": "2.2.1", "url": ".../ocpi/2.2.1" } ], "status_code": 1000 } - PugEV → CPOเราส่ง TOKEN_C ที่ออกให้ CPO พร้อม URL versions ของเรา
POST /ocpi/2.2.1/credentials Authorization: Token <TOKEN_A> { "token": "<TOKEN_C>", "url": "https://.../ocpi/versions", "roles": [ { "role": "EMSP", "country_code": "TH", "party_id": "..." } ] } - CPO → PugEVCPO เรียก versions ของเราด้วย TOKEN_C เพื่อยืนยันว่าปลายทางเปิดอยู่จริง — ขั้นนี้พังบ่อยที่สุด
- CPO → PugEVตอบ 200 พร้อม TOKEN_B แล้ว TOKEN_A หมดอายุทันที สายเปิดใช้งาน
| โทเคน | ใครออก | ใช้ทำอะไร |
|---|---|---|
| TOKEN_A | CPO | ตั๋วลงทะเบียนครั้งเดียว |
| TOKEN_B | CPO | เราใช้เรียกเข้าระบบ CPO |
| TOKEN_C | PugEV | CPO ใช้เรียกเข้าระบบเรา |
รองรับ 2.2.1 เป็นหลักและ 2.1.1 สำหรับระบบเดิม ส่วน 2.3.0 ยังไม่รองรับ แจ้งเราได้ถ้ามีเฉพาะเวอร์ชันนี้
- Out of bandThe CPO sends us TOKEN_A. Valid for one registration.
- PugEV → CPOWe ask which versions the CPO speaks and take the whole endpoint list from the answer. No URL is hardcoded.
GET /ocpi/versions Authorization: Token <TOKEN_A> 200 OK { "data": [ { "version": "2.2.1", "url": ".../ocpi/2.2.1" } ], "status_code": 1000 } - PugEV → CPOWe send TOKEN_C — the token we issue to the CPO — plus our own versions URL.
POST /ocpi/2.2.1/credentials Authorization: Token <TOKEN_A> { "token": "<TOKEN_C>", "url": "https://.../ocpi/versions", "roles": [ { "role": "EMSP", "country_code": "TH", "party_id": "..." } ] } - CPO → PugEVThe CPO calls our versions endpoint with TOKEN_C to confirm the far end is really live. This is the step that fails most often.
- CPO → PugEVReply 200 with TOKEN_B. TOKEN_A expires immediately and the link is live.
| Token | Issued by | Used for |
|---|---|---|
| TOKEN_A | CPO | Single-use registration ticket |
| TOKEN_B | CPO | We call into the CPO’s system |
| TOKEN_C | PugEV | The CPO calls into ours |
2.2.1 is the target and 2.1.1 is supported for older stacks. 2.3.0 is not supported yet — tell us if it is all the platform runs.
4OCPI — ข้อมูลไหลยังไงOCPI — how data moves#
push ให้ความเร็ว pull คือประกันว่าข้อมูลไม่ค้างเงียบ ๆPush gives speed; pull is the insurance that nothing goes quietly stale.
| เมื่อไร | ใครเริ่ม | ทำอะไร |
|---|---|---|
| ตอนเปิดสาย | เราดึง | GET locations ทั้งหมด แบ่งหน้า |
| ระหว่างวัน | CPO ส่ง | PATCH เฉพาะฟิลด์ที่เปลี่ยน ส่วนใหญ่คือ status |
| ทุกคืน | เราดึง | GET ด้วย date_from เมื่อวาน เพื่อสอบทาน |
GET /ocpi/2.2.1/locations?date_from=2026-09-04T00:00:00Z&offset=0&limit=100
Authorization: Token <TOKEN_B>
200 OK
X-Total-Count: 412
Link: <.../locations?offset=100&limit=100>; rel="next"PATCH /ocpi/2.2.1/locations/TH/EXA/LOC0142/EVSE0001
Authorization: Token <TOKEN_C>
{
"status": "CHARGING",
"last_updated": "2026-09-05T09:14:22Z"
}| When | Who starts | What happens |
|---|---|---|
| At connection | We pull | GET all locations, paginated |
| Through the day | CPO pushes | PATCH only what changed, mostly status |
| Nightly | We pull | GET with yesterday’s date_from, to reconcile |
GET /ocpi/2.2.1/locations?date_from=2026-09-04T00:00:00Z&offset=0&limit=100
Authorization: Token <TOKEN_B>
200 OK
X-Total-Count: 412
Link: <.../locations?offset=100&limit=100>; rel="next"PATCH /ocpi/2.2.1/locations/TH/EXA/LOC0142/EVSE0001
Authorization: Token <TOKEN_C>
{
"status": "CHARGING",
"last_updated": "2026-09-05T09:14:22Z"
}5สถานะ 9 ค่าThe nine statuses#
ส่งค่าตาม OCPI มา เราแปลงเป็นสิ่งที่ผู้ใช้เห็นตามตารางนี้Send the OCPI value; this is what a driver ends up seeing.
| ค่าที่ CPO ส่ง | ผู้ใช้เห็น |
|---|---|
| AVAILABLE | ว่าง |
| CHARGING · RESERVED | ใช้งานอยู่ |
| BLOCKED · OUTOFORDER | ใช้ไม่ได้ |
| INOPERATIVE | ปิดปรับปรุง |
| PLANNED · REMOVED | ไม่แสดงบนแผนที่ |
| UNKNOWN | ไม่มีข้อมูล |
| What the CPO sends | What the driver sees |
|---|---|
| AVAILABLE | Free |
| CHARGING · RESERVED | In use |
| BLOCKED · OUTOFORDER | Unavailable |
| INOPERATIVE | Under maintenance |
| PLANNED · REMOVED | Not shown on the map |
| UNKNOWN | No data |
6ข้อกำหนดที่เหลืออีกสามข้อThree more rules#
| ข้อกำหนด | ทำไม |
|---|---|
| รหัสสถานีและ EVSE ห้ามเปลี่ยน | ผู้ใช้บันทึกสถานีโปรดและประวัติการชาร์จไว้ด้วยรหัสนี้ |
| สถานะที่ไม่อัปเดตเกินรอบที่ตกลง จะถูกแสดงเป็น “ไม่มีข้อมูล” | ค่าที่ค้างไว้อันตรายกว่าการไม่มีค่า |
| เวลาเปิดปิดใช้เขตเวลา Asia/Bangkok · ราคารวม VAT | ให้ตัวเลขบนแอปตรงกับที่หน้างาน |
| Rule | Why |
|---|---|
| Site and EVSE identifiers never change | Drivers save favourites and charging history against them |
| A status not refreshed within the agreed interval shows as “no data” | A stale value is more dangerous than no value |
| Opening hours in Asia/Bangkok · prices include VAT | So the numbers in the app match the numbers on site |
7ความปลอดภัยSecurity#
- HTTPS TLS 1.2 ขึ้นไป ใบรับรองต้องตรวจสอบได้ เราไม่ปิดการตรวจไม่ว่ากรณีใด
- โทเคนเก็บใน secret store แยกจากฐานข้อมูล หมุนเวียนได้ แจ้งล่วงหน้า 7 วัน
- ต้องการ IP allowlist แจ้งได้ เรามีชุด IP ขาออกคงที่
- เราจำกัดอัตราการเรียกฝั่งเราเอง และเคารพ 429 พร้อม Retry-After ของ CPO
- ไม่มีข้อมูลผู้ใช้ปลายทางไหลไปหา CPO และเราไม่รับข้อมูลลูกค้าของ CPO
โทเคนรั่วหรือเห็นทราฟฟิกผิดปกติ แจ้ง [email protected] เราเพิกถอนภายใน 24 ชั่วโมงและออกชุดใหม่ให้
- HTTPS, TLS 1.2 or later, verifiable certificate. We never disable validation, for any reason.
- Tokens live in a secret store separate from the database and rotate on the CPO’s schedule with 7 days’ notice.
- Want an IP allowlist? Ask — we have fixed egress addresses.
- We rate-limit ourselves outbound and honour the CPO’s 429 and Retry-After.
- No end-user data reaches the CPO, and we neither ask for nor accept its customer data.
If a token is exposed or unexpected traffic shows up, write to [email protected]. We revoke within 24 hours and issue replacements.