Skip to content

Instantly share code, notes, and snippets.

@rohittiwari-dev
Last active November 14, 2025 02:13
Show Gist options
  • Select an option

  • Save rohittiwari-dev/1bed980b1ca21e5a0a09c20bdfd7f9fa to your computer and use it in GitHub Desktop.

Select an option

Save rohittiwari-dev/1bed980b1ca21e5a0a09c20bdfd7f9fa to your computer and use it in GitHub Desktop.
Complete OCPP 1.6 WebSocket Communication Developer Guide

⚑ Complete OCPP 1.6 WebSocket Communication Guide

πŸš— Your Complete Guide to OCPP 1.6 WebSocket Implementation
Master every event, every key, and every detail for perfect EV charging communication


πŸ—οΈ Architecture Overview

🌐 OCPP Server (Central System)
     ↕️ WebSocket Connection
πŸ”Œ Charge Point (EV Charger)

WebSocket URL Format:

ws://server:port/ocpp/CP001
wss://server:port/ocpp/CP001 (Secure)

πŸ“‹ Table of Contents

πŸ”§ Core Protocol

πŸ“Š Charge Point Initiated Events

🎯 Central System Initiated Events

πŸ’» Implementation & Examples

🚨 Error Handling & Operations

πŸ“‹ Compliance & Deployment

🎯 Summary & Resources


πŸ”§ WebSocket Message Format

πŸ“‘ Message Structure

All OCPP messages follow this JSON array format:

[MessageType, MessageId, Action, Payload]

Message Types:

  • 2 = CALL (Request)
  • 3 = CALLRESULT (Response)
  • 4 = CALLERROR (Error)

🎯 Example Message Flow

sequenceDiagram
    participant CP as Charge Point
    participant CS as Central System

    CP->>CS: [2, "msg_001", "Heartbeat", {}]
    CS->>CP: [3, "msg_001", {"currentTime": "2024-01-01T12:00:00.000Z"}]
Loading

πŸ›‘οΈ Security Profiles

πŸ”“ Security Profile 0 (No Security)

  • Plain WebSocket connection
  • No authentication required
  • ⚠️ Not recommended for production

πŸ”’ Security Profile 1 (Basic Authentication)

  • HTTP Basic Authentication
  • Username/Password over HTTPS
  • TLS 1.2+ required

πŸ” Security Profile 2 (Client Certificate)

  • Mutual TLS (mTLS)
  • Client certificate authentication
  • βœ… Recommended for production

πŸ›‘οΈ Security Profile 3 (Advanced)

  • Client certificate + HTTP Basic Auth
  • Enhanced security logging
  • πŸ† Maximum security

πŸ“Š Charge Point Initiated Events

πŸ”Œ Events sent from the Charge Point to the Central System

πŸ’“ Heartbeat

Purpose: Keep connection alive and sync time

πŸ“€ Request:

[2, "heartbeat_001", "Heartbeat", {}]

πŸ“₯ Response:

[
	3,
	"heartbeat_001",
	{
		"currentTime": "2024-01-01T12:00:00.000Z"
	}
]

πŸ”‘ Key Fields:

  • currentTime (DateTime): Server's current time in ISO 8601 format (Required - Response)

πŸ”„ Frequency: Every 30-300 seconds (configurable)


🏁 BootNotification

Purpose: Charge point registration and capability announcement

πŸ“€ Request:

[
	2,
	"boot_001",
	"BootNotification",
	{
		"chargePointVendor": "Tesla",
		"chargePointModel": "Supercharger V3",
		"chargePointSerialNumber": "SC001234",
		"chargeBoxSerialNumber": "CB001234",
		"firmwareVersion": "1.8.2",
		"iccid": "8944501234567890123",
		"imsi": "234567890123456",
		"meterType": "Carlo Gavazzi EM340",
		"meterSerialNumber": "EM340001"
	}
]

πŸ“₯ Response:

[
	3,
	"boot_001",
	{
		"currentTime": "2024-01-01T12:00:00.000Z",
		"interval": 300,
		"status": "Accepted"
	}
]

πŸ”‘ Key Fields:

  • chargePointVendor (String, Max 20): Vendor name (Required)
  • chargePointModel (String, Max 20): Model name (Required)
  • chargePointSerialNumber (String, Max 25): Serial number (optional)
  • chargeBoxSerialNumber (String, Max 25): Charge box serial (optional)
  • firmwareVersion (String, Max 50): Firmware version (optional)
  • iccid (String, Max 20): SIM card identifier (optional)
  • imsi (String, Max 20): Mobile subscriber identity (optional)
  • meterType (String, Max 25): Energy meter type (optional)
  • meterSerialNumber (String, Max 25): Meter serial number (optional)
  • currentTime (DateTime): Server time (Required)
  • interval (Integer): Heartbeat interval in seconds (Required)
  • status (Enum): "Accepted", "Pending", "Rejected" (Required)

🏁 BootNotification - Response Scenarios

Example 1: Pending Registration:

[
	3,
	"boot_001",
	{
		"currentTime": "2024-01-01T12:00:00.000Z",
		"interval": 300,
		"status": "Pending"
	}
]

Example 2: Rejected Registration:

[
	3,
	"boot_002",
	{
		"currentTime": "2024-01-01T12:00:00.000Z",
		"interval": 86400,
		"status": "Rejected"
	}
]

Note: When status is "Pending", the charge point should retry BootNotification periodically. When "Rejected", the charge point should not attempt normal operations.


πŸ”‹ StatusNotification

Purpose: Report connector status changes

πŸ“€ Request:

[
	2,
	"status_001",
	"StatusNotification",
	{
		"connectorId": 1,
		"status": "Occupied",
		"errorCode": "NoError",
		"info": "EV connected, charging in progress",
		"timestamp": "2024-01-01T12:00:00.000Z",
		"vendorId": "Tesla",
		"vendorErrorCode": "T001"
	}
]

πŸ“₯ Response:

[3, "status_001", {}]

πŸ”‘ Key Fields:

  • connectorId (Integer): Connector ID (0 = charge point) (Required)
  • status (Enum): "Available", "Preparing", "Charging", "SuspendedEVSE", "SuspendedEV", "Finishing", "Reserved", "Unavailable", "Faulted" (Required)
  • errorCode (Enum): "ConnectorLockFailure", "EVCommunicationError", "GroundFailure", "HighTemperature", "InternalError", "LocalListConflict", "NoError", "OtherError", "OverCurrentFailure", "PowerMeterFailure", "PowerSwitchFailure", "ReaderFailure", "ResetFailure", "UnderVoltage", "OverVoltage", "WeakSignal" (Required)
  • info (String, Max 50): Additional information (optional)
  • timestamp (DateTime): Status change timestamp (optional)
  • vendorId (String, Max 255): Vendor identifier (optional)
  • vendorErrorCode (String, Max 50): Vendor-specific error code (optional)

πŸ”‹ StatusNotification - Error Scenarios

Example 1: Fault Condition:

[
	2,
	"status_fault_001",
	"StatusNotification",
	{
		"connectorId": 1,
		"status": "Faulted",
		"errorCode": "GroundFailure",
		"info": "Ground fault detected on connector 1",
		"timestamp": "2024-01-01T12:05:00.000Z",
		"vendorId": "EVSE-Manufacturer",
		"vendorErrorCode": "GF001"
	}
]

Example 2: Connector Unavailable:

[
	2,
	"status_unavail_001",
	"StatusNotification",
	{
		"connectorId": 2,
		"status": "Unavailable",
		"errorCode": "PowerSwitchFailure",
		"info": "Contactor failure detected",
		"timestamp": "2024-01-01T12:10:00.000Z"
	}
]

Example 3: Charge Point Status (connectorId = 0):

[
	2,
	"status_cp_001",
	"StatusNotification",
	{
		"connectorId": 0,
		"status": "Available",
		"errorCode": "NoError",
		"timestamp": "2024-01-01T12:00:00.000Z"
	}
]

πŸš— StartTransaction

Purpose: Start a charging session

πŸ“€ Request:

[
	2,
	"start_001",
	"StartTransaction",
	{
		"connectorId": 1,
		"idTag": "RFID12345678",
		"meterStart": 1000,
		"timestamp": "2024-01-01T12:00:00.000Z",
		"reservationId": 123
	}
]

πŸ“₯ Response:

[
	3,
	"start_001",
	{
		"transactionId": 98765,
		"idTagInfo": {
			"status": "Accepted",
			"expiryDate": "2024-12-31T23:59:59.000Z",
			"parentIdTag": "MASTER001"
		}
	}
]

πŸ”‘ Key Fields:

  • connectorId (Integer): Connector identifier (Required)
  • idTag (String, Max 20): Authorization identifier (Required)
  • meterStart (Integer): Energy meter value at start (Wh) (Required)
  • timestamp (DateTime): Transaction start time (Required)
  • reservationId (Integer): Reservation ID (optional)
  • transactionId (Integer): Unique transaction identifier (Required - Response)
  • idTagInfo (Object): Authorization information (Required - Response)
    • status (Enum): "Accepted", "Blocked", "Expired", "Invalid", "ConcurrentTx" (Required)
    • expiryDate (DateTime): Tag expiry date (optional)
    • parentIdTag (String, Max 20): Parent tag identifier (optional)

🏁 StopTransaction

Purpose: End a charging session

πŸ“€ Request:

