Skip to content

Commit 82cbbef

Browse files
andigclaude
andcommitted
feat: Add OCPP 2.1 support (Phase B - Core foundation)
Initial implementation of OCPP 2.1 protocol support including: **Types package (ocpp2.1/types/):** - CustomDataType for vendor extensions (new in 2.1) - Extended AuthorizationStatus, IdTokenType enums - ChargingProfile types with new PriorityCharging, LocalGeneration, Dynamic - MeterValue/SampledValue with 50+ Measurand values - EnergyTransferMode enum (11 modes including BPT, DER, WPT) - AuthorizeCertificateStatus enum (7 values) - All validators registered with "21" suffix **Provisioning package:** - BootNotification, Reset (with ImmediateAndResume) - GetBaseReport, GetReport, NotifyReport - GetVariables, SetVariables - SetNetworkProfile (with identity, basicAuthPassword fields) **Authorization package:** - Authorize (with certificate support, AllowedEnergyTransfer) - ClearCache **Availability package:** - Heartbeat, StatusNotification, ChangeAvailability **Data package:** - DataTransfer All types conform to OCPP 2.1 JSON schemas with correct: - Field names and JSON tags - MaxLength constraints - Required/optional field handling - Enum values Closes #337 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent b7f92ee commit 82cbbef

22 files changed

Lines changed: 2612 additions & 0 deletions
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// Package authorization contains the authorization functional block for OCPP 2.1.
2+
// It contains different ways of authorizing a user, online and/or offline.
3+
package authorization
4+
5+
import "github.com/lorenzodonini/ocpp-go/ocpp"
6+
7+
// ProfileName is the name of the authorization functional block.
8+
const ProfileName = "authorization"
9+
10+
// ChargingStationHandler contains callbacks for handling incoming CSMS requests.
11+
type ChargingStationHandler interface {
12+
// OnClearCache handles a ClearCacheRequest from the CSMS.
13+
OnClearCache(request *ClearCacheRequest) (response *ClearCacheResponse, err error)
14+
}
15+
16+
// CSMSHandler contains callbacks for handling incoming Charging Station requests.
17+
type CSMSHandler interface {
18+
// OnAuthorize handles an AuthorizeRequest from a Charging Station.
19+
OnAuthorize(chargingStationID string, request *AuthorizeRequest) (response *AuthorizeResponse, err error)
20+
}
21+
22+
// Profile returns the authorization profile with all features.
23+
func Profile() *ocpp.Profile {
24+
return ocpp.NewProfile(
25+
ProfileName,
26+
AuthorizeFeature{},
27+
ClearCacheFeature{},
28+
)
29+
}

