EV

การพัฒนา OCPI 2.2.1: คู่มือสำหรับนักพัฒนาในการทำ Locations, Sessions และ CDR

หากคุณสร้างระบบ CSMS ที่ใช้ OCPP อยู่แล้ว และตอนนี้ต้องเปิดระบบให้พาร์ทเนอร์ roaming เข้าถึงได้ OCPI คือโจทย์ integration ที่ต่างออกไปโดยสิ้นเชิง OCPP คือการเชื่อมต่อ WebSocket แบบต่อเนื่องเส้นเดียวกับเครื่องชาร์จที่คุณควบคุมเอง ส่วน OCPI คือชุด REST module ที่มีเวอร์ชันของตัวเอง แลกเปลี่ยนกับแพลตฟอร์มพาร์ทเนอร์ที่คุณไม่ได้ควบคุม ภายใต้สัญญา และข้อมูลที่ต้องกระทบยอดกับเงินจริง

บทความนี้พาไล่ดูสิ่งที่ต้องสร้างจริง: การลงทะเบียนและแลกเปลี่ยน credentials และสาม module ที่สำคัญที่สุดในระบบจริง — Locations, Sessions และ CDR


โครงสร้างของ OCPI integration

ทุกฝ่ายใน OCPI (CPO หรือ eMSP) จะเปิด versions endpoint และรองรับ module บางส่วน โดยแต่ละ module มีเวอร์ชันของตัวเอง สำหรับ CPO ที่เปิดข้อมูลโครงสร้างพื้นฐานให้ eMSP โมดูลหลักคือ:

  • credentials — การลงทะเบียนและแลกเปลี่ยน token
  • locations — ข้อมูลเครื่องชาร์จและหัวชาร์จ (CPO → eMSP, push หรือ pull)
  • sessions — สถานะการชาร์จแบบเรียลไทม์ (CPO → eMSP)
  • cdrs — บันทึกการเรียกเก็บเงินฉบับสุดท้าย (CPO → eMSP)
  • tariffs — ข้อมูลราคา (CPO → eMSP)
  • commands — คำสั่ง remote start/stop (eMSP → CPO)

ไม่ใช่ทุกฝ่ายต้องรองรับทุก module eMSP ล้วน ๆ จะไม่ implement locations ในฐานะผู้ส่ง ส่วน CPO ล้วน ๆ ก็จะไม่ implement commands ในฐานะผู้รับคำสั่งที่มาจากแอปของตัวเอง


ขั้นตอนที่ 1: Versions และ Credentials

ก่อนข้อมูลจริงจะไหลได้ ทั้งสองฝ่ายต้องลงทะเบียนกันผ่าน module credentials

GET https://your-csms.example.com/ocpi/versions
{
  "status_code": 1000,
  "data": [
    { "version": "2.2.1", "url": "https://your-csms.example.com/ocpi/2.2.1/details" }
  ]
}

พาร์ทเนอร์จะดึง /2.2.1/details เพื่อดูรายการ module และ endpoint ที่คุณรองรับ จากนั้นลงทะเบียน token:

POST /ocpi/2.2.1/credentials
Authorization: Token CREDENTIALS_TOKEN_A

{
  "token": "CREDENTIALS_TOKEN_B",
  "url": "https://your-csms.example.com/ocpi/versions",
  "roles": [
    { "role": "CPO", "party_id": "SIM", "country_code": "TH",
      "business_details": { "name": "Simplico EV Network" } }
  ]
}

จุดที่ทีมส่วนใหญ่มองข้าม: การยืนยันตัวตนของ OCPI เป็นแบบ ต่อความสัมพันธ์ ไม่ใช่ API key ตัวเดียวใช้ทุกที่ ในระบบจริงคุณต้องเก็บและหมุนเวียนคู่ token แยกต่างหากสำหรับพาร์ทเนอร์แต่ละราย พร้อมติดตามว่าแต่ละพาร์ทเนอร์รองรับ module เวอร์ชันไหนบ้าง นี่คือ state ที่ CSMS ต้องมี data model จริงจังรองรับ ไม่ใช่แค่ config file


ขั้นตอนที่ 2: Locations — โครงสร้าง ไม่ใช่แค่เนื้อหา

Location ไม่ใช่ record แบบแบนราบเดียว แต่ซ้อนกันเป็นชั้น:

Location
 └─ EVSEs (เครื่องชาร์จจริง)
     └─ Connectors (ประเภทหัวชาร์จ กำลังไฟ สถานะ)
{
  "id": "LOC001",
  "party_id": "SIM",
  "country_code": "TH",
  "name": "Simplico Demo Site",
  "address": "123 Sukhumvit Rd",
  "city": "Bangkok",
  "coordinates": { "latitude": "13.736717", "longitude": "100.523186" },
  "evses": [
    {
      "uid": "EVSE001",
      "evse_id": "TH*SIM*E001",
      "status": "AVAILABLE",
      "connectors": [
        { "id": "1", "standard": "IEC_62196_T2", "format": "SOCKET",
          "power_type": "AC_3_PHASE", "max_voltage": 400, "max_amperage": 32 }
      ]
    }
  ],
  "last_updated": "2026-08-07T09:00:00Z"
}