[
	2,
	"stop_001",
	"StopTransaction",
	{
		"transactionId": 98765,
		"idTag": "RFID12345678",
		"meterStop": 15000,
		"timestamp": "2024-01-01T14:30:00.000Z",
		"reason": "EVDisconnected",
		"transactionData": [
			{
				"timestamp": "2024-01-01T12:30:00.000Z",
				"sampledValue": [
					{
						"value": "5000",
						"context": "Transaction.Begin",
						"format": "Raw",
						"measurand": "Energy.Active.Import.Register",
						"location": "Outlet",
						"unit": "Wh"
					}
				]
			},
			{
				"timestamp": "2024-01-01T13:00:00.000Z",
				"sampledValue": [
					{
						"value": "10000",
						"context": "Sample.Periodic",
						"format": "Raw",
						"measurand": "Energy.Active.Import.Register",
						"location": "Outlet",
						"unit": "Wh"
					},
					{
						"value": "22.5",
						"context": "Sample.Periodic",
						"format": "Raw",
						"measurand": "Power.Active.Import",
						"location": "Outlet",
						"unit": "kW"
					},
					{
						"value": "45",
						"context": "Sample.Periodic",
						"format": "Raw",
						"measurand": "SoC",
						"location": "EV",
						"unit": "Percent"
					}
				]
			},
			{
				"timestamp": "2024-01-01T14:30:00.000Z",
				"sampledValue": [
					{
						"value": "15000",
						"context": "Transaction.End",
						"format": "Raw",
						"measurand": "Energy.Active.Import.Register",
						"location": "Outlet",
						"unit": "Wh"
					},
					{
						"value": "80",
						"context": "Transaction.End",
						"format": "Raw",
						"measurand": "SoC",
						"location": "EV",
						"unit": "Percent"
					}
				]
			}
		]
	}
]

πŸ“₯ Response:

[
	3,
	"stop_001",
	{
		"idTagInfo": {
			"status": "Accepted",
			"expiryDate": "2024-12-31T23:59:59.000Z"
		}
	}
]

πŸ”‘ Key Fields:

  • transactionId (Integer): Transaction to stop (Required)
  • idTag (String, Max 20): Authorization identifier (optional)
  • meterStop (Integer): Energy meter value at stop (Wh) (Required)
  • timestamp (DateTime): Transaction stop time (Required)
  • reason (Enum): "EmergencyStop", "EVDisconnected", "HardReset", "Local", "Other", "PowerLoss", "Reboot", "Remote", "SoftReset", "UnlockCommand", "DeAuthorized" (optional)
  • transactionData (Array): Historical meter values (optional)
    • timestamp (DateTime): Sample timestamp (Required)
    • sampledValue (Array): Sampled values (Required)
      • value (String): Measured value (Required)
      • context (Enum): "Interruption.Begin", "Interruption.End", "Sample.Clock", "Sample.Periodic", "Transaction.Begin", "Transaction.End", "Trigger", "Other" (optional)
      • format (Enum): "Raw", "SignedData" (optional)
      • measurand (Enum): "Energy.Active.Export.Register", "Energy.Active.Import.Register", "Energy.Reactive.Export.Register", "Energy.Reactive.Import.Register", "Energy.Active.Export.Interval", "Energy.Active.Import.Interval", "Energy.Reactive.Export.Interval", "Energy.Reactive.Import.Interval", "Power.Active.Export", "Power.Active.Import", "Power.Offered", "Power.Reactive.Export", "Power.Reactive.Import", "Power.Factor", "Current.Import", "Current.Export", "Current.Offered", "Voltage", "Frequency", "Temperature", "SoC", "RPM" (optional)
      • phase (Enum): "L1", "L2", "L3", "N", "L1-N", "L2-N", "L3-N", "L1-L2", "L2-L3", "L3-L1" (optional)
      • location (Enum): "Body", "Cable", "EV", "Inlet", "Outlet" (optional)
      • unit (Enum): "Wh", "kWh", "varh", "kvarh", "W", "kW", "VA", "kVA", "var", "kvar", "A", "V", "Celsius", "Fahrenheit", "K", "Percent" (optional)
  • idTagInfo (Object): Authorization information (optional - Response)

πŸ“Š MeterValues

Purpose: Send meter readings during charging

πŸ“€ Request:

[
	2,
	"meter_001",
	"MeterValues",
	{
		"connectorId": 1,
		"transactionId": 98765,
		"meterValue": [
			{
				"timestamp": "2024-01-01T12:30:00.000Z",
				"sampledValue": [
					{
						"value": "7500",
						"context": "Sample.Periodic",
						"format": "Raw",
						"measurand": "Energy.Active.Import.Register",
						"location": "Outlet",
						"unit": "Wh"
					},
					{
						"value": "150",
						"context": "Sample.Periodic",
						"format": "Raw",
						"measurand": "Energy.Reactive.Import.Register",
						"location": "Outlet",
						"unit": "varh"
					},
					{
						"value": "22.5",
						"context": "Sample.Periodic",
						"format": "Raw",
						"measurand": "Power.Active.Import",
						"location": "Outlet",
						"unit": "kW"
					},
					{
						"value": "2.1",
						"context": "Sample.Periodic",
						"format": "Raw",
						"measurand": "Power.Reactive.Import",
						"location": "Outlet",
						"unit": "kvar"
					},
					{
						"value": "0.95",
						"context": "Sample.Periodic",
						"format": "Raw",
						"measurand": "Power.Factor",
						"location": "Outlet"
					},
					{
						"value": "32.0",
						"context": "Sample.Periodic",
						"format": "Raw",
						"measurand": "Current.Import",
						"phase": "L1",
						"location": "Outlet",
						"unit": "A"
					},
					{
						"value": "31.8",
						"context": "Sample.Periodic",
						"format": "Raw",
						"measurand": "Current.Import",
						"phase": "L2",
						"location": "Outlet",
						"unit": "A"
					},
					{
						"value": "32.2",
						"context": "Sample.Periodic",
						"format": "Raw",
						"measurand": "Current.Import",
						"phase": "L3",
						"location": "Outlet",
						"unit": "A"
					},
					{
						"value": "230.5",
						"context": "Sample.Periodic",
						"format": "Raw",
						"measurand": "Voltage",
						"phase": "L1-N",
						"location": "Outlet",
						"unit": "V"
					},
					{
						"value": "229.8",
						"context": "Sample.Periodic",
						"format": "Raw",
						"measurand": "Voltage",
						"phase": "L2-N",
						"location": "Outlet",
						"unit": "V"
					},
					{
						"value": "231.2",
						"context": "Sample.Periodic",
						"format": "Raw",
						"measurand": "Voltage",
						"phase": "L3-N",
						"location": "Outlet",
						"unit": "V"
					},
					{
						"value": "50.0",
						"context": "Sample.Periodic",
						"format": "Raw",
						"measurand": "Frequency",
						"location": "Outlet",
						"unit": "Hz"
					},
					{
						"value": "35.2",
						"context": "Sample.Periodic",
						"format": "Raw",
						"measurand": "Temperature",
						"location": "Body",
						"unit": "Celsius"
					},
					{
						"value": "65",
						"context": "Sample.Periodic",
						"format": "Raw",
						"measurand": "SoC",
						"location": "EV",
						"unit": "Percent"
					},
					{
						"value": "1250",
						"context": "Sample.Periodic",
						"format": "Raw",
						"measurand": "Energy.Active.Import.Interval",
						"location": "Outlet",
						"unit": "Wh"
					},
					{
						"value": "22000",
						"context": "Sample.Periodic",
						"format": "Raw",
						"measurand": "Power.Offered",
						"location": "Outlet",
						"unit": "W"
					}
				]
			},
			{
				"timestamp": "2024-01-01T12:31:00.000Z",
				"sampledValue": [
					{
						"value": "8750",
						"context": "Sample.Periodic",
						"format": "Raw",
						"measurand": "Energy.Active.Import.Register",
						"location": "Outlet",
						"unit": "Wh"
					},
					{
						"value": "22.8",
						"context": "Sample.Periodic",
						"format": "Raw",
						"measurand": "Power.Active.Import",
						"location": "Outlet",
						"unit": "kW"
					},
					{
						"value": "68",
						"context": "Sample.Periodic",
						"format": "Raw",
						"measurand": "SoC",
						"location": "EV",
						"unit": "Percent"
					}
				]
			}
		]
	}
]

πŸ“₯ Response:

[3, "meter_001", {}]

πŸ”‘ Key Fields:

  • connectorId (Integer): Connector identifier (Required)
  • transactionId (Integer): Associated transaction (optional)
  • meterValue (Array): Meter readings (Required)
    • timestamp (DateTime): Reading timestamp (Required)
    • sampledValue (Array): Sampled values (Required)
      • value (String): Measured value (Required)
      • context (Enum): "Interruption.Begin", "Interruption.End", "Sample.Clock", "Sample.Periodic", "Transaction.Begin", "Transaction.End", "Trigger", "Other" (optional)
      • format (Enum): "Raw", "SignedData" (optional)
      • measurand (Enum): "Energy.Active.Export.Register", "Energy.Active.Import.Register", "Energy.Reactive.Export.Register", "Energy.Reactive.Import.Register", "Energy.Active.Export.Interval", "Energy.Active.Import.Interval", "Energy.Reactive.Export.Interval", "Energy.Reactive.Import.Interval", "Power.Active.Export", "Power.Active.Import", "Power.Offered", "Power.Reactive.Export", "Power.Reactive.Import", "Power.Factor", "Current.Import", "Current.Export", "Current.Offered", "Voltage", "Frequency", "Temperature", "SoC", "RPM" (optional)
      • phase (Enum): "L1", "L2", "L3", "N", "L1-N", "L2-N", "L3-N", "L1-L2", "L2-L3", "L3-L1" (optional)
      • location (Enum): "Body", "Cable", "EV", "Inlet", "Outlet" (optional)
      • unit (Enum): "Wh", "kWh", "varh", "kvarh", "W", "kW", "VA", "kVA", "var", "kvar", "A", "V", "Celsius", "Fahrenheit", "K", "Percent" (optional)

πŸ”§ Authorize

Purpose: Validate user authorization

πŸ“€ Request:

[
	2,
	"auth_001",
	"Authorize",
	{
		"idTag": "RFID12345678"
	}
]

πŸ“₯ Response:

