+
Skip to content

❔ Question: Proposal for processing CustomData in OCPP 2.x. #384

@sderkacs

Description

@sderkacs

❔ What is your question?

Sorry for reopening this topic.

I read the previous discussion, but I feel like there's some misunderstanding. There's no need to "catch" custom fields directly in requests/responses. The JSON schema for OCPP (both 2.0 and 2.1) defines CustomData as a separate type: CustomDataType.

"CustomDataType": {
  "description": "This class does not get 'AdditionalProperties = false' in the schema generation, so it can be extended with arbitrary JSON properties to allow adding custom data.",
  "javaType": "CustomData",
  "type": "object",
  "properties": {
    "vendorId": {
      "type": "string",
      "maxLength": 255
    }
  },
  "required": [
    "vendorId"
  ]
}

Accordingly, other data types that can contain custom data should have an optional "customData" field. (e.g. AuthorizeRequest)

{
  "$schema": "http://json-schema.org/draft-06/schema#",
  "$id": "urn:OCPP:Cp:2:2020:3:AuthorizeRequest",
  "comment": "OCPP 2.0.1 FINAL",
  "definitions": {
    // ...
  },
  "type": "object",
  "additionalProperties": false,
  "properties": {
    "customData": {
      "$ref": "#/definitions/CustomDataType"
    },
    "idToken": {
      "$ref": "#/definitions/IdTokenType"
    },
    "certificate": {
      "description": "The X.509 certificated presented by EV and encoded in PEM format.\r\n",
      "type": "string",
      "maxLength": 5500
    },
    "iso15118CertificateHashData": {
      "type": "array",
      "additionalItems": false,
      "items": {
        "$ref": "#/definitions/OCSPRequestDataType"
      },
      "minItems": 1,
      "maxItems": 4
    }
  },
  "required": [
    "idToken"
  ]
}

Example of custom data from the OCPP specification (OCPP 2.0.1 Part 4, Chapter 9):

{
  "customData": {
    "vendorId": "com.mycompany.customheartbeat",
    "mainMeterValue": 12345,
    "sessionsToDate": 342
  }
}

Therefore, special handling (custom marshaler and unmarshaler) should only be implemented for CustomDataType.

Possible implementation of CustomDataType:

type CustomData struct {
	VendorId string         `json:"vendorId" validate:"required,max=255"`
	Values   map[string]any `json:"-"` // Ignore during default JSON unmarshaling
}

func (c *CustomData) UnmarshalJSON(data []byte) error {
	raw := make(map[string]interface{})

	// Unmarshal into raw map
	if err := json.Unmarshal(data, &raw); err != nil {
		return err
	}

	// Do not return any error here. Will fail later during validation.
	rawVendorId := raw["vendorId"]
	vendorId := ""
	if rawVendorId != nil {
		s, ok := rawVendorId.(string)
		if ok {
			vendorId = s
		}
	}

	*c = CustomData{
		VendorId: vendorId,
		Values:   raw,
	}

	// Remove vendorId from Values to avoid duplication
	delete(c.Values, "vendorId")

	return nil
}

func (c CustomData) MarshalJSON() ([]byte, error) {
	output := make(map[string]interface{})

	output["vendorId"] = c.VendorId
	for k, v := range c.Values {
		output[k] = v
	}

	return json.Marshal(output)
}

Modified AuthorizeRequest:

// The field definition of the Authorize request payload sent by the Charging Station to the CSMS.
type AuthorizeRequest struct {
	Certificate         string                      `json:"certificate,omitempty" validate:"max=5500"`
	IdToken             types.IdToken               `json:"idToken" validate:"required"`
	CertificateHashData []types.OCSPRequestDataType `json:"iso15118CertificateHashData,omitempty" validate:"max=4,dive"`
	CustomData          *types.CustomData           `json:"customData,omitempty" validate:"omitempty"`
}

I tested this approach on examples from the project, and everything works perfectly. This change also doesn't break the tests.

Example of a validation error with an empty vendorId:

INFO[2025-10-07T00:19:50+03:00] connected to CSMS at ws://localhost:8887
INFO[2025-10-07T00:19:50+03:00] dispatched request 1363214786 to server
INFO[2025-10-07T00:19:50+03:00] status: Accepted, interval: 600, current time: 2025-10-06 21:19:50 +0000 UTC  message=BootNotification
INFO[2025-10-07T00:19:50+03:00] operational status for evse 1 updated to: Operative
INFO[2025-10-07T00:19:50+03:00] dispatched request 3437763191 to server
INFO[2025-10-07T00:19:50+03:00] status for evse 1 - connector 0 updated to: Available  message=StatusNotification
INFO[2025-10-07T00:19:52+03:00] reservation 42 accepted for evse 1, connector 0  message=ReserveNow
INFO[2025-10-07T00:19:52+03:00] dispatched request 1561461413 to server
INFO[2025-10-07T00:19:52+03:00] status for evse 1 - connector 0 updated to: Reserved  message=StatusNotification
INFO[2025-10-07T00:19:53+03:00] reservation 42 for evse 1 canceled            message=CancelReservation
INFO[2025-10-07T00:19:53+03:00] dispatched request 1968302455 to server
INFO[2025-10-07T00:19:53+03:00] status for evse 1 - connector 0 updated to: Available  message=StatusNotification
INFO[2025-10-07T00:19:55+03:00] dispatched request 1681938932 to server
INFO[2025-10-07T00:19:55+03:00] status for evse 1 - connector 0 updated to: Occupied  message=StatusNotification
INFO[2025-10-07T00:19:55+03:00] dispatched request 4150724469 to server
INFO[2025-10-07T00:19:55+03:00] transaction 3DAA0EE9-4A49-62BF-954F-7779A5C1E666 started  message=TransactionEvent
INFO[2025-10-07T00:19:55+03:00] dispatched request 2999641483 to server
FATA[2025-10-07T00:19:55+03:00] ocpp message (2999641483): OccurrenceConstraintViolation - Field CallResult.Payload.CustomData.VendorId required but not found for feature Authorize

I also looked at other OCPP implementations (libocpp, ChargeTimeEU), and everything is the same.

If there is interest in this and this solution suits everyone, I can prepare a feature request for all types that can have custom data.

Which OCPP version referring to?

  • OCPP 1.6
  • OCPP 2.0.1

Are you using any OCPP extensions?

  • OCPP 1.6 Security
  • OCPP 1.6 Plug and Charge

👀 Have you spent some time to check if this question has been asked before?

  • I checked and didn't find a similar issue

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions

      点击 这是indexloc提供的php浏览器服务,不要输入任何密码和下载