ocpp2.1/authorization/authorize.go

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package authorization
2+
3+
import (
4+
"reflect"
5+
6+
"github.com/lorenzodonini/ocpp-go/ocpp2.1/types"
7+
)
8+
9+
// -------------------- Authorize (CS -> CSMS) --------------------
10+
11+
const AuthorizeFeatureName = "Authorize"
12+
13+
// AuthorizeRequest is the payload for an Authorize request from CS to CSMS.
14+
type AuthorizeRequest struct {
15+
IdToken types.IdToken `json:"idToken" validate:"required"`
16+
Certificate string `json:"certificate,omitempty" validate:"omitempty,max=10000"`
17+
ISO15118CertificateHashData []types.OCSPRequestDataType `json:"iso15118CertificateHashData,omitempty" validate:"omitempty,max=4,dive"`
18+
CustomData *types.CustomDataType `json:"customData,omitempty" validate:"omitempty"`
19+
}
20+
21+
// AuthorizeResponse is the payload for an Authorize response from CSMS to CS.
22+
type AuthorizeResponse struct {
23+
CertificateStatus types.AuthorizeCertificateStatus `json:"certificateStatus,omitempty" validate:"omitempty,authorizeCertificateStatus21"`
24+
IdTokenInfo types.IdTokenInfo `json:"idTokenInfo" validate:"required"`
25+
AllowedEnergyTransfer []types.EnergyTransferMode `json:"allowedEnergyTransfer,omitempty" validate:"omitempty,dive,energyTransferMode21"`
26+
CustomData *types.CustomDataType `json:"customData,omitempty" validate:"omitempty"`
27+
}
28+
29+
// AuthorizeFeature represents the Authorize feature.
30+
type AuthorizeFeature struct{}
31+
32+
func (f AuthorizeFeature) GetFeatureName() string {
33+
return AuthorizeFeatureName
34+
}
35+
36+
func (f AuthorizeFeature) GetRequestType() reflect.Type {
37+
return reflect.TypeOf(AuthorizeRequest{})
38+
}
39+
40+
func (f AuthorizeFeature) GetResponseType() reflect.Type {
41+
return reflect.TypeOf(AuthorizeResponse{})
42+
}
43+
44+
func (r AuthorizeRequest) GetFeatureName() string {
45+
return AuthorizeFeatureName
46+
}
47+
48+
func (c AuthorizeResponse) GetFeatureName() string {
49+
return AuthorizeFeatureName
50+
}
51+
52+
// NewAuthorizeRequest creates a new AuthorizeRequest.
53+
func NewAuthorizeRequest(idToken string, tokenType types.IdTokenType) *AuthorizeRequest {
54+
return &AuthorizeRequest{IdToken: types.IdToken{IdToken: idToken, Type: tokenType}}
55+
}
56+
57+
// NewAuthorizeResponse creates a new AuthorizeResponse.
58+
func NewAuthorizeResponse(idTokenInfo types.IdTokenInfo) *AuthorizeResponse {
59+
return &AuthorizeResponse{IdTokenInfo: idTokenInfo}
60+
}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
package authorization
2+
3+
import (
4+
"reflect"
5+
6+
"gopkg.in/go-playground/validator.v9"
7+
8+
"github.com/lorenzodonini/ocpp-go/ocpp2.1/types"
9+
)
10+
11+
// -------------------- Clear Cache (CSMS -> CS) --------------------
12+
13+
const ClearCacheFeatureName = "ClearCache"
14+
15+
// ClearCacheStatus represents the status of a clear cache request.
16+
type ClearCacheStatus string
17+
18+
const (
19+
ClearCacheStatusAccepted ClearCacheStatus = "Accepted"
20+
ClearCacheStatusRejected ClearCacheStatus = "Rejected"
21+
)
22+
23+
func isValidClearCacheStatus(fl validator.FieldLevel) bool {
24+
s := ClearCacheStatus(fl.Field().String())
25+
switch s {
26+
case ClearCacheStatusAccepted, ClearCacheStatusRejected:
27+
return true
28+
default:
29+
return false
30+
}
31+
}
32+
33+
// ClearCacheRequest is the payload for a ClearCache request from CSMS to CS.
34+
type ClearCacheRequest struct {
35+
CustomData *types.CustomDataType `json:"customData,omitempty" validate:"omitempty"`
36+
}
37+
38+
// ClearCacheResponse is the payload for a ClearCache response from CS to CSMS.
39+
type ClearCacheResponse struct {
40+
Status ClearCacheStatus `json:"status" validate:"required,clearCacheStatus21"`
41+
StatusInfo *types.StatusInfo `json:"statusInfo,omitempty" validate:"omitempty"`
42+
CustomData *types.CustomDataType `json:"customData,omitempty" validate:"omitempty"`
43+
}
44+
45+
// ClearCacheFeature represents the ClearCache feature.
46+
type ClearCacheFeature struct{}
47+
48+
func (f ClearCacheFeature) GetFeatureName() string {
49+
return ClearCacheFeatureName
50+
}
51+
52+
func (f ClearCacheFeature) GetRequestType() reflect.Type {
53+
return reflect.TypeOf(ClearCacheRequest{})
54+
}
55+
56+
func (f ClearCacheFeature) GetResponseType() reflect.Type {
57+
return reflect.TypeOf(ClearCacheResponse{})
58+
}
59+
60+
func (r ClearCacheRequest) GetFeatureName() string {
61+
return ClearCacheFeatureName
62+
}
63+
64+
func (c ClearCacheResponse) GetFeatureName() string {
65+
return ClearCacheFeatureName
66+
}
67+
68+
// NewClearCacheRequest creates a new ClearCacheRequest.
69+
func NewClearCacheRequest() *ClearCacheRequest {
70+
return &ClearCacheRequest{}
71+
}
72+
73+
// NewClearCacheResponse creates a new ClearCacheResponse.
74+
func NewClearCacheResponse(status ClearCacheStatus) *ClearCacheResponse {
75+
return &ClearCacheResponse{Status: status}
76+
}
77+
78+
func init() {
79+
_ = types.Validate.RegisterValidation("clearCacheStatus21", isValidClearCacheStatus)
80+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// Package availability contains the availability functional block for OCPP 2.1.
2+
// It contains features for notifying the CSMS of availability and status changes.
3+
package availability
4+
5+
import "github.com/lorenzodonini/ocpp-go/ocpp"
6+
7+
// ProfileName is the name of the availability functional block.
8+
const ProfileName = "availability"
9+
10+
// ChargingStationHandler contains callbacks for handling incoming CSMS requests.
11+
type ChargingStationHandler interface {
12+
// OnChangeAvailability handles a ChangeAvailabilityRequest from the CSMS.
13+
OnChangeAvailability(request *ChangeAvailabilityRequest) (response *ChangeAvailabilityResponse, err error)
14+
}
15+
16+
// CSMSHandler contains callbacks for handling incoming Charging Station requests.
17+
type CSMSHandler interface {
18+
// OnHeartbeat handles a HeartbeatRequest from a Charging Station.
19+
OnHeartbeat(chargingStationID string, request *HeartbeatRequest) (response *HeartbeatResponse, err error)
20+
// OnStatusNotification handles a StatusNotificationRequest from a Charging Station.
21+
OnStatusNotification(chargingStationID string, request *StatusNotificationRequest) (response *StatusNotificationResponse, err error)
22+
}
23+
24+
// Profile returns the availability profile with all features.
25+
func Profile() *ocpp.Profile {
26+
return ocpp.NewProfile(
27+
ProfileName,
28+
ChangeAvailabilityFeature{},
29+
HeartbeatFeature{},
30+
StatusNotificationFeature{},
31+
)
32+
}
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
package availability
2+
3+
import (
4+
"reflect"
5+
6+
"gopkg.in/go-playground/validator.v9"
7+
8+
"github.com/lorenzodonini/ocpp-go/ocpp2.1/types"
9+
)
10+
11+
// -------------------- Change Availability (CSMS -> CS) --------------------
12+
13+
const ChangeAvailabilityFeatureName = "ChangeAvailability"
14+
15+
// OperationalStatus represents the requested operational status.
16+
type OperationalStatus string
17+
18+
const (
19+
OperationalStatusInoperative OperationalStatus = "Inoperative"
20+
OperationalStatusOperative OperationalStatus = "Operative"
21+
)
22+
23+
func isValidOperationalStatus(fl validator.FieldLevel) bool {
24+
s := OperationalStatus(fl.Field().String())
25+
switch s {
26+
case OperationalStatusInoperative, OperationalStatusOperative:
27+
return true
28+
default:
29+
return false
30+
}
31+
}
32+
33+
// ChangeAvailabilityStatus represents the response status.
34+
type ChangeAvailabilityStatus string
35+
36+
const (
37+
ChangeAvailabilityStatusAccepted ChangeAvailabilityStatus = "Accepted"
38+
ChangeAvailabilityStatusRejected ChangeAvailabilityStatus = "Rejected"
39+
ChangeAvailabilityStatusScheduled ChangeAvailabilityStatus = "Scheduled"
40+
)
41+
42+
func isValidChangeAvailabilityStatus(fl validator.FieldLevel) bool {
43+
s := ChangeAvailabilityStatus(fl.Field().String())
44+
switch s {
45+
case ChangeAvailabilityStatusAccepted, ChangeAvailabilityStatusRejected, ChangeAvailabilityStatusScheduled:
46+
return true
47+
default:
48+
return false
49+
}
50+
}
51+
52+
// ChangeAvailabilityRequest is the payload for a ChangeAvailability request from CSMS to CS.
53+
type ChangeAvailabilityRequest struct {
54+
OperationalStatus OperationalStatus `json:"operationalStatus" validate:"required,operationalStatus21"`
55+
EVSE *types.EVSE `json:"evse,omitempty" validate:"omitempty"`
56+
CustomData *types.CustomDataType `json:"customData,omitempty" validate:"omitempty"`
57+
}
58+
59+
// ChangeAvailabilityResponse is the payload for a ChangeAvailability response from CS to CSMS.
60+
type ChangeAvailabilityResponse struct {
61+
Status ChangeAvailabilityStatus `json:"status" validate:"required,changeAvailabilityStatus21"`
62+
StatusInfo *types.StatusInfo `json:"statusInfo,omitempty" validate:"omitempty"`
63+
CustomData *types.CustomDataType `json:"customData,omitempty" validate:"omitempty"`
64+
}
65+
66+
// ChangeAvailabilityFeature represents the ChangeAvailability feature.
67+
type ChangeAvailabilityFeature struct{}
68+
69+
func (f ChangeAvailabilityFeature) GetFeatureName() string {
70+
return ChangeAvailabilityFeatureName
71+
}
72+
73+
func (f ChangeAvailabilityFeature) GetRequestType() reflect.Type {
74+
return reflect.TypeOf(ChangeAvailabilityRequest{})
75+
}
76+
77+
func (f ChangeAvailabilityFeature) GetResponseType() reflect.Type {
78+
return reflect.TypeOf(ChangeAvailabilityResponse{})
79+
}
80+
81+
func (r ChangeAvailabilityRequest) GetFeatureName() string {
82+
return ChangeAvailabilityFeatureName
83+
}
84+
85+
func (c ChangeAvailabilityResponse) GetFeatureName() string {
86+
return ChangeAvailabilityFeatureName
87+
}
88+
89+
// NewChangeAvailabilityRequest creates a new ChangeAvailabilityRequest.
90+
func NewChangeAvailabilityRequest(operationalStatus OperationalStatus) *ChangeAvailabilityRequest {
91+
return &ChangeAvailabilityRequest{OperationalStatus: operationalStatus}
92+
}
93+
94+
// NewChangeAvailabilityResponse creates a new ChangeAvailabilityResponse.
95+
func NewChangeAvailabilityResponse(status ChangeAvailabilityStatus) *ChangeAvailabilityResponse {
96+
return &ChangeAvailabilityResponse{Status: status}
97+
}
98+
99+
func init() {
100+
_ = types.Validate.RegisterValidation("operationalStatus21", isValidOperationalStatus)
101+
_ = types.Validate.RegisterValidation("changeAvailabilityStatus21", isValidChangeAvailabilityStatus)
102+
}

ocpp2.1/availability/heartbeat.go

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package availability
2+
3+
import (
4+
"reflect"
5+
6+
"github.com/lorenzodonini/ocpp-go/ocpp2.1/types"
7+
)
8+
9+
// -------------------- Heartbeat (CS -> CSMS) --------------------
10+
11+
const HeartbeatFeatureName = "Heartbeat"
12+
13+
// HeartbeatRequest is the payload for a Heartbeat request from CS to CSMS.
14+
type HeartbeatRequest struct {
15+
CustomData *types.CustomDataType `json:"customData,omitempty" validate:"omitempty"`
16+
}
17+
18+
// HeartbeatResponse is the payload for a Heartbeat response from CSMS to CS.
19+
type HeartbeatResponse struct {
20+
CurrentTime types.DateTime `json:"currentTime" validate:"required"`
21+
CustomData *types.CustomDataType `json:"customData,omitempty" validate:"omitempty"`
22+
}
23+
24+
// HeartbeatFeature represents the Heartbeat feature.
25+
type HeartbeatFeature struct{}
26+
27+
func (f HeartbeatFeature) GetFeatureName() string {
28+
return HeartbeatFeatureName
29+
}
30+
31+
func (f HeartbeatFeature) GetRequestType() reflect.Type {
32+
return reflect.TypeOf(HeartbeatRequest{})
33+
}
34+
35+
func (f HeartbeatFeature) GetResponseType() reflect.Type {
36+
return reflect.TypeOf(HeartbeatResponse{})
37+
}
38+
39+
func (r HeartbeatRequest) GetFeatureName() string {
40+
return HeartbeatFeatureName
41+
}
42+
43+
func (c HeartbeatResponse) GetFeatureName() string {
44+
return HeartbeatFeatureName
45+
}
46+
47+
// NewHeartbeatRequest creates a new HeartbeatRequest.
48+
func NewHeartbeatRequest() *HeartbeatRequest {
49+
return &HeartbeatRequest{}
50+
}
51+
52+
// NewHeartbeatResponse creates a new HeartbeatResponse.
53+
func NewHeartbeatResponse(currentTime types.DateTime) *HeartbeatResponse {
54+
return &HeartbeatResponse{CurrentTime: currentTime}
55+
}

0 commit comments

Comments
 (0)