[
	3,
	"auth_001",
	{
		"idTagInfo": {
			"status": "Accepted",
			"expiryDate": "2024-12-31T23:59:59.000Z",
			"parentIdTag": "MASTER001"
		}
	}
]

πŸ”‘ Key Fields:

  • idTag (String, Max 20): Authorization identifier (Required)
  • idTagInfo (Object): Authorization information (Required - Response)
    • status (Enum): "Accepted", "Blocked", "Expired", "Invalid", "ConcurrentTx" (Required)
    • expiryDate (DateTime): Tag expiry date (optional)
    • parentIdTag (String, Max 20): Parent tag identifier (optional)

πŸ”§ Authorize - Response Scenarios

Example 1: Blocked ID Tag:

[
	3,
	"auth_blocked_001",
	{
		"idTagInfo": {
			"status": "Blocked",
			"expiryDate": "2024-12-31T23:59:59.000Z",
			"parentIdTag": "MASTER001"
		}
	}
]

Example 2: Expired ID Tag:

[
	3,
	"auth_expired_001",
	{
		"idTagInfo": {
			"status": "Expired",
			"expiryDate": "2023-12-31T23:59:59.000Z"
		}
	}
]

Example 3: Invalid ID Tag:

[
	3,
	"auth_invalid_001",
	{
		"idTagInfo": {
			"status": "Invalid"
		}
	}
]

Example 4: Concurrent Transaction:

[
	3,
	"auth_concurrent_001",
	{
		"idTagInfo": {
			"status": "ConcurrentTx",
			"expiryDate": "2024-12-31T23:59:59.000Z"
		}
	}
]

πŸ”„ DataTransfer

Purpose: Exchange vendor-specific data

πŸ“€ Request:

[
	2,
	"data_001",
	"DataTransfer",
	{
		"vendorId": "Tesla",
		"messageId": "CustomMessage",
		"data": "{\"temperature\": 25.5, \"humidity\": 60}"
	}
]

πŸ“₯ Response:

[
	3,
	"data_001",
	{
		"status": "Accepted",
		"data": "{\"response\": \"OK\"}"
	}
]

πŸ”‘ Key Fields:

  • vendorId (String, Max 255): Vendor identifier (Required)
  • messageId (String, Max 50): Message identifier (optional)
  • data (String): Custom data payload (optional)
  • status (Enum): "Accepted", "Rejected", "UnknownMessageId", "UnknownVendorId" (Required - Response)
  • data (String): Response data payload (optional - Response)

πŸ”§ FirmwareStatusNotification

Purpose: Report firmware update status

πŸ“€ Request:

[
	2,
	"firmware_status_001",
	"FirmwareStatusNotification",
	{
		"status": "Downloading",
		"requestId": 12345
	}
]

πŸ“₯ Response:

[3, "firmware_status_001", {}]

πŸ”‘ Key Fields:

  • status (Enum): "Downloaded", "DownloadFailed", "Downloading", "Idle", "InstallationFailed", "Installing", "Installed" (Required)
  • requestId (Integer): Request identifier (optional)

πŸ” DiagnosticsStatusNotification

Purpose: Report diagnostics collection status

πŸ“€ Request:

[
	2,
	"diagnostics_status_001",
	"DiagnosticsStatusNotification",
	{
		"status": "Uploading"
	}
]

πŸ“₯ Response:

[3, "diagnostics_status_001", {}]

πŸ”‘ Key Fields:

  • status (Enum): "Idle", "Uploaded", "UploadFailed", "Uploading" (Required)

🎯 Central System Initiated Events

🌐 Events sent from the Central System to the Charge Point

πŸ”„ Reset

Purpose: Reset the charge point

πŸ“€ Request:

[
	2,
	"reset_001",
	"Reset",
	{
		"type": "Hard"
	}
]

πŸ“₯ Response:

[
	3,
	"reset_001",
	{
		"status": "Accepted"
	}
]

πŸ”‘ Key Fields:

  • type (Enum): "Hard", "Soft" (Required)
  • status (Enum): "Accepted", "Rejected" (Required - Response)

πŸ”“ UnlockConnector

Purpose: Unlock a connector

πŸ“€ Request:

[
	2,
	"unlock_001",
	"UnlockConnector",
	{
		"connectorId": 1
	}
]

πŸ“₯ Response:

[
	3,
	"unlock_001",
	{
		"status": "Unlocked"
	}
]

πŸ”‘ Key Fields:

  • connectorId (Integer): Connector to unlock (Required)
  • status (Enum): "Unlocked", "UnlockFailed", "NotSupported" (Required - Response)

πŸ›‘ RemoteStartTransaction

Purpose: Start charging remotely

πŸ“€ Request:

[
	2,
	"remote_start_001",
	"RemoteStartTransaction",
	{
		"connectorId": 1,
		"idTag": "RFID12345678",
		"chargingProfile": {
			"chargingProfileId": 1,
			"transactionId": 98765,
			"stackLevel": 0,
			"chargingProfilePurpose": "TxProfile",
			"chargingProfileKind": "Absolute",
			"recurrencyKind": "Daily",
			"validFrom": "2024-01-01T00:00:00.000Z",
			"validTo": "2024-01-02T00:00:00.000Z",
			"chargingSchedule": {
				"duration": 3600,
				"startSchedule": "2024-01-01T12:00:00.000Z",
				"chargingRateUnit": "A",
				"chargingSchedulePeriod": [
					{
						"startPeriod": 0,
						"limit": 32.0,
						"numberPhases": 3
					}
				]
			}
		}
	}
]

πŸ“₯ Response:

[
	3,
	"remote_start_001",
	{
		"status": "Accepted"
	}
]

πŸ”‘ Key Fields:

  • connectorId (Integer): Connector identifier (Required)
  • idTag (String, Max 20): Authorization identifier (Required)
  • chargingProfile (Object): Charging profile (optional)
    • chargingProfileId (Integer): Profile identifier (Required)
    • transactionId (Integer): Transaction identifier (optional)
    • stackLevel (Integer): Profile stack level (Required)
    • chargingProfilePurpose (Enum): "ChargePointMaxProfile", "TxDefaultProfile", "TxProfile" (Required)
    • chargingProfileKind (Enum): "Absolute", "Recurring", "Relative" (Required)
    • recurrencyKind (Enum): "Daily", "Weekly" (optional)
    • validFrom (DateTime): Profile validity start (optional)
    • validTo (DateTime): Profile validity end (optional)
    • chargingSchedule (Object): Charging schedule (Required)
      • duration (Integer): Schedule duration in seconds (optional)
      • startSchedule (DateTime): Schedule start time (optional)
      • chargingRateUnit (Enum): "A", "W" (Required)
      • chargingSchedulePeriod (Array): Schedule periods (Required)
        • startPeriod (Integer): Period start offset in seconds (Required)
        • limit (Decimal): Charging rate limit (Required)
        • numberPhases (Integer): Number of phases (optional)
      • minChargingRate (Decimal): Minimum charging rate (optional)
  • status (Enum): "Accepted", "Rejected" (Required - Response)

πŸ›‘ RemoteStartTransaction - Advanced Scenarios

Example 1: Complex Multi-Period Charging Profile:

[
	2,
	"remote_start_advanced_001",
	"RemoteStartTransaction",
	{
		"connectorId": 1,
		"idTag": "RFID12345678",
		"chargingProfile": {
			"chargingProfileId": 2,
			"stackLevel": 1,
			"chargingProfilePurpose": "TxProfile",
			"chargingProfileKind": "Absolute",
			"validFrom": "2024-01-01T00:00:00.000Z",
			"validTo": "2024-01-02T00:00:00.000Z",
			"chargingSchedule": {
				"duration": 14400,
				"startSchedule": "2024-01-01T18:00:00.000Z",
				"chargingRateUnit": "W",
				"chargingSchedulePeriod": [
					{
						"startPeriod": 0,
						"limit": 7400.0,
						"numberPhases": 1
					},
					{
						"startPeriod": 3600,
						"limit": 11000.0,
						"numberPhases": 1
					},
					{
						"startPeriod": 7200,
						"limit": 22000.0,
						"numberPhases": 3
					},
					{
						"startPeriod": 10800,
						"limit": 7400.0,
						"numberPhases": 1
					}
				],
				"minChargingRate": 6000.0
			}
		}
	}
]

Example 2: Recurring Weekly Profile:

[
	2,
	"remote_start_recurring_001",
	"RemoteStartTransaction",
	{
		"connectorId": 2,
		"idTag": "FLEET-CARD-001",
		"chargingProfile": {
			"chargingProfileId": 3,
			"stackLevel": 0,
			"chargingProfilePurpose": "TxDefaultProfile",
			"chargingProfileKind": "Recurring",
			"recurrencyKind": "Weekly",
			"validFrom": "2024-01-01T00:00:00.000Z",
			"validTo": "2024-12-31T23:59:59.000Z",
			"chargingSchedule": {
				"chargingRateUnit": "A",
				"chargingSchedulePeriod": [
					{
						"startPeriod": 0,
						"limit": 6.0,
						"numberPhases": 1
					},
					{
						"startPeriod": 21600,
						"limit": 16.0,
						"numberPhases": 1
					},
					{
						"startPeriod": 64800,
						"limit": 32.0,
						"numberPhases": 3
					}
				],
				"minChargingRate": 6.0
			}
		}
	}
]

Example 3: Simple Start without Charging Profile:

[
	2,
	"remote_start_simple_001",
	"RemoteStartTransaction",
	{
		"connectorId": 1,
		"idTag": "GUEST-USER-001"
	}
]

πŸ”΄ RemoteStopTransaction

Purpose: Stop charging remotely

πŸ“€ Request:

[
	2,
	"remote_stop_001",
	"RemoteStopTransaction",
	{
		"transactionId": 98765
	}
]

πŸ“₯ Response:

[
	3,
	"remote_stop_001",
	{
		"status": "Accepted"
	}
]