รายละเอียดสองอย่างที่ทำให้เกิดบั๊กมากที่สุดในส่วนนี้:

  • last_updated เป็นตัวขับเคลื่อนทุกอย่าง พาร์ทเนอร์มักดึงเฉพาะ record ที่เปลี่ยนตั้งแต่ครั้ง sync ล่าสุด (?date_from=) หาก backend ของคุณไม่อัปเดต timestamp นี้ทุกครั้งที่มีการเปลี่ยนแปลงที่เกี่ยวข้อง รวมถึงสถานะ EVSE/connector ที่ซ้อนอยู่ พาร์ทเนอร์จะใช้ข้อมูลความพร้อมใช้งานที่เก่าโดยไม่รู้ตัว
  • Push กับ pull ต้องตกลงกันเป็นรายพาร์ทเนอร์ ไม่ได้ถูกกำหนดตายตัวโดยสเปก eMSP บางรายต้องการให้คุณ PUT อัปเดตไปยัง endpoint locations ของเขาทุกครั้งที่สถานะเปลี่ยน บางรายต้องการ poll จากฝั่งคุณเอง CSMS ของคุณต้องรองรับทั้งสองทิศทางตั้งแต่วันแรก เพราะคุณไม่สามารถกำหนดได้ว่าพาร์ทเนอร์แต่ละรายจะเลือกโมเดลไหน

ขั้นตอนที่ 3: Sessions — ส่วนที่ต้องเรียลไทม์จริง ๆ

object Session ติดตามการชาร์จตั้งแต่การอนุมัติสิทธิ์จนถึงเสร็จสิ้น field ที่มักทำให้พลาดคือ status ซึ่งต้องสะท้อนสถานะจริงภายในไม่กี่วินาที ไม่ใช่หลายนาที เพราะแอปของผู้ขับฝั่ง eMSP กำลังแสดงข้อมูลนี้แบบเรียลไทม์

{
  "id": "SESS001",
  "location_id": "LOC001",
  "evse_uid": "EVSE001",
  "connector_id": "1",
  "start_date_time": "2026-08-07T14:02:00Z",
  "kwh": 12.4,
  "auth_method": "WHITELIST",
  "status": "ACTIVE",
  "last_updated": "2026-08-07T14:15:00Z"
}

ในทางปฏิบัติ หมายความว่า module sessions ฝั่ง OCPI ของคุณไม่สามารถเป็นแค่ batch job ที่รันเป็นรอบ ๆ อยู่บนตาราง session ของ OCPP เท่านั้น แต่ต้อง subscribe กับ event เดียวกันที่ชั้น OCPP ส่งออกมา (StartTransaction, MeterValues, StopTransaction) แล้ว push หรืออัปเดต session object ฝั่ง OCPI ในรอบ request เดียวกัน หากสถานะ session ของ OCPP กับ OCPI เพี้ยนไม่ตรงกัน นั่นคือสิ่งแรกที่พาร์ทเนอร์ roaming จะสังเกตเห็นและร้องเรียน


ขั้นตอนที่ 4: CDR — จุดที่การเรียกเก็บเงินเกิดขึ้นจริง

CDR (Charge Detail Record) คือ object ที่ทุกอย่างก่อนหน้ามีไว้เพื่อสร้างให้ถูกต้อง ส่งครั้งเดียวหลังเซสชันจบ และเป็น source of truth สำหรับการชำระเงิน

{
  "id": "CDR001",
  "session_id": "SESS001",
  "start_date_time": "2026-08-07T14:02:00Z",
  "end_date_time": "2026-08-07T14:48:00Z",
  "cdr_token": { "uid": "TOKEN123", "type": "RFID", "contract_id": "TH-SIM-0001" },
  "total_energy": 12.4,
  "total_time": 0.77,
  "total_cost": { "excl_vat": 62.00, "incl_vat": 66.34 },
  "charging_periods": [
    { "start_date_time": "2026-08-07T14:02:00Z",
      "dimensions": [ { "type": "ENERGY", "volume": 12.4 } ] }
  ]
}