πŸ”‘ Key Fields:

  • transactionId (Integer): Transaction to stop
  • status (Enum): "Accepted", "Rejected"

βš™οΈ ChangeConfiguration

Purpose: Change charge point configuration

πŸ“€ Request:

[
	2,
	"config_001",
	"ChangeConfiguration",
	{
		"key": "HeartbeatInterval",
		"value": "60"
	}
]

πŸ“₯ Response:

[
	3,
	"config_001",
	{
		"status": "Accepted"
	}
]

πŸ”‘ Key Fields:

  • key (String, Max 50): Configuration key (Required)
  • value (String, Max 500): Configuration value (Required)
  • status (Enum): "Accepted", "Rejected", "RebootRequired", "NotSupported" (Required - Response)

πŸ“‹ GetConfiguration

Purpose: Get charge point configuration

πŸ“€ Request:

[
	2,
	"get_config_001",
	"GetConfiguration",
	{
		"key": [
			"HeartbeatInterval",
			"MeterValueSampleInterval",
			"InvalidKey",
			"UnknownSetting"
		]
	}
]

πŸ“₯ Response:

[
	3,
	"get_config_001",
	{
		"configurationKey": [
			{
				"key": "HeartbeatInterval",
				"readonly": false,
				"value": "300"
			},
			{
				"key": "MeterValueSampleInterval",
				"readonly": false,
				"value": "60"
			},
			{
				"key": "AuthorizeRemoteTxRequests",
				"readonly": true,
				"value": "true"
			},
			{
				"key": "ClockAlignedDataInterval",
				"readonly": false,
				"value": "900"
			},
			{
				"key": "ConnectionTimeOut",
				"readonly": false,
				"value": "60"
			},
			{
				"key": "LocalAuthorizeOffline",
				"readonly": false,
				"value": "true"
			},
			{
				"key": "LocalPreAuthorize",
				"readonly": false,
				"value": "false"
			},
			{
				"key": "MeterValuesAlignedData",
				"readonly": false,
				"value": "Energy.Active.Import.Register,Power.Active.Import"
			},
			{
				"key": "MeterValuesSampledData",
				"readonly": false,
				"value": "Energy.Active.Import.Register,Power.Active.Import,Current.Import,Voltage,SoC"
			},
			{
				"key": "NumberOfConnectors",
				"readonly": true,
				"value": "2"
			},
			{
				"key": "ResetRetries",
				"readonly": false,
				"value": "3"
			},
			{
				"key": "StopTransactionOnEVSideDisconnect",
				"readonly": false,
				"value": "true"
			},
			{
				"key": "StopTransactionOnInvalidId",
				"readonly": false,
				"value": "true"
			},
			{
				"key": "StopTxnAlignedData",
				"readonly": false,
				"value": "Energy.Active.Import.Register"
			},
			{
				"key": "StopTxnSampledData",
				"readonly": false,
				"value": "Energy.Active.Import.Register,Power.Active.Import"
			},
			{
				"key": "TransactionMessageAttempts",
				"readonly": false,
				"value": "3"
			},
			{
				"key": "TransactionMessageRetryInterval",
				"readonly": false,
				"value": "60"
			},
			{
				"key": "UnlockConnectorOnEVSideDisconnect",
				"readonly": false,
				"value": "true"
			}
		],
		"unknownKey": ["InvalidKey", "UnknownSetting"]
	}
]

πŸ”‘ Key Fields:

  • key (Array): Configuration keys to retrieve (optional)
  • configurationKey (Array): Configuration items (Required - Response)
    • key (String, Max 50): Configuration key (Required)
    • readonly (Boolean): Is key read-only (Required)
    • value (String, Max 500): Configuration value (optional)
  • unknownKey (Array): Unknown keys (Required - Response)

πŸ” GetDiagnostics

Purpose: Request diagnostic data from charge point

πŸ“€ Request:

[
	2,
	"diagnostics_001",
	"GetDiagnostics",
	{
		"location": "ftp://server.com/diagnostics/",
		"retries": 3,
		"retryInterval": 60,
		"startTime": "2024-01-01T00:00:00.000Z",
		"stopTime": "2024-01-01T23:59:59.000Z"
	}
]

πŸ“₯ Response:

[
	3,
	"diagnostics_001",
	{
		"fileName": "diagnostics_CP001_20240101.log"
	}
]

πŸ”‘ Key Fields:

  • location (String): Upload location URI
  • retries (Integer): Number of retries (optional)
  • retryInterval (Integer): Retry interval in seconds (optional)
  • startTime (DateTime): Start time for diagnostic data (optional)
  • stopTime (DateTime): Stop time for diagnostic data (optional)
  • fileName (String): Generated diagnostic file name (optional)

πŸ”„ UpdateFirmware

Purpose: Update charge point firmware

πŸ“€ Request:

[
	2,
	"firmware_001",
	"UpdateFirmware",
	{
		"location": "ftp://server.com/firmware/charger_v2.0.bin",
		"retries": 3,
		"retrieveDate": "2024-01-01T02:00:00.000Z",
		"retryInterval": 300
	}
]

πŸ“₯ Response:

[3, "firmware_001", {}]

πŸ”‘ Key Fields:

  • location (String): Firmware download URI
  • retries (Integer): Number of retries (optional)
  • retrieveDate (DateTime): When to retrieve firmware
  • retryInterval (Integer): Retry interval in seconds (optional)

πŸ“… ReserveNow

Purpose: Reserve a connector for a specific user

πŸ“€ Request:

[
	2,
	"reserve_001",
	"ReserveNow",
	{
		"connectorId": 1,
		"expiryDate": "2024-01-01T15:00:00.000Z",
		"idTag": "RFID12345678",
		"reservationId": 12345,
		"parentIdTag": "MASTER001"
	}
]

πŸ“₯ Response:

[
	3,
	"reserve_001",
	{
		"status": "Accepted"
	}
]

πŸ”‘ Key Fields:

  • connectorId (Integer): Connector to reserve
  • expiryDate (DateTime): Reservation expiry time
  • idTag (String, Max 20): Authorized user identifier
  • reservationId (Integer): Unique reservation identifier
  • parentIdTag (String, Max 20): Parent identifier (optional)
  • status (Enum): "Accepted", "Faulted", "Occupied", "Rejected", "Unavailable"

❌ CancelReservation

Purpose: Cancel an existing reservation

πŸ“€ Request:

[
	2,
	"cancel_res_001",
	"CancelReservation",
	{
		"reservationId": 12345
	}
]

πŸ“₯ Response:

[
	3,
	"cancel_res_001",
	{
		"status": "Accepted"
	}
]

πŸ”‘ Key Fields:

  • reservationId (Integer): Reservation to cancel
  • status (Enum): "Accepted", "Rejected"

πŸ—‘οΈ ClearCache

Purpose: Clear authorization cache

πŸ“€ Request:

[2, "clear_cache_001", "ClearCache", {}]

πŸ“₯ Response:

[
	3,
	"clear_cache_001",
	{
		"status": "Accepted"
	}
]

πŸ”‘ Key Fields:

  • status (Enum): "Accepted", "Rejected"

πŸ“‹ GetLocalListVersion

Purpose: Get local authorization list version

πŸ“€ Request:

[2, "get_list_001", "GetLocalListVersion", {}]

πŸ“₯ Response:

[
	3,
	"get_list_001",
	{
		"listVersion": 42
	}
]

πŸ”‘ Key Fields:

  • listVersion (Integer): Current list version (-1 if no list)

πŸ“€ SendLocalList

Purpose: Update local authorization list

πŸ“€ Request:

[
	2,
	"send_list_001",
	"SendLocalList",
	{
		"listVersion": 43,
		"updateType": "Differential",
		"localAuthorizationList": [
			{
				"idTag": "RFID12345678",
				"idTagInfo": {
					"status": "Accepted",
					"expiryDate": "2024-12-31T23:59:59.000Z",
					"parentIdTag": "MASTER001"
				}
			},
			{
				"idTag": "RFID87654321",
				"idTagInfo": {
					"status": "Blocked"
				}
			}
		]
	}
]

πŸ“₯ Response:

[
	3,
	"send_list_001",
	{
		"status": "Accepted"
	}
]

πŸ”‘ Key Fields:

  • listVersion (Integer): New list version
  • updateType (Enum): "Differential", "Full"
  • localAuthorizationList (Array): Authorization entries
    • idTag (String, Max 20): Tag identifier
    • idTagInfo (Object): Authorization info (same as StartTransaction)
  • status (Enum): "Accepted", "Failed", "NotSupported", "VersionMismatch"

⚑ SetChargingProfile

Purpose: Set charging profile for power management

πŸ“€ Request:

[
	2,
	"set_profile_001",
	"SetChargingProfile",
	{
		"connectorId": 1,
		"csChargingProfiles": {
			"chargingProfileId": 1,
			"transactionId": 98765,
			"stackLevel": 0,
			"chargingProfilePurpose": "TxProfile",
			"chargingProfileKind": "Absolute",
			"recurrencyKind": "Daily",
			"validFrom": "2024-01-01T00:00:00.000Z",
			"validTo": "2024-01-02T00:00:00.000Z",
			"chargingSchedule": {
				"duration": 7200,
				"startSchedule": "2024-01-01T12:00:00.000Z",
				"chargingRateUnit": "A",
				"chargingSchedulePeriod": [
					{
						"startPeriod": 0,
						"limit": 32.0,
						"numberPhases": 3
					},
					{
						"startPeriod": 3600,
						"limit": 16.0,
						"numberPhases": 1
					}
				],
				"minChargingRate": 6.0
			}
		}
	}
]

πŸ“₯ Response:

[
	3,
	"set_profile_001",
	{
		"status": "Accepted"
	}
]

πŸ”‘ Key Fields:

  • connectorId (Integer): Connector ID (0 = entire charge point)
  • csChargingProfiles (Object): Charging profile (same structure as RemoteStartTransaction)
  • status (Enum): "Accepted", "Rejected", "NotSupported"

πŸ—‘οΈ ClearChargingProfile

Purpose: Remove charging profiles

πŸ“€ Request:

[
	2,
	"clear_profile_001",
	"ClearChargingProfile",
	{
		"id": 1,
		"connectorId": 1,
		"chargingProfilePurpose": "TxProfile",
		"stackLevel": 0
	}
]

πŸ“₯ Response:

[
	3,
	"clear_profile_001",
	{
		"status": "Accepted"
	}
]

πŸ”‘ Key Fields:

  • id (Integer): Charging profile ID (optional)
  • connectorId (Integer): Connector ID (optional)
  • chargingProfilePurpose (Enum): Profile purpose (optional)
  • stackLevel (Integer): Stack level (optional)
  • status (Enum): "Accepted", "Unknown"

πŸ“Š GetCompositeSchedule

Purpose: Get composite charging schedule

πŸ“€ Request:

[
	2,
	"get_schedule_001",
	"GetCompositeSchedule",
	{
		"connectorId": 1,
		"duration": 3600,
		"chargingRateUnit": "A"
	}
]

πŸ“₯ Response:

[
	3,
	"get_schedule_001",
	{
		"status": "Accepted",
		"connectorId": 1,
		"scheduleStart": "2024-01-01T12:00:00.000Z",
		"chargingSchedule": {
			"duration": 3600,
			"startSchedule": "2024-01-01T12:00:00.000Z",
			"chargingRateUnit": "A",
			"chargingSchedulePeriod": [
				{
					"startPeriod": 0,
					"limit": 32.0,
					"numberPhases": 3
				},
				{
					"startPeriod": 1800,
					"limit": 16.0,
					"numberPhases": 1
				}
			]
		}
	}
]

πŸ”‘ Key Fields:

  • connectorId (Integer): Connector identifier
  • duration (Integer): Schedule duration in seconds
  • chargingRateUnit (Enum): "A", "W"
  • status (Enum): "Accepted", "Rejected"
  • scheduleStart (DateTime): Schedule start time
  • chargingSchedule (Object): Composite schedule

🎯 TriggerMessage

Purpose: Trigger specific message from charge point

πŸ“€ Request:

[
	2,
	"trigger_001",
	"TriggerMessage",
	{
		"requestedMessage": "StatusNotification",
		"connectorId": 1
	}
]

πŸ“₯ Response:

[
	3,
	"trigger_001",
	{
		"status": "Accepted"
	}
]

πŸ”‘ Key Fields:

  • requestedMessage (Enum): "BootNotification", "DiagnosticsStatusNotification", "FirmwareStatusNotification", "Heartbeat", "MeterValues", "StatusNotification" (Required)
  • connectorId (Integer): Connector ID (optional, required for specific messages like StatusNotification, MeterValues)
  • status (Enum): "Accepted", "Rejected", "NotImplemented" (Required - Response)

πŸ”§ ChangeAvailability

Purpose: Change connector availability (operative/inoperative)

πŸ“€ Request:

[
	2,
	"change_avail_001",
	"ChangeAvailability",
	{
		"connectorId": 1,
		"type": "Inoperative"
	}
]

πŸ“₯ Response:

[
	3,
	"change_avail_001",
	{
		"status": "Accepted"
	}
]

πŸ”‘ Key Fields:

  • connectorId (Integer): Connector to change (0 = entire charge point)
  • type (Enum): "Inoperative", "Operative"
  • status (Enum): "Accepted", "Rejected", "Scheduled"

πŸ’» Complete Implementation Examples

πŸ› οΈ Ready-to-use code examples for Node.js, Python, and Java

🟒 Node.js Implementation

πŸ“¦ WebSocket Client Setup

πŸ“‹ Required Dependencies:

npm install ws uuid

πŸš€ Complete OCPP 1.6 Client:

const WebSocket = require("ws");
const { v4: uuidv4 } = require("uuid");

class OCPPClient {
	constructor(url, chargePointId) {
		this.url = url;
		this.chargePointId = chargePointId;
		this.ws = null;
		this.messageQueue = new Map();
		this.heartbeatInterval = null;
	}

	connect() {
		return new Promise((resolve, reject) => {
			const wsUrl = `${this.url}/${this.chargePointId}`;
			this.ws = new WebSocket(wsUrl, ["ocpp1.6"]);

			this.ws.on("open", () => {
				console.log("πŸ”Œ Connected to OCPP Server");
				this.sendBootNotification()
					.then(() => {
						this.startHeartbeat();
						resolve();
					})
					.catch(reject);
			});

			this.ws.on("message", (data) => {
				this.handleMessage(JSON.parse(data.toString()));
			});

			this.ws.on("error", (error) => {
				console.error("❌ WebSocket Error:", error);
				reject(error);
			});

			this.ws.on("close", () => {
				console.log("πŸ”’ Connection Closed");
				this.stopHeartbeat();
			});
		});
	}

	// πŸ’“ Send Heartbeat
	sendHeartbeat() {
		return this.sendCall("Heartbeat", {});
	}

	// 🏁 Send BootNotification
	sendBootNotification() {
		const payload = {
			chargePointVendor: "YourCompany",
			chargePointModel: "FastCharger-Pro",
			chargePointSerialNumber: "FC-001234",
			chargeBoxSerialNumber: "CB-001234",
			firmwareVersion: "1.0.0",
			iccid: "8944501234567890123",
			imsi: "234567890123456",
			meterType: "EnergyMeter-X1",
			meterSerialNumber: "EM-001234",
		};
		return this.sendCall("BootNotification", payload);
	}

	// 🏁 Send BootNotification - Response Scenarios
	sendBootNotificationPending() {
		const payload = {
			currentTime: "2024-01-01T12:00:00.000Z",
			interval: 300,
			status: "Pending",
		};
		return this.sendCall("BootNotification", payload);
	}

	sendBootNotificationRejected() {
		const payload = {
			currentTime: "2024-01-01T12:00:00.000Z",
			interval: 86400,
			status: "Rejected",
		};
		return this.sendCall("BootNotification", payload);
	}

	// πŸ”‹ Send StatusNotification
	sendStatusNotification(connectorId, status, errorCode = "NoError") {
		const payload = {
			connectorId: connectorId,
			status: status,
			errorCode: errorCode,
			timestamp: new Date().toISOString(),
			info: `Connector ${connectorId} is ${status}`,
		};
		return this.sendCall("StatusNotification", payload);
	}

	// πŸš— Send StartTransaction
	sendStartTransaction(connectorId, idTag, meterStart) {
		const payload = {
			connectorId: connectorId,
			idTag: idTag,
			meterStart: meterStart,
			timestamp: new Date().toISOString(),
		};
		return this.sendCall("StartTransaction", payload);
	}

	// 🏁 Send StopTransaction
	sendStopTransaction(transactionId, idTag, meterStop, reason = "Local") {
		const payload = {
			transactionId: transactionId,
			idTag: idTag,
			meterStop: meterStop,
			timestamp: new Date().toISOString(),
			reason: reason,
		};
		return this.sendCall("StopTransaction", payload);
	}

	// πŸ“Š Send MeterValues
	sendMeterValues(connectorId, transactionId, meterValues) {
		const payload = {
			connectorId: connectorId,
			transactionId: transactionId,
			meterValue: [
				{
					timestamp: new Date().toISOString(),
					sampledValue: meterValues,
				},
			],
		};
		return this.sendCall("MeterValues", payload);
	}

	// πŸ”§ Send Authorize
	sendAuthorize(idTag) {
		const payload = { idTag: idTag };
		return this.sendCall("Authorize", payload);
	}

	// Generic method to send CALL messages
	sendCall(action, payload) {
		return new Promise((resolve, reject) => {
			const messageId = uuidv4();
			const message = [2, messageId, action, payload];

			this.messageQueue.set(messageId, { resolve, reject });
			this.ws.send(JSON.stringify(message));

			console.log(`πŸ“€ Sent ${action}:`, payload);

			// Timeout after 30 seconds
			setTimeout(() => {
				if (this.messageQueue.has(messageId)) {
					this.messageQueue.delete(messageId);
					reject(new Error(`Timeout waiting for ${action} response`));
				}
			}, 30000);
		});
	}

	// Handle incoming messages
	handleMessage(message) {
		const [messageType, messageId, actionOrPayload, payload] = message;

		if (messageType === 3) {
			// CALLRESULT
			console.log(`πŸ“₯ Received Response:`, actionOrPayload);
			if (this.messageQueue.has(messageId)) {
				const { resolve } = this.messageQueue.get(messageId);
				this.messageQueue.delete(messageId);
				resolve(actionOrPayload);
			}
		} else if (messageType === 4) {
			// CALLERROR
			console.error(`❌ Received Error:`, actionOrPayload, payload);
			if (this.messageQueue.has(messageId)) {
				const { reject } = this.messageQueue.get(messageId);
				this.messageQueue.delete(messageId);
				reject(new Error(`${actionOrPayload}: ${payload}`));
			}
		} else if (messageType === 2) {
			// CALL (from server)
			this.handleServerCall(messageId, actionOrPayload, payload);
		}
	}