สิ่งที่ควรออกแบบไว้ล่วงหน้า ไม่ใช่มาเจอเอาในระบบจริง:

  • CDR แก้ไขไม่ได้เมื่อส่งไปแล้ว หากการคำนวณ tariff มีบั๊ก คุณไม่สามารถแก้ CDR เดิมได้ ต้องออก CDR ใหม่หรือจัดการผ่านกระบวนการข้อพิพาทของพาร์ทเนอร์ ออกแบบ pipeline การเรียกเก็บเงินโดยตั้งสมมติฐานว่าการแก้ไขคือ exception ไม่ใช่เส้นทางปกติ
  • total_cost ต้องตรงกับสิ่งที่ module tariffs ที่คุณเผยแพร่บอกไว้ หากราคาใน tariffs ระบุอย่างหนึ่ง แต่ CDR คำนวณอีกอย่าง ความไม่ตรงกันนี้คือสิ่งที่ทำให้เกิด support ticket จริง มักจะเกิดขึ้นหลายสัปดาห์ให้หลัง ในรายงานกระทบยอดที่ไม่มีฝ่ายไหนอยากอ่าน
  • Idempotency สำคัญในระดับ transport การ retry ของ network บน request POST ของ CDR เกิดขึ้นบ่อย ให้ใช้ id เป็น dedup key เพื่อไม่ให้ request ที่ retry สร้าง record การเรียกเก็บเงินซ้ำซ้อนฝั่งพาร์ทเนอร์

ทดสอบก่อนเชื่อมกับพาร์ทเนอร์จริง

สองขั้นตอนที่ควรทำก่อนเชื่อมกับพาร์ทเนอร์ roaming รายแรก:

  1. ทดสอบกับ validator ของ OCA/EVRoaming หรือพาร์ทเนอร์ sandbox ก่อนคู่ค้ารายแรกในระบบจริง เพราะ error เรื่อง module version ไม่ตรงกันหรือ field validation หาเจอในสภาพแวดล้อมนี้ถูกกว่ามาก
  2. จำลอง lifecycle เต็มรูปแบบตั้งแต่ต้นจนจบ: ลงทะเบียน credentials, push location, เปิด session, ส่ง status update ต่อเนื่อง, ปิด session และสร้าง CDR ก่อนเชื่อมพาร์ทเนอร์รายที่สอง บั๊ก integration ส่วนใหญ่อยู่ที่รอยต่อระหว่าง module ไม่ใช่ภายใน module ใดตัวหนึ่ง

ตำแหน่งของ OCPI เทียบกับ OCPP

หากคุณสร้าง (หรือกำลังใช้งาน) CSMS ที่ใช้ OCPP อยู่แล้ว ชั้น OCPI จะอยู่ คู่ขนาน ไม่ใช่ ซ้อนอยู่ข้างใน OCPP ดูแล state machine ฝั่งเครื่องชาร์จ ส่วน OCPI แปลงส่วนหนึ่งของ state นั้นให้เป็น interface ที่มีเวอร์ชันและกำหนดไว้ในสัญญาสำหรับพาร์ทเนอร์ ให้มองทั้งสองเป็น backend สองตัวที่แชร์ data model ร่วมกัน ไม่ใช่ backend เดียวที่ต่อ endpoint เสริมเข้าไป นี่คือความต่างระหว่าง OCPI integration ที่รอดจนถึงพาร์ทเนอร์รายที่สาม กับตัวที่ต้องเขียนใหม่ทั้งหมดเพื่อรองรับพาร์ทเนอร์รายนั้น


คำถามที่พบบ่อย

ต้องแยกฐานข้อมูลสำหรับ OCPI ไหม หรือขยายจากตาราง session ของ OCPP ได้เลย?
ขยายจากของเดิมได้ตราบใดที่ field เฉพาะของ OCPI (party_id, country_code, CDR token, tariff reference) ไม่ได้ถูกเติมเข้าไปแบบไม่ได้วางแผน ให้ออกแบบ Locations, Sessions และ CDR เป็น entity ของตัวเองที่อ้างอิงข้อมูล OCPP ไม่ใช่กลับกัน

การเชื่อมต่อแบบ bilateral รองรับพาร์ทเนอร์ได้กี่รายก่อนต้องเปลี่ยนไปใช้ Hub?
ไม่มีตัวเลขตายตัว แต่ทีมส่วนใหญ่เริ่มรู้สึกลำบากที่ประมาณ 4-6 พาร์ทเนอร์แบบ bilateral เมื่อการจัดการ token ความชอบ push/pull ของแต่ละราย และการรองรับ module version ที่ต่างกันเริ่มต้องการการจัดการอย่างเป็นระบบมากกว่าการแก้ปัญหาเฉพาะหน้า

อะไรที่พังบ่อยที่สุดใน OCPI integration ระบบจริง?
เรียงตามลำดับ: การจัดการ timestamp (last_updated ไม่กระจายมาจาก object ที่ซ้อนอยู่ข้างใน), ความไม่ตรงกันระหว่าง CDR กับ tariff และความหน่วงของสถานะ session ระหว่างชั้น OCPP กับ OCPI


หากกำลังวางสโคปการสร้าง OCPI บน backend ที่ใช้ OCPP อยู่แล้ว หรือจะสร้างทั้งสองระบบตั้งแต่ต้น นี่คืองานที่บริการ EV CSMS — Charging Station Management System (OCPP/OCPI) ของเราออกแบบมารองรับโดยตรง สำหรับการพูดคุยเชิงเทคนิคเพื่อวางสโคป ติดต่อได้ที่ hello@simplico.net