	// Handle server-initiated calls
	handleServerCall(messageId, action, payload) {
		console.log(`πŸ“¨ Received ${action}:`, payload);

		let response = {};

		switch (action) {
			case "Reset":
				response = { status: "Accepted" };
				console.log("πŸ”„ Resetting charge point...");
				break;

			case "UnlockConnector":
				response = { status: "Unlocked" };
				console.log(`πŸ”“ Unlocking connector ${payload.connectorId}`);
				break;

			case "RemoteStartTransaction":
				response = { status: "Accepted" };
				console.log("πŸ›‘ Starting remote transaction...");
				break;

			case "RemoteStopTransaction":
				response = { status: "Accepted" };
				console.log("πŸ”΄ Stopping remote transaction...");
				break;

			case "ChangeConfiguration":
				response = { status: "Accepted" };
				console.log(
					`βš™οΈ Configuration changed: ${payload.key} = ${payload.value}`,
				);
				break;

			case "GetConfiguration":
				response = {
					configurationKey: [
						{
							key: "HeartbeatInterval",
							readonly: false,
							value: "300",
						},
					],
					unknownKey: [],
				};
				break;

			case "ChangeAvailability":
				response = { status: "Accepted" };
				console.log(
					`πŸ”§ Connector ${payload.connectorId} availability set to ${payload.type}`,
				);
				break;

			case "GetDiagnostics":
				response = { fileName: "diagnostics_CP001.log" };
				console.log(`πŸ” Diagnostics requested to ${payload.location}`);
				break;

			case "UpdateFirmware":
				response = {};
				console.log(
					`πŸ”„ Firmware update requested from ${payload.location}`,
				);
				break;

			case "ReserveNow":
				response = { status: "Accepted" };
				console.log(
					`πŸ“… Connector ${payload.connectorId} reserved for ${payload.idTag}`,
				);
				break;

			case "CancelReservation":
				response = { status: "Accepted" };
				console.log(
					`❌ Reservation ${payload.reservationId} cancelled`,
				);
				break;

			case "ClearCache":
				response = { status: "Accepted" };
				console.log("πŸ—‘οΈ Authorization cache cleared");
				break;

			case "GetLocalListVersion":
				response = { listVersion: 1 };
				console.log("πŸ“‹ Local list version requested");
				break;

			case "SendLocalList":
				response = { status: "Accepted" };
				console.log(
					`πŸ“€ Local list updated to version ${payload.listVersion}`,
				);
				break;

			case "SetChargingProfile":
				response = { status: "Accepted" };
				console.log(
					`⚑ Charging profile set for connector ${payload.connectorId}`,
				);
				break;

			case "ClearChargingProfile":
				response = { status: "Accepted" };
				console.log("πŸ—‘οΈ Charging profiles cleared");
				break;

			case "GetCompositeSchedule":
				response = {
					status: "Accepted",
					connectorId: payload.connectorId,
					scheduleStart: new Date().toISOString(),
					chargingSchedule: {
						duration: payload.duration,
						chargingRateUnit: payload.chargingRateUnit || "A",
						chargingSchedulePeriod: [
							{
								startPeriod: 0,
								limit: 32.0,
								numberPhases: 3,
							},
						],
					},
				};
				console.log(
					`πŸ“Š Composite schedule requested for connector ${payload.connectorId}`,
				);
				break;

			case "TriggerMessage":
				response = { status: "Accepted" };
				console.log(`🎯 Trigger message: ${payload.requestedMessage}`);
				break;

			default:
				response = { status: "NotSupported" };
		}

		// Send CALLRESULT
		const result = [3, messageId, response];
		this.ws.send(JSON.stringify(result));
		console.log(`πŸ“€ Sent Response:`, response);
	}

	startHeartbeat() {
		this.heartbeatInterval = setInterval(() => {
			this.sendHeartbeat().catch(console.error);
		}, 300000); // 5 minutes
	}

	stopHeartbeat() {
		if (this.heartbeatInterval) {
			clearInterval(this.heartbeatInterval);
			this.heartbeatInterval = null;
		}
	}

	disconnect() {
		this.stopHeartbeat();
		if (this.ws) {
			this.ws.close();
		}
	}
}

// πŸš€ Usage Example
async function main() {
	const client = new OCPPClient("ws://localhost:8080/ocpp", "CHARGER_001");

	try {
		await client.connect();

		// Send status notification
		await client.sendStatusNotification(1, "Available");

		// Simulate charging session
		const authResult = await client.sendAuthorize("RFID12345678");
		console.log("πŸ” Authorization:", authResult);

		if (authResult.idTagInfo.status === "Accepted") {
			const startResult = await client.sendStartTransaction(
				1,
				"RFID12345678",
				1000,
			);
			console.log("πŸš— Transaction Started:", startResult);

			// Send meter values every 60 seconds
			const meterInterval = setInterval(async () => {
				const meterValues = [
					{
						value: (Math.random() * 50 + 10).toFixed(2),
						measurand: "Power.Active.Import",
						unit: "kW",
					},
					{
						value: (Math.random() * 100 + 200).toFixed(1),
						measurand: "Voltage",
						unit: "V",
					},
				];

				await client.sendMeterValues(
					1,
					startResult.transactionId,
					meterValues,
				);
			}, 60000);

			// Stop transaction after 10 minutes
			setTimeout(async () => {
				clearInterval(meterInterval);
				await client.sendStopTransaction(
					startResult.transactionId,
					"RFID12345678",
					15000,
					"Local",
				);
				await client.sendStatusNotification(1, "Available");
			}, 600000);
		}
	} catch (error) {
		console.error("πŸ’₯ Error:", error);
	}
}

// Uncomment to run
// main();

🐍 Python Implementation

πŸ“¦ Required Dependencies:

pip install websockets asyncio uuid

πŸš€ Complete OCPP 1.6 Client:

import asyncio
import websockets
import json
import uuid
from datetime import datetime
from typing import Dict, Any, Optional
import logging

# Configure logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

class OCPPClient:
    def __init__(self, url: str, charge_point_id: str):
        self.url = url
        self.charge_point_id = charge_point_id
        self.websocket = None
        self.message_queue: Dict[str, asyncio.Future] = {}
        self.heartbeat_task = None

    async def connect(self):
        """πŸ”Œ Connect to OCPP Server"""
        ws_url = f"{self.url}/{self.charge_point_id}"
        self.websocket = await websockets.connect(
            ws_url,
            subprotocols=["ocpp1.6"]
        )
        logger.info("πŸ”Œ Connected to OCPP Server")

        # Start message handler
        asyncio.create_task(self._message_handler())

        # Send boot notification
        boot_response = await self.send_boot_notification()
        logger.info(f"🏁 Boot Response: {boot_response}")

        # Start heartbeat
        self.heartbeat_task = asyncio.create_task(self._heartbeat_loop())

    async def _message_handler(self):
        """πŸ“¨ Handle incoming messages"""
        try:
            async for message in self.websocket:
                data = json.loads(message)
                await self._handle_message(data)
        except websockets.exceptions.ConnectionClosed:
            logger.info("πŸ”’ Connection closed")
        except Exception as e:
            logger.error(f"❌ Message handler error: {e}")

    async def _handle_message(self, message: list):
        """Process incoming OCPP messages"""
        message_type, message_id = message[0], message[1]

        if message_type == 3:  # CALLRESULT
            payload = message[2]
            logger.info(f"πŸ“₯ Received Response: {payload}")
            if message_id in self.message_queue:
                future = self.message_queue.pop(message_id)
                future.set_result(payload)

        elif message_type == 4:  # CALLERROR
            error_code, error_description = message[2], message[3]
            logger.error(f"❌ Received Error: {error_code} - {error_description}")
            if message_id in self.message_queue:
                future = self.message_queue.pop(message_id)
                future.set_exception(Exception(f"{error_code}: {error_description}"))

        elif message_type == 2:  # CALL (from server)
            action, payload = message[2], message[3]
            await self._handle_server_call(message_id, action, payload)

    async def _handle_server_call(self, message_id: str, action: str, payload: dict):
        """🎯 Handle server-initiated calls"""
        logger.info(f"πŸ“¨ Received {action}: {payload}")

        response = {}

        if action == "Reset":
            response = {"status": "Accepted"}
            logger.info("πŸ”„ Resetting charge point...")

        elif action == "UnlockConnector":
            response = {"status": "Unlocked"}
            logger.info(f"πŸ”“ Unlocking connector {payload['connectorId']}")

        elif action == "RemoteStartTransaction":
            response = {"status": "Accepted"}
            logger.info("πŸ›‘ Starting remote transaction...")

        elif action == "RemoteStopTransaction":
            response = {"status": "Accepted"}
            logger.info("πŸ”΄ Stopping remote transaction...")

        elif action == "ChangeConfiguration":
            response = {"status": "Accepted"}
            logger.info(f"βš™οΈ Configuration changed: {payload['key']} = {payload['value']}")

        elif action == "GetConfiguration":
            response = {
                "configurationKey": [
                    {
                        "key": "HeartbeatInterval",
                        "readonly": False,
                        "value": "300"
                    },
                    {
                        "key": "MeterValueSampleInterval",
                        "readonly": False,
                        "value": "60"
                    }
                ],
                "unknownKey": []
            }
        elif action == "ChangeAvailability":
            response = {"status": "Accepted"}
            logger.info(f"πŸ”§ Connector {payload['connectorId']} availability set to {payload['type']}")

        elif action == "GetDiagnostics":
            response = {"fileName": "diagnostics_CP001.log"}
            logger.info(f"πŸ” Diagnostics requested to {payload['location']}")

        elif action == "UpdateFirmware":
            response = {}
            logger.info(f"πŸ”„ Firmware update requested from {payload['location']}")

        elif action == "ReserveNow":
            response = {"status": "Accepted"}
            logger.info(f"πŸ“… Connector {payload['connectorId']} reserved for {payload['idTag']}")

        elif action == "CancelReservation":
            response = {"status": "Accepted"}
            logger.info(f"❌ Reservation {payload['reservationId']} cancelled")

        elif action == "ClearCache":
            response = {"status": "Accepted"}
            logger.info("πŸ—‘οΈ Authorization cache cleared")

        elif action == "GetLocalListVersion":
            response = {"listVersion": 1}
            logger.info("πŸ“‹ Local list version requested")

        elif action == "SendLocalList":
            response = {"status": "Accepted"}
            logger.info(f"πŸ“€ Local list updated to version {payload['listVersion']}")

        elif action == "SetChargingProfile":
            response = {"status": "Accepted"}
            logger.info(f"⚑ Charging profile set for connector {payload['connectorId']}")

        elif action == "ClearChargingProfile":
            response = {"status": "Accepted"}
            logger.info("πŸ—‘οΈ Charging profiles cleared")

        elif action == "GetCompositeSchedule":
            response = {
                "status": "Accepted",
                "connectorId": payload["connectorId"],
                "scheduleStart": datetime.utcnow().isoformat() + "Z",
                "chargingSchedule": {
                    "duration": payload["duration"],
                    "chargingRateUnit": payload.get("chargingRateUnit", "A"),
                    "chargingSchedulePeriod": [
                        {
                            "startPeriod": 0,
                            "limit": 32.0,
                            "numberPhases": 3
                        },
                    ],
                },
            };
            logger.info(f"πŸ“Š Composite schedule requested for connector {payload['connectorId']}")

        elif action == "TriggerMessage":
            response = {"status": "Accepted"}
            logger.info(f"🎯 Trigger message: {payload['requestedMessage']}")

        else:
            response = {"status": "NotSupported"}

        # Send CALLRESULT
        result = [3, message_id, response]
        await self.websocket.send(json.dumps(result))
        logger.info(f"πŸ“€ Sent Response: {response}")

    async def _send_call(self, action: str, payload: dict) -> dict:
        """πŸ“€ Send CALL message and wait for response"""
        message_id = str(uuid.uuid4())
        message = [2, message_id, action, payload]

        # Create future for response
        future = asyncio.Future()
        self.message_queue[message_id] = future

        # Send message
        await self.websocket.send(json.dumps(message))
        logger.info(f"πŸ“€ Sent {action}: {payload}")

        # Wait for response with timeout
        try:
            response = await asyncio.wait_for(future, timeout=30.0)
            return response
        } except asyncio.TimeoutError {
            self.message_queue.pop(message_id, None)
            raise Exception(f"Timeout waiting for {action} response")
        }
    }

    async def send_heartbeat(self) -> dict:
        """πŸ’“ Send Heartbeat"""
        return await self._send_call("Heartbeat", {})

    async def send_boot_notification(self) -> dict:
        """🏁 Send BootNotification"""
        payload = {
            "chargePointVendor": "YourCompany",
            "chargePointModel": "FastCharger-Pro",
            "chargePointSerialNumber": "FC-001234",
            "chargeBoxSerialNumber": "CB-001234",
            "firmwareVersion": "1.0.0",
            "iccid": "8944501234567890123",
            "imsi": "234567890123456",
            "meterType": "EnergyMeter-X1",
            "meterSerialNumber": "EM-001234"
        }
        return await this._send_call("BootNotification", payload)
    async def send_boot_notification_pending(self) -> dict:
        """🏁 Send BootNotification - Pending Scenario"""
        payload = {
            "currentTime": "2024-01-01T12:00:00.000Z",
            "interval": 300,
            "status": "Pending"
        }
        return await this._send_call("BootNotification", payload)

    async def send_boot_notification_rejected(self) -> dict:
        """🏁 Send BootNotification - Rejected Scenario"""
        payload = {
            "currentTime": "2024-01-01T12:00:00.000Z",
            "interval": 86400,
            "status": "Rejected"
        }
        return await this._send_call("BootNotification", payload)

    async def send_status_notification(self, connector_id: int, status: str,
                                     error_code: str = "NoError") -> dict:
        """πŸ”‹ Send StatusNotification"""
        payload = {
            "connectorId": connector_id,
            "status": status,
            "errorCode": error_code,
            "timestamp": datetime.utcnow().isoformat() + "Z",
            "info": f"Connector {connector_id} is {status}"
        }
        return await this._send_call("StatusNotification", payload)

    async def send_start_transaction(self, connector_id: int, id_tag: str,
                                   meter_start: int) -> dict:
        """πŸš— Send StartTransaction"""
        payload = {
            "connectorId": connector_id,
            "idTag": id_tag,
            "meterStart": meter_start,
            "timestamp": datetime.utcnow().isoformat() + "Z"
        }
        return await this._send_call("StartTransaction", payload)

    async def send_stop_transaction(self, transaction_id: int, id_tag: str,
                                  meter_stop: int, reason: str = "Local") -> dict:
        """🏁 Send StopTransaction"""
        payload = {
            "transactionId": transaction_id,
            "idTag": id_tag,
            "meterStop": meter_stop,
            "timestamp": datetime.utcnow().isoformat() + "Z",
            "reason": reason
        }
        return await this._send_call("StopTransaction", payload)

    async def send_meter_values(self, connector_id: int, transaction_id: int,
                              meter_values: list) -> dict:
        """πŸ“Š Send MeterValues"""
        payload = {
            "connectorId": connector_id,
            "transactionId": transaction_id,
            "meterValue": [{
                "timestamp": datetime.utcnow().isoformat() + "Z",
                "sampledValue": meter_values
            }]
        }
        return await this._send_call("MeterValues", payload)

    async def send_authorize(self, id_tag: str) -> dict:
        """πŸ”§ Send Authorize"""
        payload = {"idTag": id_tag}
        return await this._send_call("Authorize", payload)

    async def _heartbeat_loop(self):
        """πŸ’“ Periodic heartbeat loop"""
        while True:
            try:
                await asyncio.sleep(300)  # 5 minutes
                await self.send_heartbeat()
            except Exception as e:
                logger.error(f"πŸ’” Heartbeat error: {e}")
                break

    async def disconnect(self):
        """πŸ”’ Disconnect from server"""
        if self.heartbeat_task:
            self.heartbeat_task.cancel()
        if self.websocket:
            await self.websocket.close()
        logger.info("πŸ”’ Disconnected from OCPP Server")

# πŸš€ Usage Example
async def main():
    client = OCPPClient("ws://localhost:8080/ocpp", "CHARGER_001")

    try:
        await client.connect()

        # Send status notification
        await client.send_status_notification(1, "Available")

        # Simulate charging session
        auth_result = await client.send_authorize("RFID12345678")
        logger.info(f"πŸ” Authorization: {auth_result}")

        if auth_result["idTagInfo"]["status"] == "Accepted":
            start_result = await client.send_start_transaction(1, "RFID12345678", 1000)
            logger.info(f"πŸš— Transaction Started: {start_result}")

            # Send meter values periodically
            for i in range(5):
                meter_values = [
                    {
                        "value": f"{(i + 1) * 10.5:.2f}",
                        "measurand": "Power.Active.Import",
                        "unit": "kW"
                    },
                    {
                        "value": f"{230 + i * 2:.1f}",
                        "measurand": "Voltage",
                        "unit": "V"
                    }
                ]

                await client.send_meter_values(1, start_result["transactionId"], meter_values)
                await asyncio.sleep(60)  # Wait 1 minute

            # Stop transaction
            await client.send_stop_transaction(
                start_result["transactionId"],
                "RFID12345678",
                15000,
                "Local"
            )
            await client.send_status_notification(1, "Available")

        # Keep connection alive for demonstration
        await asyncio.sleep(10)

    except Exception as e:
        logger.error(f"πŸ’₯ Error: {e}")
    finally:
        await client.disconnect()

# Uncomment to run
# if __name__ == "__main__":
#     asyncio.run(main())

🚨 Error Handling & Troubleshooting

❌ Common OCPP Error Codes

{
	"errorCode": "GenericError",
	"errorDescription": "Generic error message",
	"errorDetails": {}
}

Standard Error Codes:

  • NotImplemented - Feature not supported
  • NotSupported - Operation not supported
  • InternalError - Server internal error
  • ProtocolError - Protocol violation
  • SecurityError - Security validation failed
  • FormationViolation - Message format invalid
  • PropertyConstraintViolation - Property validation failed
  • OccurenceConstraintViolation - Required field missing
  • TypeConstraintViolation - Data type mismatch
  • GenericError - Catch-all error

πŸ”§ Error Handling Examples

WebSocket Connection Errors

// Node.js Error Handling
websocket.on("error", (error) => {
	console.error("🚨 WebSocket Error:", error.message);

	switch (error.code) {
		case "ECONNREFUSED":
			console.log("πŸ”„ Attempting reconnection in 30 seconds...");
			setTimeout(() => reconnect(), 30000);
			break;
		case "ENOTFOUND":
			console.error("❌ Server not found. Check URL and DNS.");
			break;
		default:
			console.error("❌ Unknown connection error:", error);
	}
});

Message Validation Errors

// CALLERROR Response
[4, "12345", "FormationViolation", "Invalid timestamp format", {}]

Transaction State Errors

// Handle transaction state conflicts
if (request.action === "StartTransaction") {
	if (connector.status !== "Available") {
		return [
			4,
			messageId,
			"GenericError",
			`Connector ${connectorId} not available. Current status: ${connector.status}`,
			{},
		];
	}
}

πŸ› οΈ Troubleshooting Guide

Connection Issues

Problem: WebSocket connection fails

# Check connectivity
ping your-ocpp-server.com
telnet your-ocpp-server.com 8080

Solutions:

  1. Verify server URL and port
  2. Check firewall settings
  3. Validate network connectivity
  4. Test with different security profile

Authentication Issues

Problem: BootNotification rejected

{
	"status": "Rejected",
	"currentTime": "2024-01-15T10:30:00.000Z",
	"interval": 86400
}

Solutions:

  1. Check charge point model/vendor in whitelist
  2. Validate serial number format
  3. Verify security credentials
  4. Check server configuration

Transaction Problems

Problem: StartTransaction fails

// Common validation checks
function validateStartTransaction(request) {
	const errors = [];

	if (!request.idTag || request.idTag.length > 20) {
		errors.push("Invalid idTag length");
	}

	if (!request.meterStart || request.meterStart < 0) {
		errors.push("Invalid meterStart value");
	}

	if (!request.timestamp || !Date.parse(request.timestamp)) {
		errors.push("Invalid timestamp format");
	}

	return errors;
}

MeterValues Issues

Problem: MeterValues rejected or ignored

// Ensure proper sampledValue structure
{
	"meterValue": [
		{
			"timestamp": "2024-01-15T10:30:00.000Z",
			"sampledValue": [
				{
					"value": "15.6",
					"measurand": "Power.Active.Import",
					"unit": "kW",
					"context": "Sample.Periodic"
				}
			]
		}
	]
}

Common Issues:

  • Missing required fields
  • Invalid measurand values
  • Incorrect timestamp format
  • Wrong unit for measurand
  • Invalid numeric values

πŸ† Best Practices & Optimization

πŸš€ Performance Best Practices

Efficient Message Handling

// Use message queuing for high-frequency data
class MessageQueue {
	constructor(maxSize = 1000) {
		this.queue = [];
		this.maxSize = maxSize;
		this.processing = false;
	}

	async enqueue(message) {
		if (this.queue.length >= this.maxSize) {
			console.warn("⚠️ Message queue full, dropping oldest message");
			this.queue.shift();
		}

		this.queue.push(message);

		if (!this.processing) {
			await this.processQueue();
		}
	}

	async processQueue() {
		this.processing = true;

		while (this.queue.length > 0) {
			const message = this.queue.shift();
			try {
				await this.sendMessage(message);
				await new Promise((resolve) => setTimeout(resolve, 100)); // Rate limiting
			} catch (error) {
				console.error("Failed to send message:", error);
				// Re-queue on failure
				this.queue.unshift(message);
				break;
			}
		}

		this.processing = false;
	}
}

Optimize MeterValues Frequency

// Smart meter value reporting
class SmartMeterReporting {
	constructor() {
		this.lastValues = {};
		this.thresholds = {
			"Power.Active.Import": 1.0, // 1kW threshold
			"Current.Import": 5.0, // 5A threshold
			Voltage: 10.0, // 10V threshold
		};
	}

	shouldSendMeterValue(measurand, newValue) {
		const lastValue = this.lastValues[measurand] || 0;
		const threshold = this.thresholds[measurand] || 1.0;

		// Send if change exceeds threshold or 5 minutes passed
		const timeSinceLastSend = Date.now() - (this.lastSent || 0);
		const significantChange = Math.abs(newValue - lastValue) > threshold;
		const timeExpired = timeSinceLastSend > 300000; // 5 minutes

		return significantChange || timeExpired;
	}
}

πŸ” Security Best Practices

Input Validation

// Comprehensive input validation
function validateOCPPMessage(message) {
	const [messageType, messageId, action, payload] = message;

	// Basic structure validation
	if (!Array.isArray(message) || message.length < 3) {
		throw new Error("Invalid message structure");
	}

	// Message type validation
	if (![2, 3, 4].includes(messageType)) {
		throw new Error("Invalid message type");
	}

	// Message ID validation
	if (typeof messageId !== "string" || messageId.length === 0) {
		throw new Error("Invalid message ID");
	}

	// Action validation for CALL messages
	if (messageType === 2) {
		if (typeof action !== "string" || !VALID_ACTIONS.includes(action)) {
			throw new Error(`Invalid action: ${action}`);
		}
	}

	// Payload validation
	if (typeof payload !== "object" || payload === null) {
		throw new Error("Invalid payload");
	}

	return true;
}

Secure Configuration

// Secure WebSocket configuration
const secureConfig = {
	// Enable certificate validation
	rejectUnauthorized: true,

	// Set connection timeout
	handshakeTimeout: 30000,

	// Enable compression
	perMessageDeflate: true,

	// Set frame size limits
	maxReceivedFrameSize: 131072,
	maxReceivedMessageSize: 1048576,

	// Rate limiting
	maxConnections: 100,

	// Authentication
	verifyClient: (info) => {
		// Implement custom authentication logic
		return validateClientCertificate(info.req);
	},
};

πŸ“Š Monitoring & Diagnostics

Health Check Implementation

// Health monitoring system
class HealthMonitor {
	constructor() {
		this.metrics = {
			messagesSent: 0,
			messagesReceived: 0,
			errors: 0,
			lastHeartbeat: null,
			connectionUptime: Date.now(),
		};

		setInterval(() => this.reportHealth(), 60000); // Every minute
	}

	recordMessage(type, success = true) {
		if (type === "sent") this.metrics.messagesSent++;
		if (type === "received") this.metrics.messagesReceived++;
		if (!success) this.metrics.errors++;
	}

	recordHeartbeat() {
		this.metrics.lastHeartbeat = Date.now();
	}

	getHealthStatus() {
		const now = Date.now();
		const timeSinceHeartbeat = now - (this.metrics.lastHeartbeat || 0);

		return {
			status: timeSinceHeartbeat < 120000 ? "healthy" : "warning",
			uptime: now - this.metrics.connectionUptime,
			metrics: this.metrics,
			lastHeartbeat: timeSinceHeartbeat,
		};
	}

	reportHealth() {
		const health = this.getHealthStatus();
		console.log("πŸ₯ Health Status:", health);

		// Send to monitoring system
		// sendToMonitoring(health);
	}
}

Prometheus Metrics

// Prometheus metrics for OCPP monitoring
const prometheus = require("prom-client");

// Custom metrics
const activeConnections = new prometheus.Gauge({
	name: "ocpp_active_connections",
	help: "Number of active WebSocket connections",
});

const messagesTotal = new prometheus.Counter({
	name: "ocpp_messages_total",
	help: "Total number of OCPP messages processed",
	labelNames: ["action", "status", "charge_point_id"],
});

const messageProcessingDuration = new prometheus.Histogram({
	name: "ocpp_message_processing_duration_seconds",
	help: "Time spent processing OCPP messages",
	labelNames: ["action"],
});

const transactionsActive = new prometheus.Gauge({
	name: "ocpp_transactions_active",
	help: "Number of active charging transactions",
});

// Usage in application
function recordMessage(action, status, chargePointId, duration) {
	messagesTotal.inc({ action, status, charge_point_id: chargePointId });
	messageProcessingDuration.observe({ action }, duration);
}

function updateActiveConnections(count) {
	activeConnections.set(count);
}

Health Check Endpoints

// Health check implementation
app.get('/health', (req, res) => {
    const health = {
        status: 'healthy',
        timestamp: new Date().toISOString(),
        uptime: process.uptime(),
        memory: process.memoryUsage(),
        connections: getActiveConnectionCount(),
        version: process.env.APP_VERSION || '1.0.0'
    };

    // Check database connectivity
    const dbStatus = await checkDatabaseHealth();
    health.database = dbStatus;

    // Check Redis connectivity
    const redisStatus = await checkRedisHealth();
    health.redis = redisStatus;

    const overallStatus = dbStatus.status === 'healthy' && redisStatus.status === 'healthy'
        ? 'healthy' : 'unhealthy';

    res.status(overallStatus === 'healthy' ? 200 : 503).json(health);
});

app.get('/ready', (req, res) => {
    // Readiness check for Kubernetes
    const ready = {
        status: 'ready',
        services: {
            database: await checkDatabaseHealth(),
            redis: await checkRedisHealth(),
            websocket: { status: 'healthy' }
        }
    };

    const allReady = Object.values(ready.services)
        .every(service => service.status === 'healthy');

    res.status(allReady ? 200 : 503).json(ready);
});

πŸ”§ Configuration Management

Environment-Based Configuration

// Configuration management
const config = {
	development: {
		websocket: {
			port: 8080,
			timeout: 30000,
			maxConnections: 100,
		},
		database: {
			url:
				process.env.DATABASE_URL ||
				"postgresql://localhost:5432/ocpp_dev",
			pool: { min: 2, max: 10 },
		},
		logging: {
			level: "debug",
			pretty: true,
		},
	},

	production: {
		websocket: {
			port: process.env.PORT || 8080,
			timeout: 60000,
			maxConnections: 1000,
			ssl: {
				cert: process.env.SSL_CERT_PATH,
				key: process.env.SSL_KEY_PATH,
			},
		},
		database: {
			url: process.env.DATABASE_URL,
			pool: { min: 10, max: 50 },
			ssl: { rejectUnauthorized: false },
		},
		logging: {
			level: "info",
			pretty: false,
			file: "/app/logs/ocpp.log",
		},
	},
};

const currentConfig = config[process.env.NODE_ENV || "development"];

🎯 Summary

βœ… Complete OCPP 1.6 Coverage

This guide provides complete coverage of all OCPP 1.6 features:

πŸ“Š 10 Charge Point Events

  • βœ… Heartbeat, BootNotification, StatusNotification
  • βœ… StartTransaction, StopTransaction, MeterValues
  • βœ… Authorize, DataTransfer, FirmwareStatusNotification, DiagnosticsStatusNotification

🎯 19 Central System Events

  • βœ… Reset, UnlockConnector, RemoteStartTransaction, RemoteStopTransaction
  • βœ… ChangeConfiguration, GetConfiguration, GetDiagnostics, UpdateFirmware
  • βœ… ReserveNow, CancelReservation, ClearCache, GetLocalListVersion, SendLocalList
  • βœ… SetChargingProfile, ClearChargingProfile, GetCompositeSchedule
  • βœ… TriggerMessage, ChangeAvailability, DataTransfer

πŸ›‘οΈ 4 Security Profiles

  • βœ… Profile 0 (No Security), Profile 1 (Basic Auth)
  • βœ… Profile 2 (Client Certificates), Profile 3 (Advanced)

πŸ’» Production-Ready Features

  • βœ… Complete Node.js and Python implementations
  • βœ… Error handling and troubleshooting guides
  • βœ… Schema compliance and validation
  • βœ… Deployment and monitoring guidelines
  • βœ… Best practices and optimization tips

πŸš€ Ready for Production

This guide equips you with everything needed to build robust, scalable OCPP 1.6 implementations that handle real-world charging scenarios, edge cases, and production requirements.

Happy Charging! βš‘πŸš—πŸ’š

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment