├── logger └── log.go ├── go.mod ├── messages_test.go ├── LICENSE ├── examples ├── client │ └── client.go └── server │ └── server.go ├── go.sum ├── client.go ├── v16 ├── datatypes.go ├── call_result.go ├── call.go └── validation_register.go ├── CONTRIBUTING.md ├── server.go ├── README.md ├── v201 ├── call_result.go ├── call.go ├── datatypes.go └── validation_register.go ├── charge_point.go └── messages.go /logger/log.go: -------------------------------------------------------------------------------- 1 | package logger 2 | 3 | 4 | 5 | 6 | 7 | 8 | type Logger interface { 9 | Debug(args ...interface{}) 10 | Debugf(format string, args ...interface{}) 11 | Error(args ...interface{}) 12 | Errorf(format string, args ...interface{}) 13 | } 14 | 15 | 16 | 17 | type EmptyLogger struct {} 18 | 19 | func (l *EmptyLogger) Debug(args ...interface{}) {} 20 | func (l *EmptyLogger) Debugf(format string, args ...interface{}) {} 21 | func (l *EmptyLogger) Error(args ...interface{}) {} 22 | func (l *EmptyLogger) Errorf(format string, args ...interface{}) {} 23 | 24 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/aliml92/ocpp 2 | 3 | go 1.18 4 | 5 | retract v1.0.0 6 | 7 | require ( 8 | github.com/google/uuid v1.3.0 9 | github.com/gorilla/websocket v1.5.0 10 | gopkg.in/go-playground/validator.v9 v9.31.0 11 | ) 12 | 13 | require ( 14 | go.uber.org/atomic v1.7.0 // indirect 15 | go.uber.org/multierr v1.6.0 // indirect 16 | ) 17 | 18 | require ( 19 | github.com/go-playground/locales v0.14.0 // indirect 20 | github.com/go-playground/universal-translator v0.18.0 // indirect 21 | github.com/leodido/go-urn v1.2.1 // indirect 22 | go.uber.org/zap v1.23.0 23 | gopkg.in/go-playground/assert.v1 v1.2.1 // indirect 24 | ) 25 | -------------------------------------------------------------------------------- /messages_test.go: -------------------------------------------------------------------------------- 1 | package ocpp 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | type RawMessage struct { 9 | data []byte 10 | proto string 11 | } 12 | 13 | 14 | // Add more test cases 15 | func TestUnpack(t *testing.T) { 16 | cases := []struct{ 17 | name string 18 | rawMsg RawMessage 19 | want1 OcppMessage 20 | want2 error 21 | }{ 22 | { "non array json", 23 | RawMessage{ []byte(`{"some": "data"}`),"ocppv16",}, 24 | nil, 25 | &ocppError{ 26 | id: "-1", 27 | code: "ProtocolError", 28 | cause: "Invalid JSON format", 29 | }, 30 | }, 31 | 32 | } 33 | 34 | for _, v := range cases { 35 | t.Run(v.name, func(t *testing.T) { 36 | got1, got2 := unpack(v.rawMsg.data, v.rawMsg.proto) 37 | 38 | if got1 != v.want1{ 39 | t.Errorf("got %v want %v", got1, v.want1) 40 | } 41 | 42 | if !reflect.DeepEqual(got2, v.want2) { 43 | t.Errorf("got %v want %v", got2, v.want2) 44 | } 45 | }) 46 | } 47 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Alisher Muzaffarov 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /examples/client/client.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | 7 | "net/http" 8 | _ "net/http/pprof" 9 | 10 | "github.com/aliml92/ocpp" 11 | v16 "github.com/aliml92/ocpp/v16" 12 | "go.uber.org/zap" 13 | ) 14 | 15 | 16 | 17 | var client *ocpp.Client 18 | 19 | // log replaces standard log 20 | var log *zap.SugaredLogger 21 | 22 | // initialize zap logger 23 | // for deveplopment only 24 | func initLogger() { 25 | logger, _ := zap.NewDevelopment() 26 | log = logger.Sugar() 27 | } 28 | 29 | func main() { 30 | go func() { 31 | log.Debugln(http.ListenAndServe("localhost:5050", nil)) 32 | }() 33 | // initialize logger 34 | initLogger() 35 | defer log.Sync() 36 | 37 | // set ocpp library's logger to zap logger 38 | ocpp.SetLogger(log) 39 | 40 | // create client 41 | client = ocpp.NewClient() 42 | id := "client00" 43 | client.SetID(id) 44 | client.AddSubProtocol("ocpp1.6") 45 | client.SetBasicAuth(id, "dummypass") 46 | client.SetCallQueueSize(32) 47 | client.On("ChangeAvailability", ChangeAvailabilityHandler) 48 | client.On("GetLocalListVersion", GetLocalListVersionHandler) 49 | client.On("ChangeConfiguration", ChangeConfigurationHandler) 50 | 51 | cp, err := client.Start("ws://localhost:8999", "/ws") 52 | if err != nil { 53 | fmt.Printf("error dialing: %v\n", err) 54 | return 55 | } 56 | sendBootNotification(cp) 57 | defer cp.Shutdown() 58 | log.Debugf("charge point status %v", cp.IsConnected()) 59 | select {} 60 | } 61 | 62 | func ChangeConfigurationHandler(cp *ocpp.ChargePoint, p ocpp.Payload) ocpp.Payload { 63 | req := p.(*v16.ChangeConfigurationReq) 64 | log.Debugf("ChangeConfigurationReq: %v\n", req) 65 | 66 | var res ocpp.Payload = &v16.ChangeConfigurationConf{ 67 | Status: "Accepted", 68 | } 69 | return res 70 | } 71 | 72 | 73 | func ChangeAvailabilityHandler(cp *ocpp.ChargePoint, p ocpp.Payload) ocpp.Payload { 74 | req := p.(*v16.ChangeAvailabilityReq) 75 | log.Debugf("ChangeAvailability: %v\n", req) 76 | var res ocpp.Payload = &v16.ChangeAvailabilityConf{ 77 | Status: "Accepted", 78 | } 79 | return res 80 | } 81 | 82 | func GetLocalListVersionHandler(cp *ocpp.ChargePoint, p ocpp.Payload) ocpp.Payload { 83 | req := p.(*v16.GetLocalListVersionReq) 84 | log.Debugf("GetLocalListVersionReq: %v\n", req) 85 | var res ocpp.Payload = &v16.GetLocalListVersionConf{ 86 | ListVersion: 1, 87 | } 88 | return res 89 | } 90 | 91 | func sendBootNotification(c *ocpp.ChargePoint) { 92 | req := &v16.BootNotificationReq{ 93 | ChargePointModel: "client00", 94 | ChargePointVendor: "VendorX", 95 | } 96 | res, err := c.Call("BootNotification", req) 97 | if err != nil { 98 | fmt.Printf("error dialing: %v\n", err) 99 | return 100 | } 101 | fmt.Printf("BootNotificationConf: %v\n", res) 102 | } 103 | 104 | 105 | -------------------------------------------------------------------------------- /examples/server/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net/http" 5 | "strings" 6 | "time" 7 | 8 | "go.uber.org/zap" 9 | 10 | _ "net/http/pprof" 11 | 12 | "github.com/aliml92/ocpp" 13 | v16 "github.com/aliml92/ocpp/v16" 14 | ) 15 | 16 | 17 | var csms *ocpp.Server 18 | 19 | // log replaces standard log 20 | var log *zap.SugaredLogger 21 | 22 | 23 | 24 | func main() { 25 | 26 | go func() { 27 | log.Debug(http.ListenAndServe(":6060", nil)) 28 | }() 29 | 30 | logger, _ := zap.NewDevelopment() 31 | log = logger.Sugar() 32 | defer log.Sync() 33 | 34 | // set ocpp library's logger to zap logger 35 | ocpp.SetLogger(log) 36 | 37 | // start csms server with default configurations 38 | csms = ocpp.NewServer() 39 | 40 | 41 | 42 | csms.AddSubProtocol("ocpp1.6") 43 | csms.SetCheckOriginHandler(func(r *http.Request) bool { return true }) 44 | csms.SetPreUpgradeHandler(customPreUpgradeHandler) 45 | csms.SetCallQueueSize(32) 46 | 47 | 48 | // register charge-point-initiated action handlers 49 | csms.On("BootNotification", BootNotificationHandler) 50 | // csms.On("Authorize", AuthorizationHandler) 51 | csms.After("BootNotification", SendChangeConfigration) 52 | csms.Start("0.0.0.0:8999", "/ws/", nil) 53 | 54 | 55 | } 56 | 57 | func SendChangeConfigration(cp *ocpp.ChargePoint, payload ocpp.Payload) { 58 | var req ocpp.Payload = v16.ChangeConfigurationReq{ 59 | Key: "WebSocketPingInterval", 60 | Value: "30", 61 | } 62 | res, err := cp.Call("ChangeConfiguration", req) 63 | if err != nil { 64 | log.Debug(err) 65 | } 66 | 67 | log.Debug(res) 68 | } 69 | 70 | 71 | func SendGetLocalListVersion(cp *ocpp.ChargePoint, payload ocpp.Payload) { 72 | var req ocpp.Payload = v16.GetLocalListVersionReq{} 73 | res, err := cp.Call("GetLocalListVersion", req) 74 | if err != nil { 75 | log.Debug(err) 76 | } 77 | log.Debug(res) 78 | } 79 | 80 | 81 | 82 | func customPreUpgradeHandler(w http.ResponseWriter, r *http.Request) bool { 83 | u, p, ok := r.BasicAuth() 84 | if !ok { 85 | log.Debug("error parsing basic auth") 86 | w.WriteHeader(401) 87 | return false 88 | } 89 | path := strings.Split(r.URL.Path, "/") 90 | id := path[len(path)-1] 91 | log.Debugf("%s is trying to connect with %s:%s", id, u, p) 92 | if u != id { 93 | log.Debug("username provided is correct: %s", u) 94 | w.WriteHeader(401) 95 | return false 96 | } 97 | return true 98 | } 99 | 100 | 101 | 102 | func BootNotificationHandler(cp *ocpp.ChargePoint, p ocpp.Payload) ocpp.Payload { 103 | req := p.(*v16.BootNotificationReq) 104 | log.Debugf("\nid: %s\nBootNotification: %v", cp.Id, req) 105 | var res ocpp.Payload = &v16.BootNotificationConf{ 106 | CurrentTime: time.Now().Format("2006-01-02T15:04:05.000Z"), 107 | Interval: 60 , 108 | Status: "Accepted", 109 | } 110 | return res 111 | } 112 | 113 | func AuthorizationHandler(cp *ocpp.ChargePoint, p ocpp.Payload) ocpp.Payload { 114 | time.Sleep(time.Second * 2) 115 | req := p.(*v16.AuthorizeReq) 116 | log.Debugf("\nid: %s\nAuthorizeReq: %v", cp.Id, req) 117 | var res ocpp.Payload = &v16.AuthorizeConf{ 118 | IdTagInfo: v16.IdTagInfo{ 119 | Status: "Accepted", 120 | }, 121 | } 122 | return res 123 | } -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= 2 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 4 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 5 | github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU= 6 | github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs= 7 | github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho= 8 | github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= 9 | github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= 10 | github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 11 | github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= 12 | github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= 13 | github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= 14 | github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= 15 | github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= 16 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 17 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 18 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 19 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 20 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 21 | github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= 22 | go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= 23 | go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= 24 | go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI= 25 | go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= 26 | go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= 27 | go.uber.org/zap v1.23.0 h1:OjGQ5KQDEUawVHxNwQgPpiypGHOxo2mNZsOqTak4fFY= 28 | go.uber.org/zap v1.23.0/go.mod h1:D+nX8jyLsMHMYrln8A0rJjFt/T/9/bGgIhAqxv5URuY= 29 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 30 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 31 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 32 | gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM= 33 | gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= 34 | gopkg.in/go-playground/validator.v9 v9.31.0 h1:bmXmP2RSNtFES+bn4uYuHT7iJFJv7Vj+an+ZQdDaD1M= 35 | gopkg.in/go-playground/validator.v9 v9.31.0/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ= 36 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 37 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 38 | -------------------------------------------------------------------------------- /client.go: -------------------------------------------------------------------------------- 1 | package ocpp 2 | 3 | import ( 4 | "encoding/base64" 5 | "net/http" 6 | "net/url" 7 | "time" 8 | 9 | "github.com/gorilla/websocket" 10 | ) 11 | 12 | var client *Client 13 | 14 | type ClientTimeoutConfig struct { 15 | // ocpp response timeout in seconds 16 | OcppWait time.Duration 17 | 18 | // time allowed to write a message to the peer 19 | WriteWait time.Duration 20 | 21 | // pong wait in seconds 22 | PongWait time.Duration 23 | 24 | // ping period in seconds 25 | PingPeriod time.Duration 26 | } 27 | 28 | type Client struct { 29 | Id string 30 | // register implemented action handler functions 31 | actionHandlers map[string]func(*ChargePoint, Payload) Payload 32 | // register after-action habdler functions 33 | afterHandlers map[string]func(*ChargePoint, Payload) 34 | // timeout configuration 35 | ocppWait time.Duration 36 | 37 | writeWait time.Duration 38 | 39 | pongWait time.Duration 40 | 41 | pingPeriod time.Duration 42 | 43 | header http.Header 44 | 45 | returnError func(error) 46 | 47 | callQuequeSize int 48 | } 49 | 50 | // create new Client instance 51 | func NewClient() *Client { 52 | client = &Client{ 53 | actionHandlers: make(map[string]func(*ChargePoint, Payload) Payload), 54 | afterHandlers: make(map[string]func(*ChargePoint, Payload)), 55 | ocppWait: ocppWait, 56 | writeWait: writeWait, 57 | pongWait: pongWait, 58 | pingPeriod: pingPeriod, 59 | header: http.Header{}, 60 | } 61 | return client 62 | } 63 | 64 | func (c *Client) SetCallQueueSize(size int) { 65 | c.callQuequeSize = size 66 | } 67 | 68 | func (c *Client) SetTimeoutConfig(config ClientTimeoutConfig) { 69 | c.ocppWait = config.OcppWait 70 | c.writeWait = config.WriteWait 71 | c.pongWait = config.PongWait 72 | c.pingPeriod = config.PingPeriod 73 | } 74 | 75 | // register action handler function 76 | func (c *Client) On(action string, f func(*ChargePoint, Payload) Payload) *Client { 77 | c.actionHandlers[action] = f 78 | return c 79 | } 80 | 81 | // register after-action handler function 82 | func (c *Client) After(action string, f func(*ChargePoint, Payload)) *Client { 83 | c.afterHandlers[action] = f 84 | return c 85 | } 86 | 87 | func (c *Client) getHandler(action string) func(*ChargePoint, Payload) Payload { 88 | return c.actionHandlers[action] 89 | } 90 | 91 | func (c *Client) getAfterHandler(action string) func(*ChargePoint, Payload) { 92 | return c.afterHandlers[action] 93 | } 94 | 95 | func (c *Client) AddSubProtocol(protocol string) { 96 | c.header.Add("Sec-WebSocket-Protocol", protocol) 97 | } 98 | 99 | func (c *Client) SetBasicAuth(username string, password string) { 100 | auth := username + ":" + password 101 | enc := base64.StdEncoding.EncodeToString([]byte(auth)) 102 | c.header.Set("Authorization", "Basic "+enc) 103 | } 104 | 105 | func (c *Client) Start(addr string, path string) (cp *ChargePoint, err error) { 106 | urlStr, err := url.JoinPath(addr, path, c.Id) 107 | if err != nil { 108 | c.returnError(err) 109 | return 110 | } 111 | conn, _, err := websocket.DefaultDialer.Dial(urlStr, c.header) 112 | if err != nil { 113 | return 114 | } 115 | cp = NewChargePoint(conn, c.Id, conn.Subprotocol(), false) 116 | return 117 | } 118 | 119 | func (c *Client) SetID(id string) { 120 | c.Id = id 121 | } 122 | -------------------------------------------------------------------------------- /v16/datatypes.go: -------------------------------------------------------------------------------- 1 | package v16 2 | 3 | type IdTagInfo struct { 4 | ExpiryDate string `json:"expiryDate,omitempty" validate:"omitempty,ISO8601date"` 5 | ParentIdTag string `json:"parentIdTag,omitempty" validate:"omitempty,max=20"` 6 | Status string `json:"status" validate:"required,AuthorizationStatus"` 7 | } 8 | 9 | type MeterValue struct { 10 | Timestamp string `json:"timestamp" validate:"required,ISO8601date"` 11 | SampledValue []SampledValue `json:"sampledValue" validate:"required,dive,required"` 12 | } 13 | 14 | type SampledValue struct { 15 | Value string `json:"value" validate:"required"` 16 | Context string `json:"context,omitempty" validate:"omitempty,ReadingContext"` 17 | Format string `json:"format,omitempty" validate:"omitempty,ValueFormat"` 18 | Measurand string `json:"measurand,omitempty" validate:"omitempty,Measurand"` 19 | Phase string `json:"phase,omitempty" validate:"omitempty,Phase"` 20 | Location string `json:"location,omitempty" validate:"omitempty,Location"` 21 | Unit string `json:"unit,omitempty" validate:"omitempty,UnitOfMeasure"` 22 | } 23 | 24 | type ChargingProfile struct { 25 | ChargingProfileId int `json:"chargingProfileId" validate:"required,gte=0"` 26 | TransactionId int `json:"transactionId,omitempty"` 27 | StackLevel int `json:"stackLevel" validate:"required,gte=0"` 28 | ChargingProfilePurpose string `json:"chargingProfilePurpose" validate:"required,ChargingProfilePurposeType"` 29 | ChargingProfileKind string `json:"chargingProfileKind" validate:"required,ChargingProfileKindType"` 30 | Context string `json:"context,omitempty" validate:"omitempty,ReadingContext"` 31 | RecurrencyKind string `json:"recurrencyKind,omitempty" validate:"omitempty,RecurrencyKindType"` 32 | ValidFrom string `json:"validFrom,omitempty" validate:"omitempty,ISO8601date"` 33 | ValidTo string `json:"validTo,omitempty" validate:"omitempty,ISO8601date"` 34 | ChargingSchedule ChargingSchedule `json:"chargingSchedule" validate:"required,dive,required"` 35 | } 36 | 37 | type ChargingSchedule struct { 38 | Duration int `json:"duration,omitempty"` 39 | StartSchedule string `json:"startSchedule,omitempty" validate:"omitempty,ISO8601date"` 40 | ChargingRateUnit string `json:"chargingRateUnit" validate:"required,ChargingRateUnitType"` 41 | ChargingSchedulePeriod []ChargingSchedulePeriod `json:"chargingSchedulePeriod" validate:"required,dive,required"` 42 | MinChargingRate float32 `json:"minChargingRate,omitempty"` 43 | } 44 | 45 | type ChargingSchedulePeriod struct { 46 | StartPeriod string `json:"startPeriod" validate:"required,ISO8601date"` 47 | Limit float32 `json:"limit" validate:"required,gte=0"` 48 | NumberPhases int `json:"numberPhases,omitempty"` 49 | } 50 | 51 | type AuthorizationData struct { 52 | IdTag string `json:"idTag" validate:"required,max=20"` 53 | IdTagInfo IdTagInfo `json:"idTagInfo,omitempty"` 54 | } 55 | 56 | // OCPP 1.6 security whitepaper edition 2 implementation 57 | 58 | type CertificateHashDataType struct { 59 | HashAlgorithm string `json:"hashAlgorithm" validate:"required,HashAlgorithmEnumType"` 60 | IssuerNameHash string `json:"issuerNameHash" validate:"required,max=128"` 61 | IssuerKeyHash string `json:"issuerKeyHash" validate:"required,max=128"` 62 | SerialNumber string `json:"serialNumber" validate:"required,max=40"` 63 | } 64 | 65 | type FirmwareType struct { 66 | Location string `json:"location" validate:"required,max=512"` 67 | RetrieveDateTime string `json:"retrieveDate" validate:"required,ISO8601date"` 68 | InstallDateTime string `json:"installDate,omitempty" validate:"omitempty,ISO8601date"` 69 | SigningCertificate string `json:"signingCertificate" validate:"required,max=5500"` 70 | Signature string `json:"signature" validate:"required,max=800"` 71 | } 72 | 73 | type LogParametersType struct { 74 | RemoteLocation string `json:"remoteLocation" validate:"required,max=512"` 75 | OldestTimestamp string `json:"oldestTimestamp,omitempty" validate:"omitempty,ISO8601date"` 76 | LatestTimestamp string `json:"latestTimestamp,omitempty" validate:"omitempty,ISO8601date"` 77 | } 78 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | When contributing to this repository, please first discuss the change you wish to make via issue, 4 | email, or any other method with the owners of this repository before making a change. 5 | 6 | Please note we have a code of conduct, please follow it in all your interactions with the project. 7 | 8 | ## Pull Request Process 9 | 10 | 1. Ensure any install or build dependencies are removed before the end of the layer when doing a 11 | build. 12 | 2. Update the README.md with details of changes to the interface, this includes new environment 13 | variables, exposed ports, useful file locations and container parameters. 14 | 3. Increase the version numbers in any examples files and the README.md to the new version that this 15 | Pull Request would represent. The versioning scheme we use is [SemVer](http://semver.org/). 16 | 4. You may merge the Pull Request in once you have the sign-off of two other developers, or if you 17 | do not have permission to do that, you may request the second reviewer to merge it for you. 18 | 19 | ## Code of Conduct 20 | 21 | ### Our Pledge 22 | 23 | In the interest of fostering an open and welcoming environment, we as 24 | contributors and maintainers pledge to making participation in our project and 25 | our community a harassment-free experience for everyone, regardless of age, body 26 | size, disability, ethnicity, gender identity and expression, level of experience, 27 | nationality, personal appearance, race, religion, or sexual identity and 28 | orientation. 29 | 30 | ### Our Standards 31 | 32 | Examples of behavior that contributes to creating a positive environment 33 | include: 34 | 35 | * Using welcoming and inclusive language 36 | * Being respectful of differing viewpoints and experiences 37 | * Gracefully accepting constructive criticism 38 | * Focusing on what is best for the community 39 | * Showing empathy towards other community members 40 | 41 | Examples of unacceptable behavior by participants include: 42 | 43 | * The use of sexualized language or imagery and unwelcome sexual attention or 44 | advances 45 | * Trolling, insulting/derogatory comments, and personal or political attacks 46 | * Public or private harassment 47 | * Publishing others' private information, such as a physical or electronic 48 | address, without explicit permission 49 | * Other conduct which could reasonably be considered inappropriate in a 50 | professional setting 51 | 52 | ### Our Responsibilities 53 | 54 | Project maintainers are responsible for clarifying the standards of acceptable 55 | behavior and are expected to take appropriate and fair corrective action in 56 | response to any instances of unacceptable behavior. 57 | 58 | Project maintainers have the right and responsibility to remove, edit, or 59 | reject comments, commits, code, wiki edits, issues, and other contributions 60 | that are not aligned to this Code of Conduct, or to ban temporarily or 61 | permanently any contributor for other behaviors that they deem inappropriate, 62 | threatening, offensive, or harmful. 63 | 64 | ### Scope 65 | 66 | This Code of Conduct applies both within project spaces and in public spaces 67 | when an individual is representing the project or its community. Examples of 68 | representing a project or community include using an official project e-mail 69 | address, posting via an official social media account, or acting as an appointed 70 | representative at an online or offline event. Representation of a project may be 71 | further defined and clarified by project maintainers. 72 | 73 | ### Enforcement 74 | 75 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 76 | reported by contacting the project team at [INSERT EMAIL ADDRESS]. All 77 | complaints will be reviewed and investigated and will result in a response that 78 | is deemed necessary and appropriate to the circumstances. The project team is 79 | obligated to maintain confidentiality with regard to the reporter of an incident. 80 | Further details of specific enforcement policies may be posted separately. 81 | 82 | Project maintainers who do not follow or enforce the Code of Conduct in good 83 | faith may face temporary or permanent repercussions as determined by other 84 | members of the project's leadership. 85 | 86 | ### Attribution 87 | 88 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 89 | available at [http://contributor-covenant.org/version/1/4][version] 90 | 91 | [homepage]: http://contributor-covenant.org 92 | [version]: http://contributor-covenant.org/version/1/4/ 93 | -------------------------------------------------------------------------------- /server.go: -------------------------------------------------------------------------------- 1 | package ocpp 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "net/http" 7 | "strings" 8 | "sync" 9 | "time" 10 | 11 | "github.com/gorilla/websocket" 12 | ) 13 | 14 | var server *Server 15 | 16 | type ServerTimeoutConfig struct { 17 | // ocpp response timeout in seconds 18 | OcppWait time.Duration 19 | 20 | // time allowed to write a message to the peer 21 | WriteWait time.Duration 22 | 23 | // time allowed to read the next pong message from the peer 24 | PingWait time.Duration 25 | } 26 | 27 | // Server type representes csms server 28 | type Server struct { 29 | // keeps track of all connected ChargePoints 30 | chargepoints map[string]*ChargePoint 31 | 32 | // register implemented action handler functions 33 | actionHandlers map[string]func(*ChargePoint, Payload) Payload 34 | 35 | // register after-action habdler functions 36 | afterHandlers map[string]func(*ChargePoint, Payload) 37 | 38 | // timeout configuration 39 | ocppWait time.Duration 40 | 41 | writeWait time.Duration 42 | 43 | pingWait time.Duration 44 | 45 | mu sync.Mutex 46 | 47 | upgrader websocket.Upgrader 48 | 49 | preUpgradeHandler func(w http.ResponseWriter, r *http.Request) bool 50 | 51 | returnError func(err error) 52 | 53 | callQuequeSize int 54 | } 55 | 56 | // create new CSMS instance acting as main handler for ChargePoints 57 | func NewServer() *Server { 58 | server = &Server{ 59 | chargepoints: make(map[string]*ChargePoint), 60 | actionHandlers: make(map[string]func(*ChargePoint, Payload) Payload), 61 | afterHandlers: make(map[string]func(*ChargePoint, Payload)), 62 | ocppWait: ocppWait, 63 | writeWait: writeWait, 64 | pingWait: pingWait, 65 | upgrader: websocket.Upgrader{ 66 | Subprotocols: []string{}, 67 | }, 68 | } 69 | return server 70 | } 71 | 72 | func (s *Server) SetTimeoutConfig(config ServerTimeoutConfig) { 73 | s.ocppWait = config.OcppWait 74 | s.writeWait = config.WriteWait 75 | s.pingWait = config.PingWait 76 | } 77 | 78 | // register action handler function 79 | func (s *Server) On(action string, f func(*ChargePoint, Payload) Payload) *Server { 80 | s.actionHandlers[action] = f 81 | return s 82 | } 83 | 84 | // register after-action handler function 85 | func (s *Server) After(action string, f func(*ChargePoint, Payload)) *Server { 86 | s.afterHandlers[action] = f 87 | return s 88 | } 89 | 90 | func (s *Server) IsConnected(id string) bool { 91 | if cp, ok := s.chargepoints[id]; ok { 92 | return cp.connected 93 | } 94 | return false 95 | } 96 | 97 | func (s *Server) getHandler(action string) func(*ChargePoint, Payload) Payload { 98 | return s.actionHandlers[action] 99 | } 100 | 101 | func (s *Server) getAfterHandler(action string) func(*ChargePoint, Payload) { 102 | return s.afterHandlers[action] 103 | } 104 | 105 | func (s *Server) Delete(id string) { 106 | s.mu.Lock() 107 | if cp, ok := s.chargepoints[id]; ok { 108 | cp.connected = false 109 | } 110 | delete(s.chargepoints, id) 111 | s.mu.Unlock() 112 | } 113 | 114 | func (s *Server) Store(cp *ChargePoint) { 115 | s.mu.Lock() 116 | server.chargepoints[cp.Id] = cp 117 | s.mu.Unlock() 118 | } 119 | 120 | func (s *Server) Load(id string) (*ChargePoint, bool) { 121 | s.mu.Lock() 122 | defer s.mu.Unlock() 123 | if cp, ok := s.chargepoints[id]; ok { 124 | fmt.Printf("ChargePoint with id: %s exist\n", cp.Id) 125 | return cp, true 126 | } 127 | return nil, false 128 | } 129 | 130 | func (s *Server) AddSubProtocol(protocol string) { 131 | for _, p := range server.upgrader.Subprotocols { 132 | if p == protocol { 133 | return 134 | } 135 | } 136 | s.upgrader.Subprotocols = append(s.upgrader.Subprotocols, protocol) 137 | } 138 | 139 | func (s *Server) SetCheckOriginHandler(f func(r *http.Request) bool) { 140 | s.upgrader.CheckOrigin = f 141 | } 142 | 143 | func (s *Server) SetPreUpgradeHandler(f func(w http.ResponseWriter, r *http.Request) bool) { 144 | s.preUpgradeHandler = f 145 | } 146 | 147 | // TODO: add more functionality 148 | func (s *Server) Start(addr string, path string, handler func(http.ResponseWriter, *http.Request)) { 149 | if handler != nil { 150 | http.HandleFunc(path, handler) 151 | } else { 152 | http.HandleFunc(path, defaultWebsocketHandler) 153 | } 154 | http.ListenAndServe(addr, nil) 155 | } 156 | 157 | func defaultWebsocketHandler(w http.ResponseWriter, r *http.Request) { 158 | preCheck := server.preUpgradeHandler 159 | if preCheck != nil { 160 | if preCheck(w, r) { 161 | upgrade(w, r) 162 | } else { 163 | server.returnError(errors.New("cannot start server")) 164 | } 165 | } else { 166 | upgrade(w, r) 167 | } 168 | } 169 | 170 | func upgrade(w http.ResponseWriter, r *http.Request) { 171 | c, err := server.upgrader.Upgrade(w, r, nil) 172 | if err != nil { 173 | server.returnError(err) 174 | return 175 | } 176 | p := strings.Split(r.URL.Path, "/") 177 | id := p[len(p)-1] 178 | cp := NewChargePoint(c, id, c.Subprotocol(), true) 179 | server.Store(cp) 180 | } 181 | 182 | func (s *Server) SetCallQueueSize(size int) { 183 | s.callQuequeSize = size 184 | } 185 | 186 | func (s *Server) getCallQueueSize() int { 187 | s.mu.Lock() 188 | size := s.callQuequeSize 189 | s.mu.Unlock() 190 | return size 191 | } 192 | -------------------------------------------------------------------------------- /v16/call_result.go: -------------------------------------------------------------------------------- 1 | package v16 2 | 3 | type AuthorizeConf struct { 4 | IdTagInfo IdTagInfo `json:"idTagInfo" validate:"required"` 5 | } 6 | 7 | type BootNotificationConf struct { 8 | CurrentTime string `json:"currentTime" validate:"required,ISO8601date"` 9 | Interval int `json:"interval" validate:"required,gte=0"` 10 | Status string `json:"status" validate:"required,RegistrationStatus"` 11 | } 12 | 13 | type DataTransferConf struct { 14 | Status string `json:"status" validate:"required,DataTransferStatus"` 15 | Data string `json:"data,omitempty"` 16 | } 17 | 18 | type DiagnosticsStatusNotificationConf struct{} 19 | 20 | type FirmwareStatusNotificationConf struct{} 21 | 22 | type HeartbeatConf struct { 23 | CurrentTime string `json:"currentTime" validate:"required,ISO8601date"` 24 | } 25 | 26 | type MeterValuesConf struct{} 27 | 28 | type StartTransactionConf struct { 29 | IdTagInfo IdTagInfo `json:"idTagInfo" validate:"required"` 30 | TransactionId int `json:"transactionId" validate:"required"` 31 | } 32 | 33 | type StatusNotificationConf struct{} 34 | 35 | type StopTransactionConf struct { 36 | IdTagInfo IdTagInfo `json:"idTagInfo" validate:"required"` 37 | } 38 | 39 | type CancelReservationConf struct { 40 | Status string `json:"status" validate:"required,CancelReservationStatus"` 41 | } 42 | 43 | type ChangeAvailabilityConf struct { 44 | Status string `json:"status" validate:"required,AvailabilityStatus"` 45 | } 46 | 47 | type ChangeConfigurationConf struct { 48 | Status string `json:"status" validate:"required,ConfigurationStatus"` 49 | } 50 | 51 | type ClearCacheConf struct { 52 | Status string `json:"status" validate:"required,ClearCacheStatus"` 53 | } 54 | 55 | type ClearChargingProfileConf struct { 56 | Status string `json:"status" validate:"required,ClearChargingProfileStatus"` 57 | } 58 | 59 | type GetCompositeScheduleConf struct { 60 | Status string `json:"status" validate:"required,GetCompositeScheduleStatus"` 61 | ConnectorId int `json:"connectorId" validate:"required,gte=0"` 62 | ScheduleStart string `json:"scheduleStart,omitempty" validate:"omitempty,ISO8601date"` 63 | ChargingSchedule *ChargingSchedule `json:"chargingSchedule,omitempty"` 64 | } 65 | 66 | type GetConfigurationConf struct { 67 | ConfigurationKey map[string]string `json:"configurationKey,omitempty"` 68 | UnknownKey []string `json:"unknownKey,omitempty" validate:"omitempty,max=50"` 69 | } 70 | 71 | type GetDiagnosticsConf struct { 72 | FileName string `json:"fileName,omitempty" validate:"omitempty,max=255"` 73 | } 74 | 75 | type GetLocalListVersionConf struct { 76 | ListVersion int `json:"listVersion" validate:"required,gte=0"` 77 | } 78 | 79 | type RemoteStartTransactionConf struct { 80 | Status string `json:"status" validate:"required,RemoteStartStopStatus"` 81 | } 82 | 83 | type RemoteStopTransactionConf struct { 84 | Status string `json:"status" validate:"required,RemoteStartStopStatus"` 85 | } 86 | 87 | type ReserveNowConf struct { 88 | Status string `json:"status" validate:"required,ReservationStatus"` 89 | } 90 | 91 | type ResetConf struct { 92 | Status string `json:"status" validate:"required,ResetStatus"` 93 | } 94 | 95 | type SendLocalListConf struct { 96 | Status string `json:"status" validate:"required,UpdateStatus"` 97 | } 98 | 99 | type SetChargingProfileConf struct { 100 | Status string `json:"status" validate:"required,ChargingProfileStatus"` 101 | } 102 | 103 | type TriggerMessageConf struct { 104 | Status string `json:"status" validate:"required,TriggerMessageStatus"` 105 | } 106 | 107 | type UnlockConnectorConf struct { 108 | Status string `json:"status" validate:"required,UnlockStatus"` 109 | } 110 | 111 | type UpdateFirmwareConf struct{} 112 | 113 | // OCPP 1.6 security whitepaper edition 2 implementation 114 | 115 | type CertificateSignedConf struct { 116 | Status string `json:"status" validate:"required,CertificateSignedStatusEnumType"` 117 | } 118 | 119 | type DeleteCertificateConf struct { 120 | Status string `json:"status" validate:"required,DeleteCertificateStatusEnumType"` 121 | } 122 | 123 | type ExtendedTriggerMessageConf struct { 124 | Status string `json:"status" validate:"required,TriggerMessageStatusEnumType"` 125 | } 126 | 127 | type GetInstalledCertificateIdsConf struct { 128 | Status string `json:"status" validate:"required,GetInstalledCertificateStatusEnumType"` 129 | CertificateHashData []CertificateHashDataType `json:"certificateHashData,omitempty" validate:"omitempty,dive,required"` 130 | } 131 | 132 | type GetLogConf struct { 133 | Status string `json:"status" validate:"required,LogStatusEnumType"` 134 | Filename string `json:"filename,omitempty" validate:"omitempty,max=255"` 135 | } 136 | 137 | type InstallCertificateConf struct { 138 | Status string `json:"status" validate:"required,CertificateStatusEnumType"` 139 | } 140 | 141 | type LogStatusNotificationConf struct{} 142 | 143 | type SecurityEventNotificationConf struct{} 144 | 145 | type SignCertificateConf struct { 146 | Status string `json:"status" validate:"required,GenericStatusEnumType"` 147 | } 148 | 149 | type SignedFirmwareStatusNotificationConf struct{} 150 | 151 | type SignedUpdateFirmwareConf struct { 152 | Status string `json:"status" validate:"required,UpdateFirmwareStatusEnumType"` 153 | } 154 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # ocpp 3 | 4 | [![MIT License](https://img.shields.io/apm/l/atomic-design-ui.svg?)](https://github.com/tterb/atomic-design-ui/blob/master/LICENSEs) 5 | 6 | Golang package implementing the JSON version of the Open Charge Point Protocol (OCPP). Currently OCPP 1.6 and 2.0.1 is supported. 7 | The project is initially inspired by [mobility/ocpp](https://github.com/mobilityhouse/ocpp) 8 | 9 | ## Installation 10 | 11 | Go version 1.18+ is required 12 | 13 | ```bash 14 | go get github.com/aliml92/ocpp 15 | ``` 16 | 17 | ## Features 18 | 19 | - [x] ocpp1.6 and ocpp2.0.1 support 20 | - [x] logging 21 | - [x] ping/pong customization on `WebSocketPingInterval` 22 | - [x] server initiated ping activation 23 | 24 | ## Roadmap 25 | 26 | - [ ] add unit/integration tests 27 | - [x] improve logging 28 | - [ ] add validation disabling feature 29 | - [ ] add better queque implementation 30 | 31 | 32 | ## Usage 33 | 34 | ### Cental System (Server) 35 | ```go 36 | package main 37 | 38 | import ( 39 | "net/http" 40 | "strings" 41 | "time" 42 | 43 | "go.uber.org/zap" 44 | 45 | "github.com/aliml92/ocpp" 46 | v16 "github.com/aliml92/ocpp/v16" 47 | ) 48 | 49 | 50 | var csms *ocpp.Server 51 | 52 | // log replaces standard log 53 | var log *zap.SugaredLogger 54 | 55 | 56 | 57 | func main() 58 | logger, _ := zap.NewDevelopment() 59 | log = logger.Sugar() 60 | defer log.Sync() 61 | 62 | // set ocpp library's logger to zap logger 63 | ocpp.SetLogger(log) 64 | 65 | // start csms server with default configurations 66 | csms = ocpp.NewServer() 67 | 68 | csms.AddSubProtocol("ocpp1.6") 69 | csms.SetCheckOriginHandler(func(r *http.Request) bool { return true }) 70 | csms.SetPreUpgradeHandler(customPreUpgradeHandler) 71 | csms.SetCallQueueSize(32) 72 | 73 | // register charge-point-initiated action handlers 74 | csms.On("BootNotification", BootNotificationHandler) 75 | csms.After("BootNotification", SendChangeConfigration) 76 | csms.On("Authorize", AuthorizationHandler) 77 | csms.Start("0.0.0.0:8999", "/ws/", nil) 78 | 79 | } 80 | 81 | func SendChangeConfigration(cp *ocpp.ChargePoint, payload ocpp.Payload) { 82 | var req ocpp.Payload = v16.ChangeConfigurationReq{ 83 | Key: "WebSocketPingInterval", 84 | Value: "30", 85 | } 86 | res, err := cp.Call("ChangeConfiguration", req) 87 | if err != nil { 88 | log.Debug(err) 89 | } 90 | log.Debug(res) 91 | } 92 | 93 | 94 | func customPreUpgradeHandler(w http.ResponseWriter, r *http.Request) bool { 95 | u, p, ok := r.BasicAuth() 96 | if !ok { 97 | log.Debug("error parsing basic auth") 98 | w.WriteHeader(401) 99 | return false 100 | } 101 | path := strings.Split(r.URL.Path, "/") 102 | id := path[len(path)-1] 103 | log.Debugf("%s is trying to connect with %s:%s", id, u, p) 104 | if u != id { 105 | log.Debug("username provided is correct: %s", u) 106 | w.WriteHeader(401) 107 | return false 108 | } 109 | return true 110 | } 111 | 112 | 113 | 114 | func BootNotificationHandler(cp *ocpp.ChargePoint, p ocpp.Payload) ocpp.Payload { 115 | req := p.(*v16.BootNotificationReq) 116 | log.Debugf("\nid: %s\nBootNotification: %v", cp.Id, req) 117 | 118 | var res ocpp.Payload = &v16.BootNotificationConf{ 119 | CurrentTime: time.Now().Format("2006-01-02T15:04:05.000Z"), 120 | Interval: 60 , 121 | Status: "Accepted", 122 | } 123 | return res 124 | } 125 | 126 | func AuthorizationHandler(cp *ocpp.ChargePoint, p ocpp.Payload) ocpp.Payload { 127 | req := p.(*v16.AuthorizeReq) 128 | log.Debugf("\nid: %s\nAuthorizeReq: %v", cp.Id, req) 129 | 130 | var res ocpp.Payload = &v16.AuthorizeConf{ 131 | IdTagInfo: v16.IdTagInfo{ 132 | Status: "Accepted", 133 | }, 134 | } 135 | return res 136 | } 137 | ``` 138 | `ChargePoint` represents a single Charge Point (CP) connected to Central System 139 | and after initializing `*ocpp.Server` , register CP initiated call handlers using `csms.On` method. 140 | Making a Call can be done by excuting `cp.Call` method. 141 | 142 | 143 | 144 | ### Charge Point (Client) 145 | ```go 146 | package main 147 | 148 | import ( 149 | "fmt" 150 | "time" 151 | "github.com/aliml92/ocpp" 152 | v16 "github.com/aliml92/ocpp/v16" 153 | "go.uber.org/zap" 154 | ) 155 | 156 | 157 | 158 | var client *ocpp.Client 159 | 160 | // log replaces standard log 161 | var log *zap.SugaredLogger 162 | 163 | // initialize zap logger 164 | // for deveplopment only 165 | func initLogger() { 166 | logger, _ := zap.NewDevelopment() 167 | log = logger.Sugar() 168 | } 169 | 170 | func main() { 171 | initLogger() 172 | defer log.Sync() 173 | 174 | // set ocpp library's logger to zap logger 175 | ocpp.SetLogger(log) 176 | 177 | // create client 178 | client = ocpp.NewClient() 179 | id := "client00" 180 | client.SetID(id) 181 | client.AddSubProtocol("ocpp1.6") 182 | client.SetBasicAuth(id, "dummypass") 183 | client.SetCallQueueSize(32) 184 | client.On("ChangeAvailability", ChangeAvailabilityHandler) 185 | client.On("GetLocalListVersion", GetLocalListVersionHandler) 186 | client.On("ChangeConfiguration", ChangeConfigurationHandler) 187 | 188 | cp, err := client.Start("ws://localhost:8999", "/ws") 189 | if err != nil { 190 | fmt.Printf("error dialing: %v\n", err) 191 | return 192 | } 193 | sendBootNotification(cp) 194 | defer cp.Shutdown() 195 | log.Debugf("charge point status %v", cp.IsConnected()) 196 | select {} 197 | } 198 | 199 | func ChangeConfigurationHandler(cp *ocpp.ChargePoint, p ocpp.Payload) ocpp.Payload { 200 | req := p.(*v16.ChangeConfigurationReq) 201 | log.Debugf("ChangeConfigurationReq: %v\n", req) 202 | var res ocpp.Payload = &v16.ChangeConfigurationConf{ 203 | Status: "Accepted", 204 | } 205 | return res 206 | } 207 | 208 | Later use 209 | func ChangeAvailabilityHandler(cp *ocpp.ChargePoint, p ocpp.Payload) ocpp.Payload { 210 | req := p.(*v16.ChangeAvailabilityReq) 211 | log.Debugf("ChangeAvailability: %v\n", req) 212 | var res ocpp.Payload = &v16.ChangeAvailabilityConf{ 213 | Status: "Accepted", 214 | } 215 | return res 216 | } 217 | 218 | func GetLocalListVersionHandler(cp *ocpp.ChargePoint, p ocpp.Payload) ocpp.Payload { 219 | req := p.(*v16.GetLocalListVersionReq) 220 | log.Debugf("GetLocalListVersionReq: %v\n", req) 221 | var res ocpp.Payload = &v16.GetLocalListVersionConf{ 222 | ListVersion: 1, 223 | } 224 | return res 225 | } 226 | 227 | func sendBootNotification(c *ocpp.ChargePoint) { 228 | req := &v16.BootNotificationReq{ 229 | ChargePointModel: "client00", 230 | ChargePointVendor: "VendorX", 231 | } 232 | res, err := c.Call("BootNotification", req) 233 | if err != nil { 234 | fmt.Printf("error dialing: %v\n", err) 235 | return 236 | } 237 | fmt.Printf("BootNotificationConf: %v\n", res) 238 | } 239 | 240 | 241 | func sendAuthorize(c *ocpp.ChargePoint) { 242 | req := &v16.AuthorizeReq{ 243 | IdTag: "safdasdfdsa", 244 | } 245 | res, err := c.Call("Authorize", req, 10) 246 | if err != nil { 247 | fmt.Printf("error dialing: %v\n", err) 248 | return 249 | } 250 | fmt.Printf("AuthorizeConf: %v\n", res) 251 | } 252 | ``` 253 | After creating `*ocpp.Client` instance, register CS (Central System) initiated call handlers. 254 | Making a call to CS is same as the above snippet where just call `cp.Call` method. 255 | ## Contributing 256 | 257 | Contributions are always welcome! 258 | Implementing higher versions of ocpp is highly appreciated! 259 | 260 | See `CONTRIBUTING.md` for ways to get started. 261 | -------------------------------------------------------------------------------- /v16/call.go: -------------------------------------------------------------------------------- 1 | package v16 2 | 3 | type AuthorizeReq struct { 4 | IdTag string `json:"idTag" validate:"required,max=20"` 5 | } 6 | 7 | type BootNotificationReq struct { 8 | ChargeBoxSerialNumber string `json:"chargeBoxSerialNumber,omitempty" validate:"omitempty,max=25"` 9 | ChargePointModel string `json:"chargePointModel" validate:"required,max=20"` 10 | ChargePointSerialNumber string `json:"chargePointSerialNumber,omitempty" validate:"omitempty,max=25"` 11 | ChargePointVendor string `json:"chargePointVendor" validate:"required,max=20"` 12 | FirmwareVersion string `json:"firmwareVersion,omitempty" validate:"omitempty,max=50"` 13 | Iccid string `json:"iccid,omitempty" validate:"omitempty,max=20"` 14 | Imsi string `json:"imsi,omitempty" validate:"omitempty,max=20"` 15 | MeterSerialNumber string `json:"meterSerialNumber,omitempty" validate:"omitempty,max=25"` 16 | MeterType string `json:"meterType,omitempty" validate:"omitempty,max=25"` 17 | } 18 | 19 | type DataTransferReq struct { 20 | VendorId string `json:"vendorId" validate:"required,max=255"` 21 | MessageId string `json:"messageId,omitempty" validate:"omitempty,max=255"` 22 | Data string `json:"data,omitempty"` 23 | } 24 | 25 | type DiagnosticsStatusNotificationReq struct { 26 | Status string `json:"status" validate:"required,DiagnosticsStatus"` 27 | } 28 | 29 | type FirmwareStatusNotificationReq struct { 30 | Status string `json:"status" validate:"required,FirmwareStatus"` 31 | } 32 | 33 | type HeartbeatReq struct{} 34 | 35 | type MeterValuesReq struct { 36 | ConnectorId *int `json:"connectorId" validate:"required,gte=0"` 37 | TransactionId int `json:"transactionId"` 38 | MeterValue []MeterValue `json:"meterValue" validate:"required,gt=0,dive"` 39 | } 40 | 41 | type StartTransactionReq struct { 42 | ConnectorId int `json:"connectorId" validate:"required,gt=0"` 43 | IdTag string `json:"idTag" validate:"required,max=20"` 44 | MeterStart *int `json:"meterStart" validate:"required,gte=0"` 45 | ReservationId int `json:"reservationId,omitempty" validate:"omitempty"` 46 | Timestamp string `json:"timestamp" validate:"required,ISO8601date"` 47 | } 48 | 49 | type StatusNotificationReq struct { 50 | ConnectorId *int `json:"connectorId" validate:"required,gte=0"` 51 | ErrorCode string `json:"errorCode" validate:"required,ChargePointErrorCode"` 52 | Info string `json:"info,omitempty" validate:"omitempty,max=50"` 53 | Status string `json:"status" validate:"required,ChargePointStatus"` 54 | Timestamp string `json:"timestamp,omitempty" validate:"omitempty,ISO8601date"` 55 | VendorId string `json:"vendorId,omitempty" validate:"omitempty,max=255"` 56 | VendorErrorCode string `json:"vendorErrorCode,omitempty" validate:"omitempty,max=50"` 57 | } 58 | 59 | // temporary type for unmarshalling 60 | type StopTransactionReq struct { 61 | IdTag string `json:"idTag" validate:"omitempty,max=20"` 62 | MeterStop *int `json:"meterStop" validate:"required"` 63 | Timestamp string `json:"timestamp" validate:"required,ISO8601date"` 64 | TransactionId int `json:"transactionId" validate:"required"` 65 | Reason string `json:"reason,omitempty" validate:"omitempty,Reason"` 66 | TransactionData []MeterValue `json:"transactionData,omitempty" validate:"omitempty,dive,required"` 67 | } 68 | 69 | type CancelReservationReq struct { 70 | ReservationId int `json:"reservationId" validate:"required"` 71 | } 72 | 73 | // actions by csms 74 | 75 | type ChangeAvailabilityReq struct { 76 | ConnectorId *int `json:"connectorId" validate:"required,gte=0"` 77 | Type string `json:"type" validate:"required,AvailabilityType"` 78 | } 79 | 80 | type ChangeConfigurationReq struct { 81 | Key string `json:"key" validate:"required,max=50"` 82 | Value string `json:"value" validate:"required,max=50"` 83 | } 84 | 85 | type ClearCacheReq struct{} 86 | 87 | type ClearChargingProfileReq struct { 88 | Id string `json:"id,omitempty"` 89 | ConnectorId *int `json:"connectorId,omitempty" validate:"omitempty,gte=0"` 90 | ChargingProfilePurpose string `json:"chargingProfilePurpose,omitempty" validate:"omitempty,ChargingProfilePurpose"` 91 | StackLevel *int `json:"stackLevel,omitempty" validate:"omitempty,gte=0"` 92 | } 93 | 94 | type GetCompositeScheduleReq struct { 95 | ConnectorId *int `json:"connectorId,omitempty" validate:"omitempty,gte=0"` 96 | Duration int `json:"duration" validate:"required,gt=0"` 97 | ChargingRateUnit string `json:"chargingRateUnit,omitempty" validate:"omitempty,ChargingRateUnit"` 98 | } 99 | 100 | type GetConfigurationReq struct { 101 | Key []string `json:"key,omitempty" validate:"omitempty,dive,max=50"` 102 | } 103 | 104 | type GetDiagnosticsReq struct { 105 | Location string `json:"location" validate:"required"` 106 | Retries int `json:"retries,omitempty" validate:"omitempty,gt=0"` 107 | RetryInterval int `json:"retryInterval,omitempty" validate:"omitempty,gt=0"` 108 | StartTime string `json:"startTime,omitempty" validate:"omitempty,ISO8601date"` 109 | StopTime string `json:"stopTime,omitempty" validate:"omitempty,ISO8601date"` 110 | } 111 | 112 | type GetLocalListVersionReq struct{} 113 | 114 | type RemoteStartTransactionReq struct { 115 | ConnectorId *int `json:"connectorId,omitempty" validate:"omitempty,gte=0"` 116 | IdTag string `json:"idTag" validate:"required,max=20"` 117 | ChargingProfile *ChargingProfile `json:"chargingProfile,omitempty"` 118 | } 119 | 120 | type RemoteStopTransactionReq struct { 121 | TransactionId int `json:"transactionId" validate:"required"` 122 | } 123 | 124 | type ReserveNowReq struct { 125 | ConnectorId *int `json:"connectorId" validate:"required,gte=0"` 126 | ExpiryDate string `json:"expiryDate" validate:"required,ISO8601date"` 127 | IdTag string `json:"idTag" validate:"required,max=20"` 128 | ParentIdTag string `json:"parentIdTag,omitempty" validate:"omitempty,max=20"` 129 | ReservationId int `json:"reservationId" validate:"required"` 130 | } 131 | 132 | type ResetReq struct { 133 | Type string `json:"type" validate:"required,ResetType"` 134 | } 135 | 136 | type SendLocalListReq struct { 137 | ListVersion *int `json:"listVersion" validate:"required,gte=0"` 138 | LocalAuthorizationList []AuthorizationData `json:"localAuthorizationList,omitempty" validate:"omitempty,dive,required"` 139 | UpdateType string `json:"updateType" validate:"required,UpdateType"` 140 | } 141 | 142 | type SetChargingProfileReq struct { 143 | ConnectorId *int `json:"connectorId" validate:"required,gte=0"` 144 | CsChargingProfiles ChargingProfile `json:"csChargingProfiles" validate:"required"` 145 | } 146 | 147 | type TriggerMessageReq struct { 148 | RequestedMessage string `json:"requestedMessage" validate:"required,MessageTrigger"` 149 | ConnectorId *int `json:"connectorId,omitempty" validate:"omitempty,gte=0"` 150 | } 151 | 152 | type UnlockConnectorReq struct { 153 | ConnectorId *int `json:"connectorId" validate:"required,gte=0"` 154 | } 155 | 156 | type UpdateFirmwareReq struct { 157 | Location string `json:"location" validate:"required"` 158 | Retries int `json:"retries,omitempty" validate:"omitempty,gt=0"` 159 | RetrieveDate string `json:"retrieveDate" validate:"required,ISO8601date"` 160 | RetryInterval int `json:"retryInterval,omitempty" validate:"omitempty,gt=0"` 161 | } 162 | 163 | // OCPP 1.6 security whitepaper edition 2 implementation 164 | 165 | type CertificateSignedReq struct { 166 | CertificateChain string `json:"certificateChain" validate:"required,max=10000"` 167 | } 168 | 169 | type DeleteCertificateReq struct { 170 | CertificateHashData CertificateHashDataType `json:"certificateHashData" validate:"required"` 171 | } 172 | 173 | type ExtendedTriggerMessageReq struct { 174 | RequestedMessage string `json:"requestedMessage" validate:"required,MessageTriggerEnumType"` 175 | ConnectorId *int `json:"connectorId,omitempty" validate:"omitempty,gt=0"` 176 | } 177 | 178 | type GetInstalledCertificateIdsReq struct { 179 | CertificateType string `json:"certificateType" validate:"required,CertificateUseEnumType"` 180 | } 181 | 182 | type GetLogReq struct { 183 | LogType string `json:"logType" validate:"required,LogEnumType"` 184 | RequestId int `json:"requestId" validate:"required"` 185 | Retries int `json:"retries,omitempty" validate:"omitempty,gt=0"` 186 | RetryInterval int `json:"retryInterval,omitempty" validate:"omitempty,gt=0"` 187 | Log string `json:"log" validate:"required,LogParametersType"` 188 | } 189 | 190 | type InstallCertificateReq struct { 191 | CertificateType string `json:"certificateType" validate:"required,CertificateUseEnumType"` 192 | Certificate string `json:"certificate" validate:"required,max=5500"` 193 | } 194 | 195 | type LogStatusNotificationReq struct { 196 | Status string `json:"status" validate:"required,UploadLogStatusEnumType"` 197 | RequestId int `json:"requestId,omitempty"` 198 | } 199 | 200 | type SecurityEventNotificationReq struct { 201 | Type string `json:"type" validate:"required,max=50"` 202 | Timestamp string `json:"timestamp" validate:"required,ISO8601date"` 203 | TechInfo string `json:"techInfo,omitempty" validate:"omitempty,max=255"` 204 | } 205 | 206 | type SignCertificateReq struct { 207 | Csr string `json:"csr" validate:"required,max=5500"` 208 | Status string `json:"status" validate:"required,CertificateStatusEnumType"` 209 | } 210 | 211 | type SignedFirmwareStatusNotificationReq struct { 212 | Status string `json:"status" validate:"required,FirmwareStatusEnumType"` 213 | RequestId int `json:"requestId,omitempty"` 214 | } 215 | 216 | type SignedUpdateFirmwareReq struct { 217 | Retries int `json:"retries,omitempty" validate:"omitempty,gt=0"` 218 | RetryInterval int `json:"retryInterval,omitempty" validate:"omitempty,gt=0"` 219 | RequestId int `json:"requestId" validate:"required"` 220 | Firmware string `json:"firmware" validate:"required,FirewareType"` 221 | } 222 | -------------------------------------------------------------------------------- /v201/call_result.go: -------------------------------------------------------------------------------- 1 | package v201 2 | 3 | type AuthorizeRes struct { 4 | CertificateStatus string `json:"certificateStatus,omitempty" validate:"omitempty,AuthorizeCertificateStatusEnumType"` 5 | IdTokenInfo IdTokenInfoType `json:"idTokenInfo" validate:"required"` 6 | } 7 | 8 | type BootNotificationRes struct { 9 | CurrentTime string `json:"currentTime" validate:"required,ISO8601date"` 10 | Interval int `json:"interval" validate:"required"` 11 | Status string `json:"status" validate:"required,RegistrationStatusEnumType"` 12 | StatusInfo StatusInfoType `json:"statusInfo,omitempty" ` 13 | } 14 | 15 | type CancelReservationRes struct { 16 | Status string `json:"status" validate:"required,CancelReservationStatusEnumType"` 17 | StatusInfo StatusInfoType `json:"statusInfo,omitempty" ` 18 | } 19 | 20 | type CertificateSignedRes struct { 21 | Status string `json:"status" validate:"required,CertificateSignedStatusEnumType"` 22 | StatusInfo StatusInfoType `json:"statusInfo,omitempty" ` 23 | } 24 | 25 | type ChangeAvailabilityRes struct { 26 | Status string `json:"status" validate:"required,ChangeAvailabilityStatusEnumType"` 27 | StatusInfo StatusInfoType `json:"statusInfo,omitempty" ` 28 | } 29 | 30 | type ClearCacheRes struct { 31 | Status string `json:"status" validate:"required,ClearCacheStatusEnumType"` 32 | StatusInfo StatusInfoType `json:"statusInfo,omitempty" ` 33 | } 34 | 35 | type ClearChargingProfileRes struct { 36 | Status string `json:"status" validate:"required,ClearChargingProfileStatusEnumType"` 37 | StatusInfo StatusInfoType `json:"statusInfo,omitempty" ` 38 | } 39 | 40 | type ClearDisplayMessageRes struct { 41 | Status string `json:"status" validate:"required,ClearMessageStatusEnumType"` 42 | StatusInfo StatusInfoType `json:"statusInfo,omitempty" ` 43 | } 44 | 45 | type ClearedChargingLimitRes struct{} 46 | 47 | type ClearVariableMonitoringRes struct { 48 | ClearMonitoringResult []ClearMonitoringResultType `json:"clearMonitoringResult" validate:"required,dive,required"` 49 | } 50 | 51 | type CostUpdatedRes struct{} 52 | 53 | type CustomerInformationRes struct { 54 | Status string `json:"status" validate:"required,CustomerInformationStatusEnumType"` 55 | StatusInfo StatusInfoType `json:"statusInfo,omitempty" ` 56 | } 57 | 58 | type DataTransferRes struct { 59 | Status string `json:"status" validate:"required,DataTransferStatusEnumType"` 60 | Data interface{} `json:"data,omitempty" ` 61 | StatusInfo StatusInfoType `json:"statusInfo,omitempty" ` 62 | } 63 | 64 | type DeleteCertificateRes struct { 65 | Status string `json:"status" validate:"required,DeleteCertificateStatusEnumType"` 66 | StatusInfo StatusInfoType `json:"statusInfo,omitempty" ` 67 | } 68 | 69 | type FirmwareStatusNotificationRes struct{} 70 | 71 | type Get15118EVCertificateRes struct { 72 | Status string `json:"status" validate:"required,Iso15118EVCertificateStatusEnumType"` // todo 73 | ExiResponse string `json:"exiResponse" validate:"required,max=5600"` 74 | StatusInfo StatusInfoType `json:"statusInfo,omitempty" ` 75 | } 76 | 77 | type GetBaseReportRes struct { 78 | Status string `json:"status" validate:"required,GenericDeviceModelStatusEnumType"` // todo 79 | StatusInfo StatusInfoType `json:"statusInfo,omitempty"` 80 | } 81 | 82 | type GetCertificateStatusRes struct { 83 | Status string `json:"status" validate:"required,GetCertificateStatusEnumType"` // todo 84 | OcspResult string `json:"ocspResult,omitempty" validate:"omitempty,max=5500"` 85 | StatusInfo StatusInfoType `json:"statusInfo,omitempty"` 86 | } 87 | 88 | type GetChargingProfilesRes struct { 89 | Status string `json:"status" validate:"required,GetChargingProfilesStatusEnumType"` // todo 90 | StatusInfo StatusInfoType `json:"statusInfo,omitempty"` 91 | } 92 | 93 | type GetCompositeScheduleRes struct { 94 | Status string `json:"status" validate:"required,GenericStatusEnumType"` 95 | Schedule CompositeScheduleType `json:"schedule,omitempty"` 96 | StatusInfo StatusInfoType `json:"statusInfo,omitempty"` 97 | } 98 | 99 | type GetDisplayMessagesRes struct { 100 | Status string `json:"status" validate:"required,GetDisplayMessagesStatusEnumType"` 101 | StatusInfo StatusInfoType `json:"statusInfo,omitempty"` 102 | } 103 | 104 | type GetInstalledCertificateIdsRes struct { 105 | Status string `json:"status" validate:"required,GetInstalledCertificateIdsStatusEnumType"` 106 | CertificateHashDataChain []CertificateHashDataChainType `json:"certificateHashDataChain,omitempty" validate:"omitempty,dive,required"` 107 | StatusInfo StatusInfoType `json:"statusInfo,omitempty"` 108 | } 109 | 110 | type GetLocalListVersionRes struct { 111 | VersionNumber *int `json:"versionNumber" validate:"required"` 112 | } 113 | 114 | type GetLogRes struct { 115 | Status string `json:"status" validate:"required,LogStatusEnumType"` 116 | Filename string `json:"filename,omitempty" validate:"omitempty,max=255"` 117 | StatusInfo StatusInfoType `json:"statusInfo,omitempty"` 118 | } 119 | 120 | type GetMonitoringReportRes struct { 121 | Status string `json:"status" validate:"required,GenericDeviceModelStatusEnumType"` 122 | StatusInfo StatusInfoType `json:"statusInfo,omitempty"` 123 | } 124 | 125 | type GetReportRes struct { 126 | Status string `json:"status" validate:"required,GenericDeviceModelStatusEnumType"` 127 | StatusInfo StatusInfoType `json:"statusInfo,omitempty"` 128 | } 129 | 130 | type GetTransactionStatusRes struct { 131 | OngoingIndicator bool `json:"ongoingIndicator,omitempty"` 132 | MessagesInQueue bool `json:"messagesInQueue" validate:"required"` 133 | } 134 | 135 | type GetVariablesRes struct { 136 | GetVariableResult []GetVariableResultType `json:"getVariableResult" validate:"required,dive,required"` 137 | } 138 | 139 | type HeartbeatRes struct { 140 | CurrentTime string `json:"currentTime" validate:"required,ISO8601date"` 141 | } 142 | 143 | type InstallCertificateRes struct { 144 | Status string `json:"status" validate:"required,InstallCertificateStatusEnumType"` 145 | StatusInfo StatusInfoType `json:"statusInfo,omitempty" ` 146 | } 147 | 148 | type LogStatusNotificationRes struct{} 149 | 150 | type MeterValuesRes struct{} 151 | 152 | type NotifyChargingLimitRes struct{} 153 | 154 | type NotifyCustomerInformationRes struct{} 155 | 156 | type NotifyDisplayMessagesRes struct{} 157 | 158 | type NotifyEVChargingNeedsRes struct { 159 | Status string `json:"status" validate:"required,NotifyEVChargingNeedsStatusEnumType"` 160 | StatusInfo StatusInfoType `json:"statusInfo,omitempty" ` 161 | } 162 | 163 | type NotifyEVChargingScheduleRes struct { 164 | Status string `json:"status" validate:"required,GenericStatusEnumType"` 165 | StatusInfo StatusInfoType `json:"statusInfo,omitempty" ` 166 | } 167 | 168 | type NotifyEventRes struct{} 169 | 170 | type NotifyMonitoringReportRes struct{} 171 | 172 | type NotifyReportRes struct{} 173 | 174 | type PublishFirmwareRes struct { 175 | Status string `json:"status" validate:"required,GenericStatusEnumType"` 176 | StatusInfo StatusInfoType `json:"statusInfo,omitempty" ` 177 | } 178 | 179 | type PublishFirmwareStatusNotificationRes struct{} 180 | 181 | type ReportChargingProfilesRes struct{} 182 | 183 | type RequestStartTransactionRes struct { 184 | Status string `json:"status" validate:"required,RequestStartStopStatusEnumType"` 185 | TransactionId string `json:"transactionId,omitempty" validate:"omitempty,max=36"` 186 | StatusInfo StatusInfoType `json:"statusInfo,omitempty" ` 187 | } 188 | 189 | type RequestStopTransactionRes struct { 190 | Status string `json:"status" validate:"required,RequestStartStopStatusEnumType"` 191 | StatusInfo StatusInfoType `json:"statusInfo,omitempty"` 192 | } 193 | 194 | type ReservationStatusUpdateRes struct{} 195 | 196 | type ReserveNowRes struct { 197 | Status string `json:"status" validate:"required,ReserveNowStatusEnumType"` 198 | StatusInfo StatusInfoType `json:"statusInfo,omitempty" ` 199 | } 200 | 201 | type ResetRes struct { 202 | Status string `json:"status" validate:"required,ResetStatusEnumType"` 203 | StatusInfo StatusInfoType `json:"statusInfo,omitempty" ` 204 | } 205 | 206 | type SecurityEventNotificationRes struct{} 207 | 208 | type SendLocalListRes struct { 209 | Status string `json:"status" validate:"required,SendLocalListStatusEnumType"` 210 | StatusInfo StatusInfoType `json:"statusInfo,omitempty" ` 211 | } 212 | 213 | type SetChargingProfileRes struct { 214 | Status string `json:"status" validate:"required,ChargingProfileStatusEnumType"` 215 | StatusInfo StatusInfoType `json:"statusInfo,omitempty" ` 216 | } 217 | 218 | type SetDisplayMessageRes struct { 219 | Status string `json:"status" validate:"required,DisplayMessageStatusEnumType"` 220 | StatusInfo StatusInfoType `json:"statusInfo,omitempty" ` 221 | } 222 | 223 | type SetMonitoringBaseRes struct { 224 | Status string `json:"status" validate:"required,GenericDeviceModelStatusEnumType"` 225 | StatusInfo StatusInfoType `json:"statusInfo,omitempty" ` 226 | } 227 | 228 | type SetMonitoringLevelRes struct { 229 | Status string `json:"status" validate:"required,GenericStatusEnumType"` 230 | StatusInfo StatusInfoType `json:"statusInfo,omitempty" ` 231 | } 232 | 233 | type SetNetworkProfileRes struct { 234 | Status string `json:"status" validate:"required,SetNetworkProfileStatusEnumType"` 235 | StatusInfo StatusInfoType `json:"statusInfo,omitempty" ` 236 | } 237 | 238 | type SetVariableMonitoringRes struct { 239 | SetMonitoringResult []SetMonitoringResultType `json:"setMonitoringResult" validate:"required,dive,required"` 240 | } 241 | 242 | type SetVariablesRes struct { 243 | SetVariableResult []SetVariableResultType `json:"setVariableResult" validate:"required,dive,required"` 244 | } 245 | 246 | type SignCertificateRes struct { 247 | Status string `json:"status" validate:"required,GenericStatusEnumType"` 248 | StatusInfo StatusInfoType `json:"statusInfo,omitempty" ` 249 | } 250 | 251 | type StatusNotificationRes struct{} 252 | 253 | type TransactionEventRes struct { 254 | TotalCost float32 `json:"totalCost,omitempty" validate:"omitempty,min=0"` 255 | ChargingPriority *int `json:"chargingPriority,omitempty" validate:"omitempty,gte=-9,lte=9"` 256 | IdTokenInfo IdTokenInfoType `json:"idTokenInfo,omitempty"` 257 | UpdatedPersonalMessage MessageContentType `json:"updatedPersonalMessage,omitempty"` 258 | } 259 | 260 | type TriggerMessageRes struct { 261 | Status string `json:"status" validate:"required,TriggerMessageStatusEnumType"` 262 | StatusInfo StatusInfoType `json:"statusInfo,omitempty" ` 263 | } 264 | 265 | type UnlockConnectorRes struct { 266 | Status string `json:"status" validate:"required,UnlockStatusEnumType"` 267 | StatusInfo StatusInfoType `json:"statusInfo,omitempty" ` 268 | } 269 | 270 | type UnpublishFirmwareRes struct { 271 | Status string `json:"status" validate:"required,UnpublishFirmwareStatusEnumType"` 272 | } 273 | 274 | type UpdateFirmwareRes struct { 275 | Status string `json:"status" validate:"required,UpdateFirmwareStatusEnumType"` 276 | StatusInfo StatusInfoType `json:"statusInfo,omitempty" ` 277 | } 278 | -------------------------------------------------------------------------------- /v201/call.go: -------------------------------------------------------------------------------- 1 | package v201 2 | 3 | type AuthorizeReq struct { 4 | Certificate string `json:"certificate,omitempty" validate:"omitempty,max=5500"` 5 | IdToken IdTokenType `json:"idTokenType" validate:"required"` 6 | Iso15118CertificateHashData []OCSPRequestDataType `json:"iso15118CertificateHashData,omitempty" validate:"omitempty,max=4,dive,required"` 7 | } 8 | 9 | type BootNotificationReq struct { 10 | Reason string `json:"reason" validate:"required,BootReasonEnumType"` 11 | ChargingStation ChargingStationType `json:"chargingStation" validate:"required"` 12 | } 13 | 14 | type CancelReservationReq struct { 15 | ReservationId *int `json:"reservationId" validate:"required"` 16 | } 17 | 18 | type CertificateSignedReq struct { 19 | CertificateChain string `json:"certificateChain" validate:"required,max=10000"` 20 | CertificateType string `json:"certificateType,omitempty" validate:"omitempty,CertificateSigningUseEnumType"` 21 | } 22 | 23 | type ChangeAvailabilityReq struct { 24 | OperationalStatus string `json:"operationalStatus" validate:"required,OperationalStatusEnumType"` 25 | Evse EVSEType `json:"evse,omitempty"` 26 | } 27 | 28 | type ClearCacheReq struct{} 29 | 30 | type ClearChargingProfileReq struct { 31 | ChargingProfileId int `json:"chargingProfileId,omitempty"` 32 | ChargingProfileCriteria ClearChargingProfileType `json:"chargingProfileCriteria,omitempty"` 33 | } 34 | 35 | type ClearDisplayMessageReq struct { 36 | Id *int `json:"id" validate:"required"` 37 | } 38 | 39 | type ClearedChargingLimitReq struct { 40 | ChargingLimitSource string `json:"chargingLimitSource" validate:"required,ChargingLimitSourceEnumType"` 41 | EvseId *int `json:"evseId,omitempty"` 42 | } 43 | 44 | type ClearVariableMonitoringReq struct { 45 | Id []int `json:"id" validate:"required,dive,required"` 46 | } 47 | 48 | type CostUpdatedReq struct { 49 | TotalCost float32 `json:"totalCost" validate:"required"` 50 | TransactionId string `json:"transactionId" validate:"required,max=36"` 51 | } 52 | 53 | type CustomerInformationReq struct { 54 | RequestId int `json:"requestId" validate:"required"` 55 | Report bool `json:"report" validate:"required"` 56 | Clear bool `json:"clear" validate:"required"` 57 | CustomerIdentifier string `json:"customerIdentifier,omitempty" validate:"omitempty,max=64"` 58 | IdToken IdTokenType `json:"idTokenType,omitempty"` 59 | CustomerCertificate CertificateHashDataType `json:"customerCertificate,omitempty" ` 60 | } 61 | 62 | type DataTransferReq struct { 63 | MessageId string `json:"messageId,omitempty" validate:"omitempty,max=50"` 64 | Data interface{} `json:"data,omitempty"` 65 | VendorId string `json:"vendorId" validate:"required,max=255"` 66 | } 67 | 68 | type DeleteCertificateReq struct { 69 | CertificateHashData CertificateHashDataType `json:"certificateHashData" validate:"required"` 70 | } 71 | 72 | type FirmwareStatusNotificationReq struct { 73 | Status string `json:"status" validate:"required,FirmwareStatusEnumType"` 74 | RequestId int `json:"requestId,omitempty" ` 75 | } 76 | 77 | type Get15118EVCertificateReq struct { 78 | Iso15118SchemaVersion string `json:"iso15118SchemaVersion" validate:"required,max=50"` 79 | Action string `json:"action" validate:"required,CertificateActionEnumType"` 80 | ExiRequest string `json:"exiRequest" validate:"required,max=5600"` 81 | } 82 | 83 | type GetBaseReportReq struct { 84 | RequestId int `json:"requestId"` 85 | ReportBase string `json:"reportBase" validate:"required,ReportBaseEnumType"` 86 | } 87 | 88 | type GetCertificateStatusReq struct { 89 | OcspRequestData OCSPRequestDataType `json:"ocspRequestData" validate:"required"` 90 | } 91 | 92 | type GetChargingProfilesReq struct { 93 | RequestId int `json:"requestId" validate:"required"` 94 | EvseId int `json:"evseId,omitempty"` 95 | ChargingProfile ChargingProfileCriterionType `json:"chargingProfile" validate:"required"` 96 | } 97 | 98 | type GetCompositeScheduleReq struct { 99 | Duration int `json:"duration" validate:"required"` 100 | ChargingRateUnit string `json:"chargingRateUnit,omitempty" validate:"omitempty,ChargingRateUnitEnumType"` 101 | EvseId *int `json:"evseId" validate:"required"` 102 | } 103 | 104 | type GetDisplayMessagesReq struct { 105 | Id []int `json:"id,omitempty" validate:"omitempty,dive,required"` 106 | RequestId int `json:"requestId" validate:"required"` 107 | Priority string `json:"priority,omitempty" validate:"omitempty,MessagePriorityEnumType"` 108 | State string `json:"state,omitempty" validate:"omitempty,MessageStateEnumType"` 109 | } 110 | 111 | type GetInstalledCertificateIdsReq struct { 112 | CertificateType []string `json:"certificateType,omitempty" validate:"omitempty,dive,GetCertificateIdUseEnumType"` 113 | } 114 | 115 | type GetLocalListVersionReq struct{} 116 | 117 | type GetLogReq struct { 118 | LogType string `json:"logType" validate:"required,LogEnumType"` 119 | RequestId int `json:"requestId" validate:"required"` 120 | Retries int `json:"retries,omitempty"` 121 | RetryInterval int `json:"retryInterval,omitempty"` 122 | Log LogParametersType `json:"log" validate:"required"` 123 | } 124 | 125 | type GetMonitoringReportReq struct { 126 | RequestId int `json:"requestId" validate:"required"` 127 | MonitoringCriteria []string `json:"monitoringCriteria,omitempty" validate:"omitempty,max=3,dive,required,MonitoringCriteriaEnumType"` 128 | ComponentVariable []ComponentVariableType `json:"componentVariable,omitempty" validate:"omitempty,dive,required"` 129 | } 130 | 131 | type GetReportReq struct { 132 | RequestId int `json:"requestId" validate:"required"` 133 | ComponentCriteria []string `json:"componentCriteria,omitempty" validate:"omitempty,max=4,dive,required,ComponentCriteriaEnumType"` 134 | ComponentVariable []ComponentVariableType `json:"componentVariable,omitempty" validate:"omitempty,dive,required"` 135 | } 136 | 137 | type GetTransactionStatusReq struct { 138 | TransactionId string `json:"transactionId,omitempty" validate:"omiempty,max=36"` 139 | } 140 | 141 | type GetVariablesReq struct { 142 | GetVariableData []GetVariableDataType `json:"getVariableData" validate:"required,dive,required"` 143 | } 144 | 145 | type HeartbeatReq struct{} 146 | 147 | type InstallCertificateReq struct { 148 | CertificateType string `json:"certificateType" validate:"required,InstallCertificateUseEnumType"` 149 | Certificate string `json:"certificate" validate:"required,max=5500"` 150 | } 151 | 152 | type LogStatusNotificationReq struct { 153 | Status string `json:"status" validate:"required,UploadLogStatusEnumType"` 154 | RequestId int `json:"requestId,omitempty"` 155 | } 156 | 157 | type MeterValuesReq struct { 158 | EvseId *int `json:"evseId" validate:"required,gte=0"` 159 | MeterValue []MeterValueType `json:"meterValue" validate:"required,dive,required"` 160 | } 161 | 162 | type NotifyChargingLimitReq struct { 163 | EvseId int `json:"evseId,omitempty" validate:"omitempty,gt=0"` 164 | ChargingLimit ChargingLimitType `json:"chargingLimit" validate:"required"` 165 | ChargingSchedule []ChargingScheduleType `json:"chargingSchedule,omitempty" validate:"omitempty,dive,required"` 166 | } 167 | 168 | type NotifyCustomerInformationReq struct { 169 | Data string `json:"data" validate:"required,max=512"` 170 | Tbc bool `json:"tbc,omitempty"` 171 | SeqNo *int `json:"seqNo" validate:"required,gte=0"` 172 | GeneratedAt string `json:"generatedAt" validate:"required,ISO8601date"` 173 | RequestId int `json:"requestId" validate:"required"` 174 | } 175 | 176 | type NotifyDisplayMessagesReq struct { 177 | RequestId int `json:"requestId" validate:"required"` 178 | Tbc bool `json:"tbc,omitempty"` 179 | MessageInfo []MessageInfoType `json:"messageInfo,omitempty" validate:"omitempty,dive,required"` 180 | } 181 | 182 | type NotifyEVChargingNeedsReq struct { 183 | MaxScheduleTuples *int `json:"maxScheduleTuples,omitempty"` 184 | EvseId *int `json:"evseId" validate:"required,gt=0"` 185 | ChargingNeeds ChargingNeedsType `json:"chargingNeeds" validate:"required"` 186 | } 187 | 188 | type NotifyEVChargingScheduleReq struct { 189 | TimeBase string `json:"timeBase" validate:"required,ISO8601date"` 190 | EvseId int `json:"evseId" validate:"required,gt=0"` 191 | ChargingSchedule ChargingScheduleType `json:"chargingSchedule" validate:"required"` 192 | } 193 | 194 | type NotifyEventReq struct { 195 | GeneratedAt string `json:"generatedAt" validate:"required,ISO8601date"` 196 | Tbc bool `json:"tbc,omitempty"` 197 | SeqNo *int `json:"seqNo" validate:"required,gte=0"` 198 | EventData []EventDataType `json:"eventData" validate:"required,dive,required"` 199 | } 200 | 201 | type NotifyMonitoringReportReq struct { 202 | RequestId int `json:"requestId" validate:"required"` 203 | Tbc bool `json:"tbc,omitempty"` 204 | SeqNo *int `json:"seqNo" validate:"required,gte=0"` 205 | GeneratedAt string `json:"generatedAt" validate:"required,ISO8601date"` 206 | Monitor []MonitoringDataType `json:"monitor,omitempty" validate:"omitempty,dive,required"` 207 | } 208 | 209 | type NotifyReportReq struct { 210 | RequestId int `json:"requestId" validate:"required"` 211 | GeneratedAt string `json:"generatedAt" validate:"required,ISO8601date"` 212 | Tbc bool `json:"tbc,omitempty"` 213 | SeqNo *int `json:"seqNo" validate:"required,gte=0"` 214 | ReportData []ReportDataType `json:"monitor,omitempty" validate:"omitempty,dive,required"` 215 | } 216 | 217 | type PublishFirmwareReq struct { 218 | Location string `json:"location" validate:"required,max=512"` 219 | Retries int `json:"retries,omitempty"` 220 | CheckSum string `json:"checkSum" validate:"required,max=32"` 221 | RequestId int `json:"requestId" validate:"required"` 222 | RetryInterval int `json:"retryInterval,omitempty"` 223 | } 224 | 225 | type PublishFirmwareStatusNotificationReq struct { 226 | Status string `json:"status" validate:"required,PublishFirmwareStatusEnumType"` 227 | Location []string `json:"location,omitempty" validate:"omitempty,dive,required,max=512"` 228 | RequestId int `json:"requestId,omitempty"` 229 | } 230 | 231 | type ReportChargingProfilesReq struct { 232 | RequestId int `json:"requestId" validate:"required"` 233 | ChargingLimitSource string `json:"chargingLimitSource" validate:"required,ChargingLimitSourceEnumType"` 234 | Tbc bool `json:"tbc,omitempty"` 235 | EvseId *int `json:"evseId" validate:"required,gte=0"` 236 | ChargingProfile []ChargingProfileType `json:"chargingProfile" validate:"required,dive,required"` 237 | } 238 | 239 | type RequestStartTransactionReq struct { 240 | EvseId *int `json:"evseId,omitempty" validate:"omitempty,gt=0"` 241 | RemoteStartId int `json:"remoteStartId" validate:"required"` 242 | IdToken IdTokenType `json:"idToken" validate:"required"` 243 | ChargingProfile ChargingProfileType `json:"chargingProfile,omitempty"` 244 | GroupIdToken IdTokenType `json:"groupIdToken,omitempty"` 245 | } 246 | 247 | type RequestStopTransactionReq struct { 248 | TransactionId string `json:"transactionId" validate:"required,max=36"` 249 | } 250 | 251 | type ReservationStatusUpdateReq struct { 252 | ReservationId int `json:"reservationId" validate:"required"` 253 | ReservationUpdateStatus string `json:"reservationUpdateStatus" validate:"required,ReservationUpdateStatusEnumType"` 254 | } 255 | 256 | type ReserveNowReq struct { 257 | Id int `json:"id" validate:"required"` 258 | ExpiryDateTime string `json:"expiryDateTime" validate:"required,ISO8601date"` 259 | ConnectorType string `json:"connectorType,omitempty" validate:"omitempty,ConnectorEnumType"` 260 | EvseId *int `json:"evseId,omitempty"` 261 | IdToken IdTokenType `json:"idToken" validate:"required"` 262 | GroupIdToken IdTokenType `json:"groupIdToken,omitempty"` 263 | } 264 | 265 | type ResetReq struct { 266 | Type string `json:"type" validate:"required,ResetTypeEnumType"` 267 | EvseId *int `json:"evseId,omitempty"` 268 | } 269 | 270 | type SecurityEventNotificationReq struct { 271 | Type string `json:"type" validate:"required,ResetTypeEnumType"` 272 | Timestamp string `json:"timestamp" validate:"required,ISO8601date"` 273 | TechInfo string `json:"techInfo,omitempty" validate:"omitempty,max=255"` 274 | } 275 | 276 | type SendLocalListReq struct { 277 | VersionNumber *int `json:"versionNumber" validate:"required"` 278 | UpdateType string `json:"updateType" validate:"required,UpdateTypeEnumType"` 279 | LocalAuthorizationList []AuthorizarionData `json:"localAuthorizationList,omitempty" validate:"omitempty,dive,required"` 280 | } 281 | 282 | type SetChargingProfileReq struct { 283 | EvseId *int `json:"evseId" validate:"required,gte=0"` 284 | ChargingProfile ChargingProfileType `json:"chargingProfile" validate:"required"` 285 | } 286 | 287 | type SetDisplayMessageReq struct { 288 | Message MessageInfoType `json:"message" validate:"required"` 289 | } 290 | 291 | type SetMonitoringBaseReq struct { 292 | MonitoringBase string `json:"monitoringBase" validate:"required,MonitoringBaseEnumType"` 293 | } 294 | 295 | type SetMonitoringLevelReq struct { 296 | Severity *int `json:"severity" validate:"required,gte=0,lte=9"` 297 | } 298 | 299 | type SetNetworkProfileReq struct { 300 | ConfigurationSlot int `json:"configurationSlot" validate:"required"` 301 | ConnectionData NetworkConnectionProfileType `json:"connectionData" validate:"required"` 302 | } 303 | 304 | type SetVariableMonitoringReq struct { 305 | SetMonitoringData []SetMonitoringDataType `json:"setMonitoringData" validate:"required,dive,required"` 306 | } 307 | 308 | type SetVariablesReq struct { 309 | SetVariableData []SetVariableDataType `json:"setVariableData" validate:"required,dive,required"` 310 | } 311 | 312 | type SignCertificateReq struct { 313 | Csr string `json:"csr" validate:"required,max=5500"` 314 | Certificate string `json:"certificate,omitempty" validate:"omitempty,CertificateSigningUseEnumType"` 315 | } 316 | 317 | type StatusNotificationReq struct { 318 | Timestamp string `json:"timestamp" validate:"required,ISO8601date"` 319 | ConnectorStatus string `json:"connectorStatus" validate:"required,ConnectorStatusEnumType"` 320 | EvseId *int `json:"evseId" validate:"required"` 321 | ConnectorId *int `json:"connectorId" validate:"required"` 322 | } 323 | 324 | type TransactionEventReq struct { 325 | EventType string `json:"eventType" validate:"required,TransactionEventTypeEnumType"` 326 | Timestamp string `json:"timestamp" validate:"required,ISO8601date"` 327 | TriggerReason string `json:"triggerReason" validate:"required,TriggerReasonEnumType"` 328 | SeqNo int `json:"seqNo" validate:"required"` 329 | Offline bool `json:"offline,omitempty"` 330 | NumberOfPhasesUsed int `json:"numberOfPhasesUsed,omitempty"` 331 | CableMaxCurrent int `json:"cableMaxCurrent,omitempty"` 332 | ReservationId int `json:"reservationId,omitempty"` 333 | TransactionInfo TransactionType `json:"transactionInfo" validate:"required"` 334 | IdToken IdTokenType `json:"idToken,omitempty"` 335 | Evse EVSEType `json:"evse,omitempty"` 336 | MeterValue []MeterValueType `json:"meterValue,omitempty" validate:"omitempty,dive,required"` 337 | } 338 | 339 | type TriggerMessageReq struct { 340 | RequestedMessage string `json:"requestedMessage" validate:"required,MessageTriggerEnumType"` 341 | Evse EVSEType `json:"evse,omitempty"` 342 | } 343 | 344 | type UnlockConnectorReq struct { 345 | EvseId *int `json:"evseId" validate:"required,gte=0"` 346 | ConnectorId *int `json:"connectorId" validate:"required,gte=0"` 347 | } 348 | 349 | type UnpublishFirmwareReq struct { 350 | Checksum string `json:"checksum" validate:"required,max=32"` 351 | } 352 | 353 | type UpdateFirmwareReq struct { 354 | Retries int `json:"retries,omitempty"` 355 | RetryInterval int `json:"retryInterval,omitempty"` 356 | RequestId int `json:"requestId" validate:"required"` 357 | Firmware FirmwareType `json:"firmware" validate:"required"` 358 | } 359 | -------------------------------------------------------------------------------- /v16/validation_register.go: -------------------------------------------------------------------------------- 1 | package v16 2 | 3 | import ( 4 | "reflect" 5 | "regexp" 6 | "strings" 7 | 8 | "gopkg.in/go-playground/validator.v9" 9 | ) 10 | 11 | var Validate = validator.New() 12 | 13 | func contains(elems []string, v string) bool { 14 | for _, s := range elems { 15 | if v == s { 16 | return true 17 | } 18 | } 19 | return false 20 | } 21 | 22 | // replaces some validators with GenericStatusEnumType 23 | func init() { 24 | 25 | // register function to get tag name from json tags. 26 | Validate.RegisterTagNameFunc(func(fld reflect.StructField) string { 27 | name := strings.SplitN(fld.Tag.Get("json"), ",", 2)[0] 28 | if name == "-" { 29 | return "" 30 | } 31 | return name 32 | }) 33 | Validate.RegisterValidation("ISO8601date", IsISO8601Date) 34 | Validate.RegisterValidation("RegistrationStatus", isValidRegistrationStatus) 35 | Validate.RegisterValidation("AuthorizationStatus", isValidAuthorizationStatus) 36 | Validate.RegisterValidation("DiagnosticsStatus", isValidDiagnosticsStatus) 37 | Validate.RegisterValidation("FirmwareStatus", isValidFirmwareStatus) 38 | Validate.RegisterValidation("ReadingContext", isValidReadingContext) 39 | Validate.RegisterValidation("ValueFormat", isValidValueFormat) 40 | Validate.RegisterValidation("Measurand", isValidMeasurand) 41 | Validate.RegisterValidation("Phase", isValidPhase) 42 | Validate.RegisterValidation("Location", isValidLocation) 43 | Validate.RegisterValidation("UnitOfMeasure", isValidUnitOfMeasure) 44 | Validate.RegisterValidation("ChargePointErrorCode", isValidChargePointErrorCode) 45 | Validate.RegisterValidation("ChargePointStatus", isValidChargePointStatus) 46 | Validate.RegisterValidation("Reason", isValidReason) 47 | Validate.RegisterValidation("DataTransferStatus", isValidDataTransferStatus) 48 | Validate.RegisterValidation("AvailabilityType", isValidAvailabilityType) 49 | Validate.RegisterValidation("AvailabilityStatus", isValidAvailabilityStatus) 50 | Validate.RegisterValidation("ConfigurationStatus", isValidConfigurationStatus) 51 | Validate.RegisterValidation("ClearCacheStatus", isValidGenericStatusEnumType) // generic status enum type 52 | Validate.RegisterValidation("ChargingProfilePurposeType", isValidChargingProfilePurposeType) 53 | Validate.RegisterValidation("ChargingRateUnitType", isValidChargingRateUnitType) 54 | Validate.RegisterValidation("ChargingProfileKindType", isValidChargingProfileKindType) 55 | Validate.RegisterValidation("RecurrencyKindType", isValidRecurrencyKindType) 56 | Validate.RegisterValidation("ResetType", isValidResetType) 57 | Validate.RegisterValidation("MessageTrigger", isValidMessageTrigger) 58 | Validate.RegisterValidation("ClearChargingProfileStatus", isValidClearChargingProfileStatus) 59 | Validate.RegisterValidation("RemoteStartStopStatus", isValidGenericStatusEnumType) // generic status enum type 60 | Validate.RegisterValidation("ReservationStatus", isValidReservationStatus) 61 | Validate.RegisterValidation("ResetStatus", isValidGenericStatusEnumType) // generic status enum type 62 | Validate.RegisterValidation("UpdateStatus", isValidUpdateStatus) 63 | Validate.RegisterValidation("ChargingProfileStatus", isValidChargingProfileStatus) 64 | Validate.RegisterValidation("TriggerMessageStatus", isValidTriggerMessageStatus) 65 | Validate.RegisterValidation("UnlockStatus", isValidUnlockStatus) 66 | Validate.RegisterValidation("CancelReservationStatus", isValidGenericStatusEnumType) // generic status enum type 67 | Validate.RegisterValidation("GetCompositeScheduleStatus", isValidGenericStatusEnumType) // generic status enum type 68 | Validate.RegisterValidation("FirmwareStatus", isValidFirmwareStatusEnumType) 69 | Validate.RegisterValidation("CertificateSignedStatusEnumType", isValidGenericStatusEnumType) // generic status enum type 70 | Validate.RegisterValidation("CertificateStatusEnumType", isValidCertificateStatusEnumType) 71 | Validate.RegisterValidation("CertificateUseTypeEnumType", isValidCertificateUseTypeEnumType) 72 | Validate.RegisterValidation("DeleteCertificateStatusEnumType", isValidDeleteCertificateStatusEnumType) 73 | Validate.RegisterValidation("FirmwareStatusEnumType", isValidFirmwareStatusEnumType) 74 | Validate.RegisterValidation("GenericStatusEnumType", isValidGenericStatusEnumType) 75 | Validate.RegisterValidation("GetInstalledCertificateStatusEnumType", isValidGetInstalledCertificateStatusEnumType) 76 | Validate.RegisterValidation("HashAlgorithmEnumType", isValidHashAlgorithmEnumType) 77 | Validate.RegisterValidation("LogEnumType", isValidLogEnumType) 78 | Validate.RegisterValidation("LogStatusEnumType", isValidLogStatusEnumType) 79 | Validate.RegisterValidation("MessageTriggerEnumType", isValidMessageTriggerEnumType) 80 | Validate.RegisterValidation("TriggerMessageStatusEnumType", isValidTriggerMessageStatusEnumType) 81 | Validate.RegisterValidation("UpdateFirmwareStatusEnumType", isValidUpdateFirmwareStatusEnumType) 82 | Validate.RegisterValidation("UploadLogStatusEnumType", isValidUploadLogStatusEnumType) 83 | } 84 | 85 | func IsISO8601Date(fl validator.FieldLevel) bool { 86 | ISO8601DateRegexString := "^(?:[1-9]\\d{3}-(?:(?:0[1-9]|1[0-2])-(?:0[1-9]|1\\d|2[0-8])|(?:0[13-9]|1[0-2])-(?:29|30)|(?:0[13578]|1[02])-31)|(?:[1-9]\\d(?:0[48]|[2468][048]|[13579][26])|(?:[2468][048]|[13579][26])00)-02-29)T(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d{1,9})?(?:Z|[+-][01]\\d:[0-5]\\d)$" 87 | ISO8601DateRegex := regexp.MustCompile(ISO8601DateRegexString) 88 | return ISO8601DateRegex.MatchString(fl.Field().String()) 89 | } 90 | 91 | func isValidRegistrationStatus(fl validator.FieldLevel) bool { 92 | status := fl.Field().String() 93 | switch status { 94 | case "Accepted", "Pending", "Rejected": 95 | return true 96 | default: 97 | return false 98 | } 99 | } 100 | 101 | func isValidAuthorizationStatus(fl validator.FieldLevel) bool { 102 | status := fl.Field().String() 103 | switch status { 104 | case "Accepted", "Blocked", "Expired", "Invalid", "ConcurrentTx": 105 | return true 106 | default: 107 | return false 108 | } 109 | } 110 | 111 | func isValidDiagnosticsStatus(fl validator.FieldLevel) bool { 112 | status := fl.Field().String() 113 | switch status { 114 | case "Idle", "Uploaded", "UploadFailed", "Uploading": 115 | return true 116 | default: 117 | return false 118 | } 119 | } 120 | 121 | func isValidFirmwareStatus(fl validator.FieldLevel) bool { 122 | status := fl.Field().String() 123 | cases := []string{ 124 | "Downloaded", 125 | "DownloadFailed", 126 | "Downloading", 127 | "Idle", 128 | "InstallationFailed", 129 | "Installing", 130 | "Installed", 131 | } 132 | return contains(cases, status) 133 | } 134 | 135 | func isValidReadingContext(fl validator.FieldLevel) bool { 136 | context := fl.Field().String() 137 | cases := []string{ 138 | "Interruption.Begin", 139 | "Interruption.End", 140 | "Other", 141 | "Sample.Clock", 142 | "Sample.Periodic", 143 | "Transaction.Begin", 144 | "Transaction.End", 145 | "Trigger", 146 | } 147 | return contains(cases, context) 148 | } 149 | 150 | func isValidValueFormat(fl validator.FieldLevel) bool { 151 | format := fl.Field().String() 152 | switch format { 153 | case "Raw", "SignedData": 154 | return true 155 | default: 156 | return false 157 | } 158 | } 159 | 160 | func isValidMeasurand(fl validator.FieldLevel) bool { 161 | measurand := fl.Field().String() 162 | cases := []string{ 163 | "Current.Export", 164 | "Current.Import", 165 | "Current.Offered", 166 | "Energy.Active.Import.Register", 167 | "Energy.Active.Export.Register", 168 | "Energy.Reactive.Import.Register", 169 | "Energy.Reactive.Export.Register", 170 | "Energy.Active.Import.Interval", 171 | "Energy.Active.Export.Interval", 172 | "Energy.Reactive.Import.Interval", 173 | "Energy.Reactive.Export.Interval", 174 | "Frequency", 175 | "Power.Active.Export", 176 | "Power.Active.Import", 177 | "Power.Reactive.Export", 178 | "Power.Reactive.Import", 179 | "Power.Factor", 180 | "Power.Offered", 181 | "RPM", 182 | "SoC", 183 | "Temperature", 184 | "Voltage"} 185 | return contains(cases, measurand) 186 | } 187 | 188 | func isValidPhase(fl validator.FieldLevel) bool { 189 | phase := fl.Field().String() 190 | cases := []string{ 191 | "L1", 192 | "L2", 193 | "L3", 194 | "N", 195 | "L1-N", 196 | "L2-N", 197 | "L3-N", 198 | "L1-L2", 199 | "L2-L3", 200 | "L3-L1"} 201 | return contains(cases, phase) 202 | } 203 | 204 | func isValidLocation(fl validator.FieldLevel) bool { 205 | location := fl.Field().String() 206 | cases := []string{ 207 | "Body", 208 | "Cable", 209 | "EV", 210 | "Inlet", 211 | "Outlet", 212 | } 213 | return contains(cases, location) 214 | } 215 | 216 | func isValidUnitOfMeasure(fl validator.FieldLevel) bool { 217 | unit := fl.Field().String() 218 | cases := []string{ 219 | "Wh", 220 | "kWh", 221 | "varh", 222 | "kvarh", 223 | "W", 224 | "VA", 225 | "kVA", 226 | "var", 227 | "kvar", 228 | "A", 229 | "V", 230 | "Celsius", 231 | "Fahrenheit", 232 | "K", 233 | "Percent", 234 | } 235 | return contains(cases, unit) 236 | } 237 | 238 | func isValidChargePointErrorCode(fl validator.FieldLevel) bool { 239 | code := fl.Field().String() 240 | cases := []string{ 241 | "ConnectorLockFailure", 242 | "EVCommunicationError", 243 | "GroundFailure", 244 | "HighTemperature", 245 | "InternalError", 246 | "LocalListConflict", 247 | "NoError", 248 | "OtherError", 249 | "OverCurrentFailure", 250 | "OverVoltage", 251 | "PowerMeterFailure", 252 | "PowerSwitchFailure", 253 | "ReaderFailure", 254 | "ResetFailure", 255 | "UnderVoltage", 256 | "WeakSignal", 257 | } 258 | return contains(cases, code) 259 | } 260 | 261 | func isValidChargePointStatus(fl validator.FieldLevel) bool { 262 | status := fl.Field().String() 263 | cases := []string{ 264 | "Available", 265 | "Preparing", 266 | "Charging", 267 | "SuspendedEVSE", 268 | "SuspendedEV", 269 | "Finishing", 270 | "Reserved", 271 | "Unavailable", 272 | "Faulted", 273 | } 274 | return contains(cases, status) 275 | } 276 | 277 | func isValidReason(fl validator.FieldLevel) bool { 278 | reason := fl.Field().String() 279 | cases := []string{ 280 | "DeAuthorized", 281 | "EmergencyStop", 282 | "EVDisconnected", 283 | "HardReset", 284 | "Local", 285 | "Other", 286 | "PowerLoss", 287 | "Reboot", 288 | "Remote", 289 | "SoftReset", 290 | "UnlockCommand", 291 | } 292 | return contains(cases, reason) 293 | } 294 | 295 | func isValidDataTransferStatus(fl validator.FieldLevel) bool { 296 | status := fl.Field().String() 297 | switch status { 298 | case "Accepted", "Rejected", "UnknownMessageId", "UnknownVendorId": 299 | return true 300 | default: 301 | return false 302 | } 303 | } 304 | 305 | func isValidAvailabilityType(fl validator.FieldLevel) bool { 306 | type_ := fl.Field().String() 307 | switch type_ { 308 | case "Inoperative", "Operative": 309 | return true 310 | default: 311 | return false 312 | } 313 | 314 | } 315 | 316 | func isValidAvailabilityStatus(fl validator.FieldLevel) bool { 317 | status := fl.Field().String() 318 | switch status { 319 | case "Accepted", "Rejected", "Scheduled": 320 | return true 321 | default: 322 | return false 323 | } 324 | } 325 | 326 | func isValidConfigurationStatus(fl validator.FieldLevel) bool { 327 | status := fl.Field().String() 328 | switch status { 329 | case "Accepted", "Rejected", "RebootRequired", "NotSupported": 330 | return true 331 | default: 332 | return false 333 | } 334 | 335 | } 336 | 337 | func isValidChargingProfilePurposeType(fl validator.FieldLevel) bool { 338 | purpose := fl.Field().String() 339 | switch purpose { 340 | case "ChargePointMaxProfile", "TxDefaultProfile", "TxProfile": 341 | return true 342 | default: 343 | return false 344 | } 345 | } 346 | 347 | func isValidChargingRateUnitType(fl validator.FieldLevel) bool { 348 | kind := fl.Field().String() 349 | switch kind { 350 | case "W", "A": 351 | return true 352 | default: 353 | return false 354 | } 355 | } 356 | 357 | func isValidChargingProfileKindType(fl validator.FieldLevel) bool { 358 | kind := fl.Field().String() 359 | switch kind { 360 | case "Absolute", "Recurring", "Relative": 361 | return true 362 | default: 363 | return false 364 | } 365 | } 366 | 367 | func isValidRecurrencyKindType(fl validator.FieldLevel) bool { 368 | kind := fl.Field().String() 369 | switch kind { 370 | case "Daily", "Weekly": 371 | return true 372 | default: 373 | return false 374 | } 375 | } 376 | 377 | func isValidResetType(fl validator.FieldLevel) bool { 378 | kind := fl.Field().String() 379 | switch kind { 380 | case "Hard", "Soft": 381 | return true 382 | default: 383 | return false 384 | } 385 | } 386 | 387 | func isValidMessageTrigger(fl validator.FieldLevel) bool { 388 | trigger := fl.Field().String() 389 | cases := []string{ 390 | "BootNotification", 391 | "DiagnosticsStatusNotification", 392 | "FirmwareStatusNotification", 393 | "Heartbeat", 394 | "MeterValues", 395 | "StatusNotification", 396 | } 397 | return contains(cases, trigger) 398 | } 399 | 400 | func isValidClearChargingProfileStatus(fl validator.FieldLevel) bool { 401 | status := fl.Field().String() 402 | switch status { 403 | case "Accepted", "Unknown": 404 | return true 405 | default: 406 | return false 407 | } 408 | } 409 | 410 | func isValidReservationStatus(fl validator.FieldLevel) bool { 411 | status := fl.Field().String() 412 | switch status { 413 | case "Accepted", "Faulted", "Occupied", "Rejected", "Unavailable": 414 | return true 415 | default: 416 | return false 417 | } 418 | } 419 | 420 | func isValidUpdateStatus(fl validator.FieldLevel) bool { 421 | status := fl.Field().String() 422 | switch status { 423 | case "Accepted", "Failed", "NotSupported", "VersionMismatch": 424 | return true 425 | default: 426 | return false 427 | } 428 | } 429 | 430 | func isValidChargingProfileStatus(fl validator.FieldLevel) bool { 431 | status := fl.Field().String() 432 | switch status { 433 | case "Accepted", "Rejected", "NotSupported": 434 | return true 435 | default: 436 | return false 437 | } 438 | } 439 | 440 | func isValidTriggerMessageStatus(fl validator.FieldLevel) bool { 441 | status := fl.Field().String() 442 | switch status { 443 | case "Accepted", "Rejected", "NotImplemented": 444 | return true 445 | default: 446 | return false 447 | } 448 | } 449 | 450 | func isValidUnlockStatus(fl validator.FieldLevel) bool { 451 | status := fl.Field().String() 452 | switch status { 453 | case "Unlocked", "UnlockFailed", "NotSupported": 454 | return true 455 | default: 456 | return false 457 | } 458 | } 459 | 460 | func isValidCertificateStatusEnumType(fl validator.FieldLevel) bool { 461 | status := fl.Field().String() 462 | switch status { 463 | case "Accepted", "Failed", "Rejected": 464 | return true 465 | default: 466 | return false 467 | } 468 | } 469 | 470 | func isValidCertificateUseTypeEnumType(fl validator.FieldLevel) bool { 471 | status := fl.Field().String() 472 | switch status { 473 | case "CentralSystemRootCertificate", "ManufacturerRootCertificate": 474 | return true 475 | default: 476 | return false 477 | } 478 | } 479 | 480 | func isValidDeleteCertificateStatusEnumType(fl validator.FieldLevel) bool { 481 | status := fl.Field().String() 482 | switch status { 483 | case "Accepted", "Failed", "NotFound": 484 | return true 485 | default: 486 | return false 487 | } 488 | } 489 | 490 | func isValidFirmwareStatusEnumType(fl validator.FieldLevel) bool { 491 | status := fl.Field().String() 492 | cases := []string{ 493 | "Downloaded", 494 | "DownloadFailed", 495 | "Downloading", 496 | "DownloadScheduled", 497 | "DownloadPaused", 498 | "Idle", 499 | "InstallationFailed", 500 | } 501 | return contains(cases, status) 502 | } 503 | 504 | func isValidGenericStatusEnumType(fl validator.FieldLevel) bool { 505 | status := fl.Field().String() 506 | switch status { 507 | case "Accepted", "Rejected": 508 | return true 509 | default: 510 | return false 511 | } 512 | } 513 | 514 | func isValidGetInstalledCertificateStatusEnumType(fl validator.FieldLevel) bool { 515 | status := fl.Field().String() 516 | switch status { 517 | case "Accepted", "NotFound": 518 | return true 519 | default: 520 | return false 521 | } 522 | } 523 | 524 | func isValidHashAlgorithmEnumType(fl validator.FieldLevel) bool { 525 | status := fl.Field().String() 526 | switch status { 527 | case "SHA256", "SHA384", "SHA512": 528 | return true 529 | default: 530 | return false 531 | } 532 | } 533 | 534 | func isValidLogEnumType(fl validator.FieldLevel) bool { 535 | status := fl.Field().String() 536 | switch status { 537 | case "DiagnosticsLog", "SecurityLog": 538 | return true 539 | default: 540 | return false 541 | } 542 | } 543 | 544 | func isValidLogStatusEnumType(fl validator.FieldLevel) bool { 545 | status := fl.Field().String() 546 | switch status { 547 | case "Accepted", "Rejected", "AcceptedCanceled": 548 | return true 549 | default: 550 | return false 551 | } 552 | } 553 | 554 | func isValidMessageTriggerEnumType(fl validator.FieldLevel) bool { 555 | status := fl.Field().String() 556 | cases := []string{ 557 | "BootNotification", 558 | "LogStatusNotification", 559 | "FirmwareStatusNotification", 560 | "Heartbeat", 561 | "MeterValues", 562 | "SignChargePointCertificate", 563 | "StatusNotification", 564 | } 565 | return contains(cases, status) 566 | } 567 | 568 | func isValidTriggerMessageStatusEnumType(fl validator.FieldLevel) bool { 569 | status := fl.Field().String() 570 | switch status { 571 | case "Accepted", "Rejected", "NotImplemented": 572 | return true 573 | default: 574 | return false 575 | } 576 | } 577 | 578 | func isValidUpdateFirmwareStatusEnumType(fl validator.FieldLevel) bool { 579 | status := fl.Field().String() 580 | cases := []string{ 581 | "Accepted", 582 | "Rejected", 583 | "AcceptedCancled", 584 | "InvalidCertificate", 585 | "RevokedCertificate", 586 | } 587 | return contains(cases, status) 588 | } 589 | 590 | func isValidUploadLogStatusEnumType(fl validator.FieldLevel) bool { 591 | status := fl.Field().String() 592 | cases := []string{ 593 | "BadMessage", 594 | "Idle", 595 | "NotSupportedOperation", 596 | "PermissionDenied", 597 | "Uploaded", 598 | "UploadFailure", 599 | "Uploading", 600 | } 601 | return contains(cases, status) 602 | } 603 | -------------------------------------------------------------------------------- /charge_point.go: -------------------------------------------------------------------------------- 1 | package ocpp 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "fmt" 7 | "sync" 8 | "time" 9 | 10 | "github.com/aliml92/ocpp/logger" 11 | "github.com/aliml92/ocpp/v16" 12 | "github.com/aliml92/ocpp/v201" 13 | "github.com/google/uuid" 14 | "github.com/gorilla/websocket" 15 | ) 16 | 17 | func init() { 18 | log = &logger.EmptyLogger{} 19 | } 20 | 21 | const ( 22 | ocppV16 = "ocpp1.6" 23 | ocppV201 = "ocpp2.0.1" 24 | 25 | // Time allowed to wait until corresponding ocpp call result received 26 | ocppWait = 20 * time.Second 27 | 28 | // Time allowed to write a message to the peer. 29 | writeWait = 10 * time.Second 30 | 31 | // Time allowed to read the next pong message from the peer. 32 | pingWait = 30 * time.Second 33 | 34 | // Time allowed to read the next pong message from the peer. 35 | pongWait = 30 * time.Second 36 | 37 | // Send pings to peer with this period. Must be less than pongWait. 38 | pingPeriod = (pongWait * 9) / 10 39 | ) 40 | 41 | // TODO: refactor or wrap with function 42 | var validateV16 = v16.Validate 43 | var validateV201 = v201.Validate 44 | 45 | var log logger.Logger 46 | 47 | func SetLogger(logger logger.Logger) { 48 | if logger == nil { 49 | panic("logger cannot be nil") 50 | } 51 | log = logger 52 | } 53 | 54 | var ErrChargePointNotConnected = errors.New("charge point not connected") 55 | var ErrCallQuequeFull = errors.New("call queque full") 56 | var ErrChargePointDisconnected = errors.New("charge point disconnected unexpectedly") 57 | 58 | // ChargePoint Represents a connected ChargePoint (also known as a Charging Station) 59 | type ChargePoint struct { 60 | // OCPP protocol version 61 | proto string 62 | 63 | // the websocket connection 64 | conn *websocket.Conn 65 | 66 | // chargePointId 67 | Id string 68 | 69 | // outgoing message channel 70 | out chan []byte 71 | 72 | // incoming message channel 73 | in chan []byte 74 | 75 | // mutex ensures that only one message is sent at a time 76 | mu sync.Mutex 77 | // crOrce carries CallResult or CallError 78 | ocppRespCh chan OcppMessage 79 | // Extras is for future use to carry data between different actions 80 | Extras map[string]interface{} 81 | 82 | // tc timeout config ensures a ChargePoint has its unique timeout configuration 83 | tc TimeoutConfig 84 | 85 | // isServer defines if a ChargePoint at server or client side 86 | isServer bool 87 | 88 | // TODO: 89 | validatePayloadFunc func(s interface{}) error 90 | unmarshalResponseFunc func(a string, r json.RawMessage) (Payload, error) 91 | 92 | // ping in channel 93 | pingIn chan []byte 94 | 95 | // closeC used to close the websocket connection by user 96 | closeC chan websocket.CloseError 97 | // TODO 98 | forceWClose chan error 99 | connected bool 100 | // Followings used for sending ping messages 101 | ticker *time.Ticker 102 | tickerC <-chan time.Time 103 | 104 | // serverPing defines if ChargePoint is in server initiated ping mode 105 | serverPing bool 106 | 107 | stopC chan struct{} 108 | dispatcherIn chan *callReq 109 | } 110 | 111 | // TimeoutConfig is for setting timeout configs at ChargePoint level 112 | type TimeoutConfig struct { 113 | 114 | // ocpp response timeout in seconds 115 | ocppWait time.Duration 116 | 117 | // time allowed to write a message to the peer 118 | writeWait time.Duration 119 | 120 | // time allowed to read the next pong message from the peer 121 | pingWait time.Duration 122 | 123 | // pong wait in seconds 124 | pongWait time.Duration 125 | 126 | // ping period in seconds 127 | pingPeriod time.Duration 128 | } 129 | 130 | type TimeoutError struct { 131 | Message string 132 | } 133 | 134 | func (e *TimeoutError) Error() string { 135 | return fmt.Sprintf("3: %s", e.Message) 136 | } 137 | 138 | // callReq is a container for calls 139 | type callReq struct { 140 | id string 141 | data []byte 142 | recvChan chan interface{} 143 | } 144 | 145 | // Payload used as a container is for both Call and CallResult' Payload 146 | type Payload interface{} 147 | 148 | type Peer interface { 149 | getHandler(string) func(*ChargePoint, Payload) Payload 150 | getAfterHandler(string) func(*ChargePoint, Payload) 151 | } 152 | 153 | func (cp *ChargePoint) unmarshalResponse(a string, r json.RawMessage) (Payload, error) { 154 | return cp.unmarshalResponseFunc(a, r) 155 | } 156 | 157 | func (cp *ChargePoint) validatePayload(v interface{}) error { 158 | return cp.validatePayloadFunc(v) 159 | } 160 | 161 | func (cp *ChargePoint) SetTimeoutConfig(config TimeoutConfig) { 162 | cp.mu.Lock() 163 | defer cp.mu.Unlock() 164 | cp.tc = config 165 | } 166 | 167 | func (cp *ChargePoint) IsConnected() bool { 168 | cp.mu.Lock() 169 | defer cp.mu.Unlock() 170 | return cp.connected 171 | } 172 | 173 | func (cp *ChargePoint) Shutdown() { 174 | cp.mu.Lock() 175 | defer cp.mu.Unlock() 176 | cp.closeC <- websocket.CloseError{Code: websocket.CloseNormalClosure, Text: ""} 177 | } 178 | 179 | // ResetPingPong resets ping/pong configuration upon WebSocketPingInterval 180 | func (cp *ChargePoint) ResetPingPong(t int) (err error) { 181 | if t < 0 { 182 | err = errors.New("interval cannot be less than 0") 183 | return 184 | } 185 | cp.mu.Lock() 186 | defer cp.mu.Unlock() 187 | if cp.isServer { 188 | log.Debug("ping/pong reconfigured") 189 | cp.tc.pingWait = time.Duration(t) * time.Second 190 | cp.conn.SetPingHandler(func(appData string) error { 191 | cp.pingIn <- []byte(appData) 192 | log.Debug("<- ping") 193 | return cp.conn.SetReadDeadline(cp.getReadTimeout()) 194 | }) 195 | return 196 | } 197 | log.Debug("ping/pong reconfigured") 198 | cp.tc.pongWait = time.Duration(t) * time.Second 199 | cp.tc.pingPeriod = (cp.tc.pongWait * 9) / 10 200 | cp.conn.SetPongHandler(func(appData string) error { 201 | log.Debug("<- pong") 202 | return cp.conn.SetReadDeadline(cp.getReadTimeout()) 203 | }) 204 | if t == 0 { 205 | cp.ticker.Stop() 206 | } else { 207 | cp.ticker.Reset(cp.tc.pingPeriod) 208 | } 209 | return 210 | } 211 | 212 | // EnableServerPing enables server initiated pings 213 | func (cp *ChargePoint) EnableServerPing(t int) (err error) { 214 | if t <= 0 { 215 | err = errors.New("interval must be greater than 0") 216 | return 217 | } 218 | cp.mu.Lock() 219 | defer cp.mu.Unlock() 220 | cp.serverPing = true 221 | if cp.isServer { 222 | log.Debug("server ping enabled") 223 | cp.tc.pongWait = time.Duration(t) * time.Second 224 | cp.tc.pingPeriod = (cp.tc.pongWait * 9) / 10 225 | cp.conn.SetPingHandler(nil) 226 | cp.ticker = time.NewTicker(cp.tc.pingPeriod) 227 | cp.tickerC = cp.ticker.C 228 | cp.conn.SetPongHandler(func(appData string) error { 229 | log.Debug("<- pong") 230 | return cp.conn.SetReadDeadline(cp.getReadTimeout()) 231 | }) 232 | return 233 | } 234 | log.Debug("server ping enabled") 235 | cp.ticker.Stop() 236 | cp.tickerC = nil 237 | cp.conn.SetPongHandler(nil) 238 | cp.tc.pingWait = time.Duration(t) * time.Second 239 | cp.pingIn = make(chan []byte) 240 | cp.conn.SetPingHandler(func(appData string) error { 241 | cp.pingIn <- []byte(appData) 242 | log.Debug("<- ping") 243 | return cp.conn.SetReadDeadline(cp.getReadTimeout()) 244 | }) 245 | return 246 | } 247 | 248 | // clientReader reads incoming websocket messages 249 | // and it runs as a goroutine on client-side charge point (physical device) 250 | func (cp *ChargePoint) clientReader() { 251 | defer func() { 252 | cp.connected = false 253 | }() 254 | cp.conn.SetPongHandler(func(appData string) error { 255 | log.Debug("<- pong") 256 | return cp.conn.SetReadDeadline(cp.getReadTimeout()) 257 | }) 258 | for { 259 | if cp.processIncoming(client) { 260 | break 261 | } 262 | } 263 | } 264 | 265 | // clientWriter writes websocket messages 266 | // and it runs as a goroutine on client-side charge point (physical device) 267 | func (cp *ChargePoint) clientWriter() { 268 | defer func() { 269 | _ = cp.conn.Close() 270 | }() 271 | if cp.tc.pingPeriod != 0 { 272 | cp.ticker = time.NewTicker(cp.tc.pingPeriod) 273 | cp.tickerC = cp.ticker.C 274 | defer cp.ticker.Stop() 275 | } 276 | for { 277 | if !cp.processOutgoing() { 278 | break 279 | } 280 | } 281 | } 282 | 283 | // serverReader reads incoming websocket messages 284 | // and it runs as a goroutine on server-side charge point (virtual device) 285 | func (cp *ChargePoint) serverReader() { 286 | cp.conn.SetPingHandler(func(appData string) error { 287 | cp.pingIn <- []byte(appData) 288 | log.Debug("<- ping") 289 | i := cp.getReadTimeout() 290 | return cp.conn.SetReadDeadline(i) 291 | }) 292 | defer func() { 293 | _ = cp.conn.Close() 294 | server.Delete(cp.Id) 295 | }() 296 | for { 297 | if cp.processIncoming(server) { 298 | break 299 | } 300 | } 301 | } 302 | 303 | // serverWriter writes websocket messages 304 | // and it runs as a goroutine on server-side charge point (virtual device) 305 | func (cp *ChargePoint) serverWriter() { 306 | defer server.Delete(cp.Id) 307 | for { 308 | if !cp.processOutgoing() { 309 | break 310 | } 311 | } 312 | } 313 | 314 | // processIncoming processes incoming websocket messages 315 | // and is used for both types of charge points (client and server side) 316 | // 317 | // incoming messages normally can be of four kind from application perspective: 318 | // - one of websocket close errors, 319 | // - ocpp Call 320 | // - ocpp CallResult 321 | // - ocpp CallError 322 | func (cp *ChargePoint) processIncoming(peer Peer) (br bool) { 323 | messageType, msg, err := cp.conn.ReadMessage() 324 | log.Debugf("messageType: %d", messageType) 325 | if err != nil { 326 | log.Debug(err) 327 | if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure, websocket.CloseNormalClosure) { 328 | // TODO: handle specific logs 329 | log.Debug(err) 330 | } 331 | // stop websocket writer goroutine 332 | cp.forceWClose <- err 333 | // stop ocpp call requests waiting in dispatcherIn channel 334 | cp.stopC <- struct{}{} 335 | return true 336 | } 337 | ocppMsg, err := unpack(msg, cp.proto) 338 | 339 | // TODO: handle this situation carefully 340 | // at this level, it is unknown if err.(*ocppError) is caused from a corrupt Call 341 | // this is very complicated case, because it could be any of corrupted Call, CallResult or CallError 342 | // possible solution: 343 | // 344 | // - if err.id is known to application (for example, id of waiting Call request in queque) 345 | // this means msg is a corrupted CallResult or CallError 346 | // and then the err can be dropped and pushed to logger 347 | // - if err.id is "-1", there is a chance it could be a corrupted Call if call request queque is empty 348 | // only in this case, CallError can be constructed and send to the peer 349 | if ocppMsg == nil && err != nil { 350 | log.Error(err) 351 | return 352 | } 353 | if call, ok := ocppMsg.(*Call); ok { 354 | if err != nil { 355 | cp.out <- call.createCallError(err) 356 | return 357 | } 358 | handler := peer.getHandler(call.Action) 359 | if handler != nil { 360 | // TODO: possible feature additions 361 | // - pushing an incoming Call into a queque 362 | // - pass Context with timeout down to handler 363 | // - or recover from panic and print error logs 364 | responsePayload := handler(cp, call.Payload) 365 | err = cp.validatePayload(responsePayload) 366 | if err != nil { 367 | log.Error(err) 368 | } else { 369 | cp.out <- call.createCallResult(responsePayload) 370 | if afterHandler := peer.getAfterHandler(call.Action); afterHandler != nil { 371 | // hadcoded delay between a Call and after Call handler 372 | time.Sleep(time.Second) 373 | go afterHandler(cp, call.Payload) 374 | } 375 | } 376 | } else { 377 | var err error = &ocppError{ 378 | id: call.UniqueId, 379 | code: "NotSupported", 380 | cause: fmt.Sprintf("Action %s is not supported", call.Action), 381 | } 382 | cp.out <- call.createCallError(err) 383 | log.Errorf("No handler for action %s", call.Action) 384 | } 385 | } else { 386 | select { 387 | case cp.ocppRespCh <- ocppMsg: 388 | default: 389 | } 390 | } 391 | return false 392 | } 393 | 394 | // process outOutoing writes both ping/pong messages and ocpp messages 395 | // to websocket connection. 396 | // also listens on extra two channels: 397 | // - forceWClose listens for signals upon websocket close erros on reader goroutine, 398 | // - closeC is used for graceful shutdown 399 | // 400 | // TODO: remove redundant err checking 401 | func (cp *ChargePoint) processOutgoing() (br bool) { 402 | select { 403 | case message, ok := <-cp.out: 404 | err := cp.conn.SetWriteDeadline(time.Now().Add(cp.tc.writeWait)) 405 | if err != nil { 406 | log.Error(err) 407 | return 408 | } 409 | if !ok { 410 | err := cp.conn.WriteMessage(websocket.CloseMessage, []byte{}) 411 | if err != nil { 412 | log.Error(err) 413 | } 414 | log.Debug("close msg ->") 415 | return 416 | } 417 | w, err := cp.conn.NextWriter(websocket.TextMessage) 418 | if err != nil { 419 | log.Debug(err) 420 | return 421 | } 422 | n, err := w.Write(message) 423 | if err != nil { 424 | log.Error(err) 425 | return 426 | } 427 | if err := w.Close(); err != nil { 428 | log.Error(err) 429 | return 430 | } 431 | log.Debugf("text msg -> %d", n) 432 | return true 433 | case <-cp.pingIn: 434 | err := cp.conn.SetWriteDeadline(time.Now().Add(cp.tc.writeWait)) 435 | if err != nil { 436 | log.Error(err) 437 | } 438 | err = cp.conn.WriteMessage(websocket.PongMessage, []byte{}) 439 | if err != nil { 440 | log.Error(err) 441 | return 442 | } 443 | log.Debug("pong ->") 444 | return true 445 | case <-cp.tickerC: 446 | _ = cp.conn.SetWriteDeadline(time.Now().Add(cp.tc.writeWait)) 447 | if err := cp.conn.WriteMessage(websocket.PingMessage, []byte{}); err != nil { 448 | log.Error(err) 449 | return 450 | } 451 | log.Debug("ping ->") 452 | return true 453 | case <-cp.forceWClose: 454 | return 455 | case closeErr := <-cp.closeC: 456 | b := websocket.FormatCloseMessage(closeErr.Code, closeErr.Text) 457 | err := cp.conn.WriteControl(websocket.CloseMessage, b, time.Now().Add(time.Second)) 458 | if err != nil && err != websocket.ErrCloseSent { 459 | log.Error(err) 460 | } 461 | return 462 | } 463 | } 464 | 465 | // getReadTimeout is used to tweak websocket ping/pong functionality 466 | // and it is for both client-side and server-side connections 467 | func (cp *ChargePoint) getReadTimeout() time.Time { 468 | if cp.serverPing { 469 | if cp.isServer { 470 | if cp.tc.pongWait == 0 { 471 | return time.Time{} 472 | } 473 | return time.Now().Add(cp.tc.pongWait) 474 | } 475 | if cp.tc.pingWait == 0 { 476 | return time.Time{} 477 | } 478 | return time.Now().Add(cp.tc.pingWait) 479 | } 480 | if cp.isServer { 481 | if cp.tc.pingWait == 0 { 482 | return time.Time{} 483 | } 484 | return time.Now().Add(cp.tc.pingWait) 485 | } 486 | if cp.tc.pongWait == 0 { 487 | return time.Time{} 488 | } 489 | return time.Now().Add(cp.tc.pongWait) 490 | 491 | } 492 | 493 | // callDispatcher sends ocpp call requests 494 | func (cp *ChargePoint) callDispatcher() { 495 | cleanUp := make(chan struct{}, 1) 496 | for { 497 | select { 498 | case callReq := <-cp.dispatcherIn: 499 | log.Debug("dispatcher in") 500 | cp.out <- callReq.data 501 | deadline := time.Now().Add(cp.tc.ocppWait) 502 | in: 503 | for { 504 | select { 505 | case <-cleanUp: 506 | log.Debug("clean up") 507 | break in 508 | case <-cp.stopC: 509 | log.Debug("charge point is closed") 510 | for ch := range cp.dispatcherIn { 511 | log.Debug("cancel remaning call requests in queque") 512 | close(ch.recvChan) 513 | } 514 | break in 515 | case ocppResp := <-cp.ocppRespCh: 516 | if ocppResp.getID() == callReq.id { 517 | callReq.recvChan <- ocppResp 518 | break in 519 | } 520 | case <-time.After(time.Until(deadline)): 521 | log.Debug("ocpp timeout occured") 522 | callReq.recvChan <- &TimeoutError{ 523 | Message: fmt.Sprintf("timeout of %s sec for response to Call with id: %s passed", cp.tc.ocppWait, callReq.id), 524 | } 525 | break in 526 | } 527 | } 528 | log.Debug("broke from loop") 529 | case <-cp.stopC: 530 | log.Debug("charge point is closed") 531 | select { 532 | case cleanUp <- struct{}{}: 533 | default: 534 | } 535 | for ch := range cp.dispatcherIn { 536 | log.Debug("cancel remaning call requests in queque") 537 | close(ch.recvChan) 538 | } 539 | } 540 | } 541 | } 542 | 543 | // Call sends a message to peer 544 | func (cp *ChargePoint) Call(action string, p Payload) (Payload, error) { 545 | // check if charge point is connected 546 | if !cp.IsConnected() { 547 | return nil, ErrChargePointNotConnected 548 | } 549 | // add validator function 550 | err := cp.validatePayload(p) 551 | if err != nil { 552 | return nil, err 553 | } 554 | id := uuid.New().String() 555 | call := [4]interface{}{ 556 | 2, 557 | id, 558 | action, 559 | p, 560 | } 561 | raw, _ := json.Marshal(call) 562 | recvChan := make(chan interface{}, 1) 563 | cr := &callReq{ 564 | id: id, 565 | data: raw, 566 | recvChan: recvChan, 567 | } 568 | select { 569 | case cp.dispatcherIn <- cr: 570 | log.Debug("call request added to dispatcher") 571 | default: 572 | return nil, ErrCallQuequeFull 573 | } 574 | r, ok := <-recvChan 575 | if !ok { 576 | return nil, ErrChargePointDisconnected 577 | } 578 | if callResult, ok := r.(*CallResult); ok { 579 | resPayload, err := cp.unmarshalResponse(action, callResult.Payload) 580 | if err != nil { 581 | return nil, err 582 | } 583 | return resPayload, nil 584 | } 585 | if callError, ok := r.(*CallError); ok { 586 | return nil, callError 587 | } 588 | return nil, r.(*TimeoutError) 589 | } 590 | 591 | // NewChargepoint creates a new ChargePoint 592 | func NewChargePoint(conn *websocket.Conn, id, proto string, isServer bool) *ChargePoint { 593 | cp := &ChargePoint{ 594 | proto: proto, 595 | conn: conn, 596 | Id: id, 597 | out: make(chan []byte), 598 | in: make(chan []byte), 599 | ocppRespCh: make(chan OcppMessage), 600 | Extras: make(map[string]interface{}), 601 | closeC: make(chan websocket.CloseError, 1), 602 | forceWClose: make(chan error, 1), 603 | stopC: make(chan struct{}), 604 | connected: true, 605 | } 606 | if isServer { 607 | cp.dispatcherIn = make(chan *callReq, server.getCallQueueSize()) 608 | cp.pingIn = make(chan []byte) 609 | cp.isServer = true 610 | cp.tickerC = nil 611 | cp.inheritServerTimeoutConfig() 612 | go cp.serverReader() 613 | go cp.serverWriter() 614 | } else { 615 | cp.dispatcherIn = make(chan *callReq, client.callQuequeSize) 616 | cp.inheritClientTimeoutConfig() 617 | go cp.clientReader() 618 | go cp.clientWriter() 619 | } 620 | go cp.callDispatcher() 621 | cp.setResponseUnmarshaller() 622 | cp.setPayloadValidator() 623 | 624 | return cp 625 | } 626 | 627 | func (cp *ChargePoint) setResponseUnmarshaller() { 628 | switch cp.proto { 629 | case ocppV16: 630 | cp.unmarshalResponseFunc = unmarshalResponsePv16 631 | case ocppV201: 632 | cp.unmarshalResponseFunc = unmarshalResponsePv201 633 | } 634 | } 635 | 636 | func (cp *ChargePoint) setPayloadValidator() { 637 | switch cp.proto { 638 | case ocppV16: 639 | cp.validatePayloadFunc = validateV16.Struct 640 | case ocppV201: 641 | cp.validatePayloadFunc = validateV201.Struct 642 | } 643 | } 644 | 645 | func (cp *ChargePoint) inheritServerTimeoutConfig() { 646 | cp.tc.ocppWait = server.ocppWait 647 | cp.tc.writeWait = server.writeWait 648 | cp.tc.pingWait = server.pingWait 649 | } 650 | 651 | func (cp *ChargePoint) inheritClientTimeoutConfig() { 652 | cp.tc.ocppWait = client.ocppWait 653 | cp.tc.writeWait = client.writeWait 654 | cp.tc.pongWait = client.pongWait 655 | cp.tc.pingPeriod = client.pingPeriod 656 | } 657 | -------------------------------------------------------------------------------- /v201/datatypes.go: -------------------------------------------------------------------------------- 1 | package v201 2 | 3 | type ACChargingParametersType struct { 4 | EnergyAmount int `json:"energyAmount" validate:"required"` 5 | EvMinCurrent int `json:"evMinCurrent" validate:"required"` 6 | EvMaxCurrent int `json:"evMaxCurrent" validate:"required"` 7 | EvMaxVoltage int `json:"evMaxVoltage" validate:"required"` 8 | } 9 | 10 | type AdditionalInfoType struct { 11 | AdditionalToken string `json:"additionalToken" validate:"required,max=36"` 12 | Type string `json:"type" validate:"required,max=50"` 13 | } 14 | 15 | type APNType struct { 16 | Apn string `json:"apn" validate:"required,max=100"` 17 | ApnUserName string `json:"apnUserName,omitempty" validate:"omitempty,max=20"` 18 | ApnPassword string `json:"apnPassword,omitempty" validate:"omitempty,max=20"` 19 | SimPin int `json:"simPin,omitempty"` 20 | PreferredNetwork string `json:"preferredNetwork,omitempty" validate:"omitempty,max=6"` 21 | UseOnlyPreferredNetwork bool `json:"useOnlyPreferredNetwork,omitempty"` 22 | ApnAuthentication string `json:"apnAuthentication" validate:"required,APNAuthenticationEnumType"` 23 | } 24 | 25 | type AuthorizarionData struct { 26 | IdTokenInfo IdTokenInfoType `json:"idTokenInfo,omitempty"` 27 | IdToken IdTokenType `json:"idToken" validate:"required"` 28 | } 29 | 30 | type CertificateHashDataChainType struct { 31 | CertificateType string `json:"certificateType" validate:"required,GetCertificateIdUseEnumType"` 32 | CertificateHashData CertificateHashDataType `json:"certificateHashData" validate:"required"` 33 | ChildCertificateHashData []CertificateHashDataType `json:"childCertificateHashData,omitempty" validate:"omitempty,max=4,dive,required"` 34 | } 35 | 36 | type CertificateHashDataType struct { 37 | HashAlgorithm string `json:"hashAlgorithm" validate:"required,HashAlgorithmEnumType"` // todo: validation register required 38 | IssuerNameHash string `json:"issuerNameHash" validate:"required,max=128"` 39 | IssuerKeyHash string `json:"issuerKeyHash" validate:"required,max=128"` 40 | SerialNumber string `json:"serialNumber" validate:"required,max=40"` 41 | } 42 | 43 | type ChargingLimitType struct { 44 | ChargingLimitSource string `json:"chargingLimitSource" validate:"required,ChargingLimitSourceEnumType"` 45 | IsGridCritical bool `json:"isGridCritical,omitempty"` 46 | } 47 | 48 | type ChargingNeedsType struct { 49 | RequestedEnergyTransfer string `json:"requestedEnergyTransfer" validate:"required,RequestedEnergyTransferEnumType"` 50 | DepartureTime string `json:"departureTime" validate:"omitempty,ISO8601date"` 51 | ACChargingParameters ACChargingParametersType `json:"acChargingParameters,omitempty"` 52 | DcChargingParameters DCChargingParametersType `json:"dcChargingParameters,omitempty"` 53 | } 54 | 55 | type ChargingProfileCriterionType struct { 56 | ChargingProfilePurpose string `json:"chargingProfilePurpose,omitempty" validate:"omitempty,ChargingProfilePurposeEnumType"` 57 | StackLevel *int `json:"stackLevel,omitempty"` 58 | ChargingProfileId []int `json:"chargingProfileId,omitempty" validate:"omitempty,dive,required"` 59 | ChargingLimitSource []string `json:"chargingLimitSource,omitempty" validate:"omitempty,max=4,dive,required,ChargingLimitSourceEnumType"` 60 | } 61 | 62 | type ChargingProfileType struct { 63 | Id int `json:"id" validate:"required"` 64 | StackLevel *int `json:"stackLevel" validate:"required"` 65 | ChargingProfilePurpose string `json:"chargingProfilePurpose" validate:"required,ChargingProfilePurposeEnumType"` 66 | ChargingProfileKind string `json:"chargingProfileKind" validate:"required,ChargingProfileKindEnumType"` 67 | RecurrencyKind string `json:"recurrencyKind,omitempty" validate:"omitempty,RecurrencyKindEnumType"` 68 | ValidFrom string `json:"validFrom,omitempty" validate:"omitempty,ISO8601date"` 69 | ValidTo string `json:"validTo,omitempty" validate:"omitempty,ISO8601date"` 70 | TransactionId string `json:"transactionId,omitempty" validate:"omitempty,max=36"` 71 | ChargingSchedule ChargingScheduleType `json:"chargingSchedule" validate:"required,min=1,max=3,dive,required"` 72 | } 73 | 74 | type ChargingSchedulePeriodType struct { 75 | StartPeriod *int `json:"startPeriod" validate:"required"` 76 | Limit float32 `json:"limit" validate:"required"` 77 | NumberPhases int `json:"numberPhases,omitempty" validate:"omitempty,gte=1,lte=3"` 78 | PhaseToUse int `json:"phaseToUse,omitempty" validate:"omitempty,gte=1,lte=3"` 79 | } 80 | 81 | type ChargingScheduleType struct { 82 | Id int `json:"id" validate:"required"` 83 | StartSchedule string `json:"startSchedule,omitempty" validate:"omitempty,ISO8601date"` 84 | Duration int `json:"duration,omitempty"` 85 | ChargingRateUnit string `json:"chargingRateUnit" validate:"required,ChargingRateUnitEnumType"` 86 | MinChargingRate float32 `json:"minChargingRate,omitempty"` 87 | ChargingSchedulePeriod []ChargingSchedulePeriodType `json:"chargingSchedulePeriod" validate:"required,min=1,max=1024,dive,required"` 88 | SalesTariff SalesTariffType `json:"salesTariff,omitempty"` 89 | } 90 | 91 | type ChargingStationType struct { 92 | SerialNumber string `json:"serialNumber,omitempty" validate:"omitempy,max=25"` 93 | Model string `json:"model" validate:"required,max=20"` 94 | VendorName string `json:"vendorName" validate:"required,max=50"` 95 | FirmwareVersion string `json:"firmwareVersion,omitempty" validate:"omitempty,max=50"` 96 | Modem ModemType `json:"modem,omitempty"` 97 | } 98 | 99 | type ClearChargingProfileType struct { 100 | EvseId *int `json:"evseId,omitempty" validate:"omitempty,gte=0"` 101 | ChargingProfilePurpose string `json:"chargingProfilePurpose,omitempty" validate:"omitempty,ChargingProfilePurposeEnumType"` // todo: validation register required 102 | StackLevel *int `json:"stackLevel,omitempty" validate:"omitempty,gte=0"` 103 | } 104 | 105 | type ClearMonitoringResultType struct { 106 | Status string `json:"status" validate:"required,ClearMonitoringStatusEnumType"` // todo: validation register required 107 | Id *int `json:"id" validate:"required"` 108 | StatusInfo StatusInfoType `json:"statusInfo,omitempty" ` 109 | } 110 | 111 | type ComponentType struct { 112 | Name string `json:"name" validate:"required,max=50"` 113 | Instance string `json:"instance,omitempty" validate:"omitempty,max=50"` 114 | Evse EVSEType `json:"evse,omitempty"` 115 | } 116 | 117 | type ComponentVariableType struct { 118 | Component ComponentType `json:"component" validate:"required"` 119 | Variable VariableType `json:"variable,omitempty"` 120 | } 121 | 122 | type CompositeScheduleType struct { 123 | EvseId *int `json:"evseId" validate:"required,gte=0"` 124 | Duration int `json:"duration" validate:"required"` 125 | ScheduleStart string `json:"scheduleStart" validate:"required,ISO8601date"` 126 | ChargingRateUnit string `json:"chargingRateUnit" validate:"required,ChargingRateUnitEnumType"` 127 | ChargingSchedulePeriod []ChargingSchedulePeriodType `json:"chargingSchedulePeriod" validate:"required,min=1,dive,required"` 128 | } 129 | 130 | type ConsumptionCostType struct { 131 | StartValue float32 `json:"startValue" validate:"required"` 132 | Cost CostType `json:"cost" validate:"required,min=1,max=3,dive,required"` 133 | } 134 | 135 | type CostType struct { 136 | CostKind string `json:"costKind" validate:"required,CostKindEnumType"` 137 | Amount int `json:"amount" validate:"required"` 138 | AmountMultiplier *int `json:"amountMultiplier,omitempty" validate:"omitempty,gte=-3,lte=3"` 139 | } 140 | 141 | type DCChargingParametersType struct { 142 | EvMaxCurrent int `json:"evMaxCurrent" validate:"required"` 143 | EvMaxVoltage int `json:"evMaxVoltage" validate:"required"` 144 | EnergyAmount int `json:"energyAmount,omitempty"` 145 | EvMaxPower int `json:"evMaxPower,omitempty"` 146 | StateOfCharge *int `json:"stateOfCharge,omitempty" validate:"omitempty,gte=0,lte=100"` 147 | EvEnergyCapacity int `json:"evEnergyCapacity,omitempty"` 148 | FullSoC *int `json:"fullSoC,omitempty" validate:"omitempty,gte=0,lte=100"` 149 | BulkSoC *int `json:"bulkSoC,omitempty" validate:"omitempty,gte=0,lte=100"` 150 | } 151 | 152 | type EventDataType struct { 153 | EventId int `json:"eventId" validate:"required"` 154 | Timestamp string `json:"timestamp" validate:"required,ISO8601date"` 155 | Trigger string `json:"trigger" validate:"required,TriggerEnumType"` 156 | Cause int `json:"cause,omitempty"` 157 | ActualValue string `json:"actualValue" validate:"required,max=2500"` 158 | TechCode string `json:"techCode,omitempty" validate:"omitempty,max=50"` 159 | TechInfo string `json:"techInfo,omitempty" validate:"omitempty,max=500"` 160 | Cleared bool `json:"cleared,omitempty"` 161 | TransactionId string `json:"transactionId,omitempty" validate:"omitempty,max=36"` 162 | VariableMonitoringId int `json:"variableMonitoringId,omitempty"` 163 | EventNotificationType string `json:"eventNotificationType" validate:"required,EventNotificationTypeEnumType"` 164 | Component ComponentType `json:"component" validate:"required"` 165 | Variable VariableType `json:"variable" validate:"required"` 166 | } 167 | 168 | type EVSEType struct { 169 | Id int `json:"id" validate:"required,gt=0"` 170 | ConnectorId *int `json:"connectorId,omitempty" validate:"omitempty,gte=0"` 171 | } 172 | 173 | type FirmwareType struct { 174 | Location string `json:"location" validate:"required,max=512"` 175 | RetrieveDateTime string `json:"retrieveDateTime" validate:"required,ISO8601date"` 176 | InstallDateTime string `json:"installDateTime,omitempty" validate:"omitempty,ISO8601date"` 177 | SigningCertificate string `json:"signingCertificate,omitempty" validate:"omitempty,max=5500"` 178 | Signature string `json:"signature,omitempty" validate:"omitempty,max=800"` 179 | } 180 | 181 | type GetVariableDataType struct { 182 | AttributeType string `json:"attributeType,omitempty" validate:"omitempty,AttributeTypeEnumType"` 183 | Component ComponentType `json:"component" validate:"required"` 184 | Variable VariableType `json:"variable" validate:"required"` 185 | } 186 | 187 | type GetVariableResultType struct { 188 | AttributeStatus string `json:"attributeStatus" validate:"required,GetVariableStatusEnumType"` 189 | AttributeType string `json:"attributeType,omitempty" validate:"omitempty,AttributeTypeEnumType"` 190 | AttributeValue string `json:"attributeValue,omitempty" validate:"omitempty,max=2500"` 191 | Component ComponentType `json:"component" validate:"required"` 192 | Variable VariableType `json:"variable" validate:"required"` 193 | AttributeStatusInfo StatusInfoType `json:"attributeStatusInfo,omitempty"` 194 | } 195 | 196 | type IdTokenInfoType struct { 197 | Status string `json:"status" validate:"required,AuthorizationStatusEnumType"` 198 | CacheExpiryDateTime string `json:"cacheExpiryDateTime,omitempty" validate:"omitempty,ISO8601date"` 199 | ChangePriority *int `json:"changePriority,omitempty" validate:"omitempty,gte=-9,lte=9"` 200 | Language1 string `json:"language1,omitempty" validate:"omitempty,max=8"` 201 | EvseId []int `json:"evseId,omitempty" validate:"omitempty,dive,required"` 202 | Language2 string `json:"language2,omitempty" validate:"omitempty,max=8"` 203 | GroupIdToken IdTokenType `json:"groupIdToken,omitempty"` 204 | PersonalMessage MessageContentType `json:"personalMessage,omitempty"` 205 | } 206 | 207 | type IdTokenType struct { 208 | IdToken string `json:"idToken" validate:"required,max=36"` 209 | Type string `json:"type" validate:"required,IdTokenEnumType"` // todo: validation register required 210 | AdditionalInfo []AdditionalInfoType `json:"additionalInfo" validate:"omitempty,dive,required"` 211 | } 212 | 213 | type LogParametersType struct { 214 | RemoteLocation string `json:"remoteLocation" validate:"required,max=512"` 215 | OldestTimestamp string `json:"oldestTimestamp,omitempty" validate:"omitempty,ISO8601date"` 216 | LatestTimeStamp string `json:"latestTimeStamp,omitempty" validate:"omitempty,ISO8601date"` 217 | } 218 | 219 | type MessageContentType struct { 220 | Format string `json:"format" validate:"required,MessageFormatEnumType"` 221 | Language string `json:"language,omitempty" validate:"omitempty,max=8"` 222 | Content string `json:"content" validate:"required,max=512"` 223 | } 224 | 225 | type MessageInfoType struct { 226 | Id int `json:"id" validate:"required"` 227 | Priority string `json:"priority" validate:"required,MessagePriorityEnumType"` 228 | State string `json:"state,omitempty" validate:"omitempty,MessageStateEnumType"` 229 | StartDateTime string `json:"startDateTime,omitempty" validate:"omitempty,ISO8601date"` 230 | EndDateTime string `json:"endDateTime,omitempty" validate:"omitempty,ISO8601date"` 231 | TransactionId string `json:"transactionId,omitempty" validate:"omitempty,max=36"` 232 | Message MessageContentType `json:"message" validate:"required"` 233 | Display ComponentType `json:"display,omitempty"` 234 | } 235 | 236 | type MeterValueType struct { 237 | Timestamp string `json:"timestamp" validate:"required,ISO8601date"` 238 | SampledValue []SampledValueType `json:"sampledValue" validate:"required,dive,required"` 239 | } 240 | 241 | type ModemType struct { 242 | Iccid string `json:"iccid,omitempty" validate:"omitempty,max=20"` 243 | Imsi string `json:"imsi,omitempty" validate:"omitempty,max=20"` 244 | } 245 | 246 | type MonitoringDataType struct { 247 | Component ComponentType `json:"component" validate:"required"` 248 | Variable VariableType `json:"variable" validate:"required"` 249 | VariableMonitoring []VariableMonitoringType `json:"variableMonitoring" validate:"required,dive,required"` 250 | } 251 | 252 | type NetworkConnectionProfileType struct { 253 | OcppVersion string `json:"ocppVersion" validate:"required,OCPPVersionEnumType"` 254 | OcppTransport string `json:"ocppTransport" validate:"required,OCPPTransportEnumType"` 255 | OcppCsmsUrl string `json:"ocppCsmsUrl" validate:"required,max=512"` 256 | MessageTimeout int `json:"messageTimeout" validate:"required"` 257 | SecurityProfile int `json:"securityProfile" validate:"required"` 258 | OcppInterface string `json:"ocppInterface" validate:"required,OCPPInterfaceEnumType"` 259 | Vpn VPNType `json:"vpn,omitempty"` 260 | Apn APNType `json:"apn,omitempty"` 261 | } 262 | 263 | type OCSPRequestDataType struct { 264 | HashAlgorithm string `json:"hashAlgorithm" validate:"required,HashAlgorithmEnumType"` // todo: validation register required 265 | IssuerNameHash string `json:"issuerNameHash" validate:"required,max=128"` 266 | IssuerKeyHash string `json:"issuerKeyHash" validate:"required,max=128"` 267 | SerialNumber string `json:"serialNumber" validate:"required,max=40"` 268 | ResponderUrl string `json:"responderUrl" validate:"required,max=512"` 269 | } 270 | 271 | type RelativeTimeIntervalType struct { 272 | Start int `json:"start" validate:"required"` 273 | End int `json:"end,omitempty"` 274 | } 275 | 276 | type ReportDataType struct { 277 | Component ComponentType `json:"component" validate:"required"` 278 | Variable VariableType `json:"variable" validate:"required"` 279 | VariableAttribute []VariableAttributeType `json:"variableAttribute" validate:"required,min=1,max=4,dive,required"` 280 | VariableCharacteristics []VariableCharacteristicsType `json:"variableCharacteristics,omitempty"` 281 | } 282 | 283 | type SalesTariffEntryType struct { 284 | EPriceLevel int `json:"ePriceLevel,omitempty"` 285 | RelativeTimeInterval RelativeTimeIntervalType `json:"relativeTimeInterval" validate:"required"` 286 | ConsumptionCost ConsumptionCostType `json:"consumptionCost,omitempty" validate:"omitempty,max=3,dive,required"` 287 | } 288 | 289 | type SalesTariffType struct { 290 | Id int `json:"id" validate:"required"` 291 | SalesTariffDescription string `json:"salesTariffDescription,omitempty" validate:"omitempty,max=512"` 292 | NumEPriceLevels int `json:"numEPriceLevels,omitempty"` 293 | SalesTariffEntry []SalesTariffEntryType `json:"salesTariffEntry" validate:"required,gte=1,lte=1024,dive,required"` 294 | } 295 | 296 | type SampledValueType struct { 297 | Value float32 `json:"value" validate:"required"` 298 | Context string `json:"context,omitempty" validate:"omitempty,ReadingContextEnumType"` 299 | Measurand string `json:"measurand,omitempty" validate:"omitempty,MeasurandEnumType"` 300 | Phase string `json:"phase,omitempty" validate:"omitempty,PhaseEnumType"` 301 | Location string `json:"location,omitempty" validate:"omitempty,LocationEnumType"` 302 | SignedMeterValue SignedMeterValueType `json:"signedMeterValue,omitempty"` 303 | UnitOfMeasure UnitOfMeasureType `json:"unitOfMeasure,omitempty"` 304 | } 305 | 306 | type SetMonitoringDataType struct { 307 | Id int `json:"id,omitempty"` 308 | Transaction bool `json:"transaction,omitempty"` 309 | Value float32 `json:"value" validate:"required"` 310 | Type string `json:"type" validate:"required,MonitoringEnumType"` 311 | Severity *int `json:"severity" validate:"required,gte=0,lte=9"` 312 | Component ComponentType `json:"component" validate:"required"` 313 | Variable VariableType `json:"variable" validate:"required"` 314 | } 315 | 316 | type SetMonitoringResultType struct { 317 | Id int `json:"id,omitempty"` 318 | Status string `json:"status" validate:"required,SetMonitoringStatusEnumType"` 319 | Type string `json:"type" validate:"required,MonitoringEnumType"` 320 | Severity *int `json:"severity" validate:"required,gte=0,lte=9"` 321 | Component ComponentType `json:"component" validate:"required"` 322 | Variable VariableType `json:"variable" validate:"required"` 323 | StatusInfo StatusInfoType `json:"statusInfo,omitempty"` 324 | } 325 | 326 | type SetVariableDataType struct { 327 | AttributeType string `json:"attributeType,omitempty" validate:"omitempty,AttributeTypeEnumType"` 328 | AttributeValue string `json:"attributeValue" validate:"required,max=10000"` 329 | Component ComponentType `json:"component" validate:"required"` 330 | Variable VariableType `json:"variable" validate:"required"` 331 | } 332 | 333 | type SetVariableResultType struct { 334 | AttributeType string `json:"attributeType,omitempty" validate:"omitempty,AttributeTypeEnumType"` 335 | AttributeStatus string `json:"attributeStatus" validate:"required,SetVariableStatusEnumType"` 336 | Component ComponentType `json:"component" validate:"required"` 337 | Variable VariableType `json:"variable" validate:"required"` 338 | AttributeStatusInfo StatusInfoType `json:"attributeStatusInfo,omitempty"` 339 | } 340 | 341 | type SignedMeterValueType struct { 342 | SignedMeterData string `json:"signedMeterData" validate:"required,max=2500"` 343 | SigningMethod string `json:"signingMethod" validate:"required,max=50"` 344 | EncodingMethod string `json:"encodingMethod" validate:"required,max=50"` 345 | PublicKey string `json:"publicKey" validate:"required,max=2500"` 346 | } 347 | 348 | type StatusInfoType struct { 349 | ReasonCode string `json:"reasonCode" validate:"required,max=20"` 350 | AdditionalInfo string `json:"additionalInfo" validate:"required,max=512"` 351 | } 352 | 353 | type TransactionType struct { 354 | TransactionId string `json:"transactionId" validate:"required,max=36"` 355 | ChargingState string `json:"chargingState,omitempty" validate:"omitempty,ChargingStateEnumType"` 356 | TimeSpentCharging int `json:"timeSpentCharging,omitempty"` 357 | StoppedReason string `json:"stoppedReason,omitempty" validate:"omitempty,ReasonEnumType"` 358 | RemoteStartId int `json:"remoteStartId,omitempty"` 359 | } 360 | 361 | type UnitOfMeasureType struct { 362 | Unit string `json:"unit,omitempty" validate:"omitempty,max=50"` 363 | Multiplier *int `json:"multiplier,omitempty"` 364 | } 365 | 366 | type VariableAttributeType struct { 367 | Type string `json:"type,omitempty" validate:"omitempty,AttributeTypeEnumType"` 368 | Value string `json:"value,omitempty" validate:"omitempty,max=2500"` 369 | Mutability string `json:"mutability,omitempty" validate:"omitempty,MutabilityEnumType"` 370 | Persistent bool `json:"persistent,omitempty"` 371 | Constant bool `json:"constant,omitempty"` 372 | } 373 | 374 | type VariableCharacteristicsType struct { 375 | Unit string `json:"unit,omitempty" validate:"omitempty,max=16"` 376 | DataType string `json:"dataType" validate:"required,DataEnumType"` 377 | MinLimit float32 `json:"minLimit,omitempty"` 378 | MaxLimit float32 `json:"maxLimit,omitempty"` 379 | ValuesList string `json:"valuesList,omitempty" validate:"omitempty,max=1000"` 380 | SupportsMonitoring bool `json:"supportsMonitoring" validate:"required"` 381 | } 382 | 383 | type VariableMonitoringType struct { 384 | Id int `json:"id" validate:"required"` 385 | Transaction bool `json:"transaction" validate:"required"` 386 | Value float32 `json:"value" validate:"required"` 387 | Type string `json:"type" validate:"required,MonitorEnumType"` 388 | Severity *int `json:"severity" validate:"required,gte=0,lte=9"` 389 | } 390 | 391 | type VariableType struct { 392 | Name string `json:"name" validate:"required,max=50"` 393 | Instance string `json:"instance,omitempty" validate:"omitempty,max=50"` 394 | } 395 | 396 | type VPNType struct { 397 | Server string `json:"server" validate:"required,max=512"` 398 | User string `json:"user" validate:"required,max=20"` 399 | Group string `json:"group,omitempty" validate:"omitempty,max=20"` 400 | Password string `json:"password" validate:"required,max=20"` 401 | Key string `json:"key" validate:"required,max=255"` 402 | Type string `json:"type" validate:"required,VPNTypeEnumType"` 403 | } 404 | -------------------------------------------------------------------------------- /messages.go: -------------------------------------------------------------------------------- 1 | package ocpp 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "fmt" 7 | 8 | "github.com/aliml92/ocpp/v16" 9 | "github.com/aliml92/ocpp/v201" 10 | ) 11 | 12 | const ( 13 | MessageTypeIdCall = 2 14 | MessageTypeIdCallResult = 3 15 | MessageTypeIdCallError = 4 16 | ) 17 | 18 | var errInvalidAction = errors.New("invalid action") 19 | 20 | var reqmapv16, resmapv16, reqmapv201, resmapv201 map[string]func(json.RawMessage) (Payload, error) 21 | 22 | type ocppError struct { 23 | id string 24 | code string 25 | cause string 26 | } 27 | 28 | func (e *ocppError) Error() string { 29 | return e.code + ": " + e.cause 30 | } 31 | 32 | // Call represents OCPP Call 33 | type Call struct { 34 | MessageTypeId uint8 35 | UniqueId string 36 | Action string 37 | Payload Payload 38 | } 39 | 40 | func (c *Call) getID() string { 41 | return c.UniqueId 42 | } 43 | 44 | // Create CallResult from a received Call 45 | func (call *Call) createCallResult(r Payload) []byte { 46 | out := [3]interface{}{ 47 | 3, 48 | call.UniqueId, 49 | r, 50 | } 51 | raw, _ := json.Marshal(out) 52 | return raw 53 | } 54 | 55 | // Creates a CallError from a received Call 56 | // TODO: organize error codes 57 | func (call *Call) createCallError(err error) []byte { 58 | var id, code, cause string 59 | var ocppErr *ocppError 60 | if errors.As(err, &ocppErr) { 61 | id = ocppErr.id 62 | code = ocppErr.code 63 | cause = ocppErr.cause 64 | } 65 | if id == "" { 66 | id = "-1" 67 | } 68 | callError := &CallError{ 69 | UniqueId: id, 70 | ErrorCode: code, 71 | ErrorDetails: cause, 72 | } 73 | switch code { 74 | case "ProtocolError": 75 | callError.ErrorDescription = "Payload for Action is incomplete" 76 | case "PropertyConstraintViolation": 77 | callError.ErrorDescription = "Payload is syntactically correct but at least one field contains an invalid value" 78 | case "NotImplemented": 79 | callError.ErrorDescription = "Requested Action is not known by receiver" 80 | case "TypeConstraintViolationError": 81 | callError.ErrorDescription = "Payload for Action is syntactically correct but at least one of the fields violates data type constraints (e.g. “somestring”: 12)" 82 | case "PropertyConstraintViolationError": 83 | callError.ErrorDescription = "Payload is syntactically correct but at least one field contains an invalid value" 84 | case "MessageTypeNotSupported": 85 | callError.ErrorDescription = "A message with an Message Type Number received that is not supported by this implementation" 86 | default: 87 | callError.ErrorDescription = "Unknown error" 88 | } 89 | return callError.marshal() 90 | } 91 | 92 | // CallResult represents OCPP CallResult 93 | type CallResult struct { 94 | MessageTypeId uint8 95 | UniqueId string 96 | Payload json.RawMessage 97 | } 98 | 99 | func (cr *CallResult) getID() string { 100 | return cr.UniqueId 101 | } 102 | 103 | // CallError represents OCPP CallError 104 | type CallError struct { 105 | MessageTypeId uint8 106 | UniqueId string 107 | ErrorCode string 108 | ErrorDescription string 109 | ErrorDetails interface{} 110 | } 111 | 112 | func (ce *CallError) marshal() []byte { 113 | ed := ce.ErrorDetails.(string) 114 | out := [5]interface{}{ 115 | 4, 116 | ce.UniqueId, 117 | ce.ErrorCode, 118 | ce.ErrorDescription, 119 | `{"cause":` + ed + `}`, 120 | } 121 | raw, _ := json.Marshal(out) 122 | return raw 123 | } 124 | 125 | func (ce *CallError) Error() string { 126 | return fmt.Sprintf("CallError: UniqueId=%s, ErrorCode=%s, ErrorDescription=%s, ErrorDetails=%s", 127 | ce.UniqueId, ce.ErrorCode, ce.ErrorDescription, ce.ErrorDetails) 128 | } 129 | 130 | func (ce *CallError) getID() string { 131 | return ce.UniqueId 132 | } 133 | 134 | type OcppMessage interface { 135 | getID() string 136 | } 137 | 138 | // umpack converts json byte to one of the ocpp messages, if not successful 139 | // returns an error 140 | // unpack expects ocpp messages in the below forms: 141 | // - [, "", "", {}] -> Call 142 | // - [, "", {}] -> CallResult 143 | // - [, "", "", "" , {}] -> CallError 144 | // 145 | // if json byte does not conform with these three formats, OcppError is created 146 | // and is returned as error 147 | // 148 | // TODO: improve error construction depending on protocol 149 | func unpack(b []byte, proto string) (OcppMessage, error) { 150 | var rm []json.RawMessage // raw message 151 | var mti uint8 // MessageTypeId 152 | var ui string // UniqueId 153 | var a string // Action 154 | var p Payload // Payload 155 | var ocppMsg OcppMessage // OcppMessage 156 | var e *ocppError 157 | 158 | // unmarshal []byte to []json.RawMessage 159 | err := json.Unmarshal(b, &rm) 160 | if err != nil { 161 | e = &ocppError{ 162 | id: "-1", 163 | code: "ProtocolError", 164 | cause: "Invalid JSON format", 165 | } 166 | return nil, e 167 | } 168 | 169 | // unmarshal [0]json.RawMessage to MessageTypeId 170 | err1 := json.Unmarshal(rm[0], &mti) 171 | // unmarshal [1]json.RawMessage to UniqueId 172 | err2 := json.Unmarshal(rm[1], &ui) 173 | if err1 != nil { 174 | if err2 != nil { 175 | e.id = "-1" 176 | e.cause = e.cause + "," + fmt.Sprintf("UniqueId: %v is not valid", rm[1]) 177 | } else { 178 | e.id = ui 179 | } 180 | return nil, e 181 | } 182 | if len(ui) > 36 { 183 | e = &ocppError{ 184 | code: "ProtocolError", 185 | cause: fmt.Sprintf("UniqueId: %v is too long", ui), 186 | } 187 | } 188 | switch mti { 189 | case MessageTypeIdCall: 190 | call := &Call{ 191 | MessageTypeId: mti, 192 | UniqueId: ui, 193 | } 194 | if e != nil { 195 | return call, e 196 | } 197 | err = json.Unmarshal(rm[2], &a) 198 | if err != nil { 199 | e := &ocppError{ 200 | id: ui, 201 | code: "ProtocolError", 202 | cause: "Message does not contain Action", 203 | } 204 | return call, e 205 | } 206 | call.Action = a 207 | p, err = unmarshalRequestPayload(a, rm[3], proto) 208 | var ocppErr *ocppError 209 | if err != nil { 210 | if errors.As(err, &ocppErr) { 211 | ocppErr.id = ui 212 | } 213 | return call, err 214 | } 215 | call.Payload = p 216 | ocppMsg = call 217 | case MessageTypeIdCallResult: 218 | p := rm[2] 219 | ocppMsg = &CallResult{ 220 | MessageTypeId: mti, 221 | UniqueId: ui, 222 | Payload: p, 223 | } 224 | case MessageTypeIdCallError: 225 | var me [5]interface{} 226 | _ = json.Unmarshal(b, &me) 227 | ocppMsg = &CallError{ 228 | MessageTypeId: mti, 229 | UniqueId: ui, 230 | ErrorCode: me[2].(string), 231 | ErrorDescription: me[3].(string), 232 | ErrorDetails: me[4], 233 | } 234 | default: 235 | e := &ocppError{ 236 | id: ui, 237 | code: "MessageTypeNotSupported", 238 | cause: fmt.Sprintf("A message with: %v is not supported by this implementation", mti), 239 | } 240 | return nil, e 241 | } 242 | return ocppMsg, nil 243 | } 244 | 245 | // unmarshalRequestPayload unmarshals raw bytes of request type payload 246 | // to a corresponding struct depending on Action and ocpp protocol 247 | func unmarshalRequestPayload(actionName string, rawPayload json.RawMessage, proto string) (Payload, error) { 248 | var uf func(json.RawMessage) (Payload, error) // uf unmarshal function for a specific action request 249 | var ok bool 250 | switch proto { 251 | case ocppV16: 252 | uf, ok = reqmapv16[actionName] 253 | case ocppV201: 254 | uf, ok = reqmapv201[actionName] 255 | } 256 | if !ok { 257 | e := &ocppError{ 258 | code: "NotImplemented", 259 | cause: fmt.Sprintf("Action %v is not implemented", actionName), 260 | } 261 | return nil, e 262 | } 263 | return uf(rawPayload) 264 | } 265 | 266 | // unmarshalRequestPayloadv16 unmarshals raw request type payload to a ***Req type struct of ocppv16 267 | func unmarshalRequestPayloadv16[T any](rawPayload json.RawMessage) (Payload, error) { 268 | var p T 269 | var payload Payload 270 | err := json.Unmarshal(rawPayload, &p) 271 | if err != nil { 272 | e := &ocppError{ 273 | code: "TypeConstraintViolationError", 274 | cause: "Call Payload is not valid", 275 | } 276 | return nil, e 277 | } 278 | err = validateV16.Struct(p) 279 | if err != nil { 280 | // TODO: construct more detailed error 281 | e := &ocppError{ 282 | code: "PropertyConstraintViolationError", 283 | cause: "Call Payload is not valid", 284 | } 285 | return nil, e 286 | } 287 | payload = &p 288 | return payload, nil 289 | } 290 | 291 | // unmarshalRequestPayloadv201 unmarshals raw request type payload to a ***Req type struct of ocppv201 292 | func unmarshalRequestPayloadv201[T any](rawPayload json.RawMessage) (Payload, error) { 293 | var p T 294 | var payload Payload 295 | err := json.Unmarshal(rawPayload, &p) 296 | if err != nil { 297 | e := &ocppError{ 298 | code: "TypeConstraintViolationError", 299 | cause: "Call Payload is not valid", 300 | } 301 | log.Error(err) 302 | return nil, e 303 | } 304 | err = validateV201.Struct(p) 305 | if err != nil { 306 | // TODO: construct more detailed error 307 | e := &ocppError{ 308 | code: "PropertyConstraintViolationError", 309 | cause: "Call Payload is not valid", 310 | } 311 | log.Error(err) 312 | return nil, e 313 | } 314 | payload = &p 315 | return payload, nil 316 | } 317 | 318 | // unmarshalResponsePv16 unmarshals raw bytes of request type payload 319 | // to a corresponding struct depending on actionName 320 | func unmarshalResponsePv16(actionName string, rawPayload json.RawMessage) (Payload, error) { 321 | uf, ok := resmapv16[actionName] // uf unmarshal function for a specific action request 322 | if !ok { 323 | return nil, errInvalidAction 324 | } 325 | return uf(rawPayload) 326 | } 327 | 328 | // unmarshalResponsePv201 unmarshals raw bytes of request type payload 329 | // to a corresponding struct depending on actionName 330 | func unmarshalResponsePv201(mAction string, rawPayload json.RawMessage) (Payload, error) { 331 | uf, ok := resmapv201[mAction] // uf unmarshal function for a specific action request 332 | if !ok { 333 | return nil, errInvalidAction 334 | } 335 | return uf(rawPayload) 336 | } 337 | 338 | // unmarshalResponsePayloadv16 unmarshals raw response type payload to a ***Conf type struct of ocppv16 339 | func unmarshalResponsePayloadv16[T any](rawPayload json.RawMessage) (Payload, error) { 340 | var p T 341 | var payload Payload 342 | err := json.Unmarshal(rawPayload, &p) 343 | if err != nil { 344 | return nil, err 345 | } 346 | err = validateV16.Struct(p) 347 | if err != nil { 348 | return nil, err 349 | } 350 | payload = &p 351 | return payload, nil 352 | } 353 | 354 | // unmarshalResponsePayloadv201 unmarshals raw response type payload to a ***Res type struct of ocppv201 355 | func unmarshalResponsePayloadv201[T any](rawPayload json.RawMessage) (Payload, error) { 356 | var p T 357 | var payload Payload 358 | err := json.Unmarshal(rawPayload, &p) 359 | if err != nil { 360 | return nil, err 361 | } 362 | err = validateV201.Struct(p) 363 | if err != nil { 364 | return nil, err 365 | } 366 | payload = &p 367 | return payload, nil 368 | } 369 | 370 | func init() { 371 | reqmapv16 = map[string]func(json.RawMessage) (Payload, error){ 372 | "BootNotification": unmarshalRequestPayloadv16[v16.BootNotificationReq], 373 | "Authorize": unmarshalRequestPayloadv16[v16.AuthorizeReq], 374 | "DataTransfer": unmarshalRequestPayloadv16[v16.DataTransferReq], 375 | "DiagnosticsStatusNotification": unmarshalRequestPayloadv16[v16.DiagnosticsStatusNotificationReq], 376 | "FirmwareStatusNotification": unmarshalRequestPayloadv16[v16.FirmwareStatusNotificationReq], 377 | "Heartbeat": unmarshalRequestPayloadv16[v16.HeartbeatReq], 378 | "MeterValues": unmarshalRequestPayloadv16[v16.MeterValuesReq], 379 | "StartTransaction": unmarshalRequestPayloadv16[v16.StartTransactionReq], 380 | "StatusNotification": unmarshalRequestPayloadv16[v16.StatusNotificationReq], 381 | "StopTransaction": unmarshalRequestPayloadv16[v16.StopTransactionReq], 382 | "CanCelReservation": unmarshalRequestPayloadv16[v16.CancelReservationReq], 383 | "ChangeAvailability": unmarshalRequestPayloadv16[v16.ChangeAvailabilityReq], 384 | "ChangeConfiguration": unmarshalRequestPayloadv16[v16.ChangeConfigurationReq], 385 | "ClearCache": unmarshalRequestPayloadv16[v16.ClearCacheReq], 386 | "ClearChargingProfile": unmarshalRequestPayloadv16[v16.ClearChargingProfileReq], 387 | "GetCompositeSchedule": unmarshalRequestPayloadv16[v16.GetCompositeScheduleReq], 388 | "GetConfiguration": unmarshalRequestPayloadv16[v16.GetConfigurationReq], 389 | "GetDiagnostics": unmarshalRequestPayloadv16[v16.GetDiagnosticsReq], 390 | "GetLocalListVersion": unmarshalRequestPayloadv16[v16.GetLocalListVersionReq], 391 | "RemoteStartTransaction": unmarshalRequestPayloadv16[v16.RemoteStartTransactionReq], 392 | "RemoteStopTransaction": unmarshalRequestPayloadv16[v16.RemoteStopTransactionReq], 393 | "ReserveNow": unmarshalRequestPayloadv16[v16.ReserveNowReq], 394 | "Reset": unmarshalRequestPayloadv16[v16.ResetReq], 395 | "SendLocalList": unmarshalRequestPayloadv16[v16.SendLocalListReq], 396 | "SetChargingProfile": unmarshalRequestPayloadv16[v16.SetChargingProfileReq], 397 | "TriggerMessage": unmarshalRequestPayloadv16[v16.TriggerMessageReq], 398 | "UnlockConnector": unmarshalRequestPayloadv16[v16.UnlockConnectorReq], 399 | "UpdateFirmware": unmarshalRequestPayloadv16[v16.UpdateFirmwareReq], 400 | } 401 | 402 | resmapv16 = map[string]func(json.RawMessage) (Payload, error){ 403 | "BootNotification": unmarshalResponsePayloadv16[v16.BootNotificationConf], 404 | "Authorize": unmarshalResponsePayloadv16[v16.AuthorizeConf], 405 | "DataTransfer": unmarshalResponsePayloadv16[v16.DataTransferConf], 406 | "DiagnosticsStatusNotification": unmarshalResponsePayloadv16[v16.DiagnosticsStatusNotificationConf], 407 | "FirmwareStatusNotification": unmarshalResponsePayloadv16[v16.FirmwareStatusNotificationConf], 408 | "Heartbeat": unmarshalResponsePayloadv16[v16.HeartbeatConf], 409 | "MeterValues": unmarshalResponsePayloadv16[v16.MeterValuesConf], 410 | "StartTransaction": unmarshalResponsePayloadv16[v16.StartTransactionConf], 411 | "StatusNotification": unmarshalResponsePayloadv16[v16.StatusNotificationConf], 412 | "StopTransaction": unmarshalResponsePayloadv16[v16.StopTransactionConf], 413 | "CancelReservation": unmarshalResponsePayloadv16[v16.CancelReservationConf], 414 | "ChangeAvailability": unmarshalResponsePayloadv16[v16.ChangeAvailabilityConf], 415 | "ChangeConfiguration": unmarshalResponsePayloadv16[v16.ChangeConfigurationConf], 416 | "ClearCache": unmarshalResponsePayloadv16[v16.ClearCacheConf], 417 | "ClearChargingProfile": unmarshalResponsePayloadv16[v16.ClearChargingProfileConf], 418 | "GetCompositeSchedule": unmarshalResponsePayloadv16[v16.GetCompositeScheduleConf], 419 | "GetConfiguration": unmarshalResponsePayloadv16[v16.GetConfigurationConf], 420 | "GetDiagnostics": unmarshalResponsePayloadv16[v16.GetDiagnosticsConf], 421 | "GetLocalListVersion": unmarshalResponsePayloadv16[v16.GetLocalListVersionConf], 422 | "RemoteStartTransaction": unmarshalResponsePayloadv16[v16.RemoteStartTransactionConf], 423 | "RemoteStopTransaction": unmarshalResponsePayloadv16[v16.RemoteStopTransactionConf], 424 | "ReserveNow": unmarshalResponsePayloadv16[v16.ReserveNowConf], 425 | "Reset": unmarshalResponsePayloadv16[v16.ResetConf], 426 | "SendLocalList": unmarshalResponsePayloadv16[v16.SendLocalListConf], 427 | "SetChargingProfile": unmarshalResponsePayloadv16[v16.SetChargingProfileConf], 428 | "TriggerMessage": unmarshalResponsePayloadv16[v16.TriggerMessageConf], 429 | "UnlockConnector": unmarshalResponsePayloadv16[v16.UnlockConnectorConf], 430 | "UpdateFirmware": unmarshalResponsePayloadv16[v16.UpdateFirmwareConf], 431 | } 432 | 433 | reqmapv201 = map[string]func(json.RawMessage) (Payload, error){ 434 | "Authorize": unmarshalRequestPayloadv201[v201.AuthorizeReq], 435 | "BootNotification": unmarshalRequestPayloadv201[v201.BootNotificationReq], 436 | "CancelReservation": unmarshalRequestPayloadv201[v201.CancelReservationReq], 437 | "CertificateSigned": unmarshalRequestPayloadv201[v201.CertificateSignedReq], 438 | "ChangeAvailability": unmarshalRequestPayloadv201[v201.ChangeAvailabilityReq], 439 | "ClearCache": unmarshalRequestPayloadv201[v201.ClearCacheReq], 440 | "ClearChargingProfile": unmarshalRequestPayloadv201[v201.ClearChargingProfileReq], 441 | "ClearDisplayMessage": unmarshalRequestPayloadv201[v201.ClearDisplayMessageReq], 442 | "ClearedChargingLimit": unmarshalRequestPayloadv201[v201.ClearedChargingLimitReq], 443 | "ClearVariableMonitoring": unmarshalRequestPayloadv201[v201.ClearVariableMonitoringReq], 444 | "CostUpdated": unmarshalRequestPayloadv201[v201.CostUpdatedReq], 445 | "CustomerInformation": unmarshalRequestPayloadv201[v201.CustomerInformationReq], 446 | "DataTransfer": unmarshalRequestPayloadv201[v201.DataTransferReq], 447 | "DeleteCertificate": unmarshalRequestPayloadv201[v201.DeleteCertificateReq], 448 | "FirmwareStatusNotification": unmarshalRequestPayloadv201[v201.FirmwareStatusNotificationReq], 449 | "Get15118EVCertificate": unmarshalRequestPayloadv201[v201.Get15118EVCertificateReq], 450 | "GetBaseReport": unmarshalRequestPayloadv201[v201.GetBaseReportReq], 451 | "GetCertificateStatus": unmarshalRequestPayloadv201[v201.GetCertificateStatusReq], 452 | "GetChargingProfiles": unmarshalRequestPayloadv201[v201.GetChargingProfilesReq], 453 | "GetCompositeSchedule": unmarshalRequestPayloadv201[v201.GetCompositeScheduleReq], 454 | "GetDisplayMessages": unmarshalRequestPayloadv201[v201.GetDisplayMessagesReq], 455 | "GetInstalledCertificateIds": unmarshalRequestPayloadv201[v201.GetInstalledCertificateIdsReq], 456 | "GetLocalListVersion": unmarshalRequestPayloadv201[v201.GetLocalListVersionReq], 457 | "GetLog": unmarshalRequestPayloadv201[v201.GetLogReq], 458 | "GetMonitoringReport": unmarshalRequestPayloadv201[v201.GetMonitoringReportReq], 459 | "GetReport": unmarshalRequestPayloadv201[v201.GetReportReq], 460 | "GetTransactionStatus": unmarshalRequestPayloadv201[v201.GetTransactionStatusReq], 461 | "GetVariables": unmarshalRequestPayloadv201[v201.GetVariablesReq], 462 | "Heartbeat": unmarshalRequestPayloadv201[v201.HeartbeatReq], 463 | "InstallCertificate": unmarshalRequestPayloadv201[v201.InstallCertificateReq], 464 | "LogStatusNotification": unmarshalRequestPayloadv201[v201.LogStatusNotificationReq], 465 | "MeterValues": unmarshalRequestPayloadv201[v201.MeterValuesReq], 466 | "NotifyChargingLimit": unmarshalRequestPayloadv201[v201.NotifyChargingLimitReq], 467 | "NotifyCustomerInformation": unmarshalRequestPayloadv201[v201.NotifyCustomerInformationReq], 468 | "NotifyDisplayMessages": unmarshalRequestPayloadv201[v201.NotifyDisplayMessagesReq], 469 | "NotifyEVChargingNeeds": unmarshalRequestPayloadv201[v201.NotifyEVChargingNeedsReq], 470 | "NotifyEVChargingSchedule": unmarshalRequestPayloadv201[v201.NotifyEVChargingScheduleReq], 471 | "NotifyEvent": unmarshalRequestPayloadv201[v201.NotifyEventReq], 472 | "NotifyMonitoringReport": unmarshalRequestPayloadv201[v201.NotifyMonitoringReportReq], 473 | "NotifyReport": unmarshalRequestPayloadv201[v201.NotifyReportReq], 474 | "PublishFirmware": unmarshalRequestPayloadv201[v201.PublishFirmwareReq], 475 | "PublishFirmawareStatusNotification": unmarshalRequestPayloadv201[v201.PublishFirmwareStatusNotificationReq], 476 | "ReportChargingProfiles": unmarshalRequestPayloadv201[v201.ReportChargingProfilesReq], 477 | "RequestStartTransaction": unmarshalRequestPayloadv201[v201.RequestStartTransactionReq], 478 | "RequestStopTransaction": unmarshalRequestPayloadv201[v201.RequestStopTransactionReq], 479 | "ReservationStatusUpdate": unmarshalRequestPayloadv201[v201.ReservationStatusUpdateReq], 480 | "ReserveNow": unmarshalRequestPayloadv201[v201.ReserveNowReq], 481 | "Reset": unmarshalRequestPayloadv201[v201.ResetReq], 482 | "SecurityEventNotification": unmarshalRequestPayloadv201[v201.SecurityEventNotificationReq], 483 | "SendLocalList": unmarshalRequestPayloadv201[v201.SendLocalListReq], 484 | "SetChargingProfile": unmarshalRequestPayloadv201[v201.SetChargingProfileReq], 485 | "SetDisplayMessage": unmarshalRequestPayloadv201[v201.SetDisplayMessageReq], 486 | "SetMonitoringBase": unmarshalRequestPayloadv201[v201.SetMonitoringBaseReq], 487 | "SetMonitoringLevel": unmarshalRequestPayloadv201[v201.SetMonitoringLevelReq], 488 | "SetNetworkProfile": unmarshalRequestPayloadv201[v201.SetNetworkProfileReq], 489 | "SetVariableMonitoring": unmarshalRequestPayloadv201[v201.SetVariableMonitoringReq], 490 | "SetVariables": unmarshalRequestPayloadv201[v201.SetVariablesReq], 491 | "SignCertificate": unmarshalRequestPayloadv201[v201.SignCertificateReq], 492 | "StatusNotification": unmarshalRequestPayloadv201[v201.StatusNotificationReq], 493 | "TransactionEvent": unmarshalRequestPayloadv201[v201.TransactionEventReq], 494 | "TriggerMessage": unmarshalRequestPayloadv201[v201.TriggerMessageReq], 495 | "UnlockConnector": unmarshalRequestPayloadv201[v201.UnlockConnectorReq], 496 | "UnpublishFirmware": unmarshalRequestPayloadv201[v201.UnpublishFirmwareReq], 497 | "UpdateFirmware": unmarshalRequestPayloadv201[v201.UpdateFirmwareReq], 498 | } 499 | 500 | resmapv201 = map[string]func(json.RawMessage) (Payload, error){ 501 | "Authorize": unmarshalResponsePayloadv201[v201.AuthorizeRes], 502 | "BootNotification": unmarshalResponsePayloadv201[v201.BootNotificationRes], 503 | "CancelReservation": unmarshalResponsePayloadv201[v201.CancelReservationRes], 504 | "CertificateSigned": unmarshalResponsePayloadv201[v201.CertificateSignedRes], 505 | "ChangeAvailability": unmarshalResponsePayloadv201[v201.ChangeAvailabilityRes], 506 | "ClearCache": unmarshalResponsePayloadv201[v201.ClearCacheRes], 507 | "ClearChargingProfile": unmarshalResponsePayloadv201[v201.ClearChargingProfileRes], 508 | "ClearDisplayMessage": unmarshalResponsePayloadv201[v201.ClearDisplayMessageRes], 509 | "ClearedChargingLimit": unmarshalResponsePayloadv201[v201.ClearedChargingLimitRes], 510 | "ClearVariableMonitoring": unmarshalResponsePayloadv201[v201.ClearVariableMonitoringRes], 511 | "CostUpdated": unmarshalResponsePayloadv201[v201.CostUpdatedRes], 512 | "CustomerInformation": unmarshalResponsePayloadv201[v201.CustomerInformationRes], 513 | "DataTransfer": unmarshalResponsePayloadv201[v201.DataTransferRes], 514 | "DeleteCertificate": unmarshalResponsePayloadv201[v201.DeleteCertificateRes], 515 | "FirmwareStatusNotification": unmarshalResponsePayloadv201[v201.FirmwareStatusNotificationRes], 516 | "Get15118EVCertificate": unmarshalResponsePayloadv201[v201.Get15118EVCertificateRes], 517 | "GetBaseReport": unmarshalResponsePayloadv201[v201.GetBaseReportRes], 518 | "GetCertificateStatus": unmarshalResponsePayloadv201[v201.GetCertificateStatusRes], 519 | "GetChargingProfiles": unmarshalResponsePayloadv201[v201.GetChargingProfilesRes], 520 | "GetCompositeSchedule": unmarshalResponsePayloadv201[v201.GetCompositeScheduleRes], 521 | "GetDisplayMessages": unmarshalResponsePayloadv201[v201.GetDisplayMessagesRes], 522 | "GetInstalledCertificateIds": unmarshalResponsePayloadv201[v201.GetInstalledCertificateIdsRes], 523 | "GetLocalListVersion": unmarshalResponsePayloadv201[v201.GetLocalListVersionRes], 524 | "GetLog": unmarshalResponsePayloadv201[v201.GetLogRes], 525 | "GetMonitoringReport": unmarshalResponsePayloadv201[v201.GetMonitoringReportRes], 526 | "GetReport": unmarshalResponsePayloadv201[v201.GetReportRes], 527 | "GetTransactionStatus": unmarshalResponsePayloadv201[v201.GetTransactionStatusRes], 528 | "GetVariables": unmarshalResponsePayloadv201[v201.GetVariablesRes], 529 | "Heartbeat": unmarshalResponsePayloadv201[v201.HeartbeatRes], 530 | "InstallCertificate": unmarshalResponsePayloadv201[v201.InstallCertificateRes], 531 | "LogStatusNotification": unmarshalResponsePayloadv201[v201.LogStatusNotificationRes], 532 | "MeterValues": unmarshalResponsePayloadv201[v201.MeterValuesRes], 533 | "NotifyChargingLimit": unmarshalResponsePayloadv201[v201.NotifyChargingLimitRes], 534 | "NotifyCustomerInformation": unmarshalResponsePayloadv201[v201.NotifyCustomerInformationRes], 535 | "NotifyDisplayMessages": unmarshalResponsePayloadv201[v201.NotifyDisplayMessagesRes], 536 | "NotifyEVChargingNeeds": unmarshalResponsePayloadv201[v201.NotifyEVChargingNeedsRes], 537 | "NotifyEVChargingSchedule": unmarshalResponsePayloadv201[v201.NotifyEVChargingScheduleRes], 538 | "NotifyEvent": unmarshalResponsePayloadv201[v201.NotifyEventRes], 539 | "NotifyMonitoringReport": unmarshalResponsePayloadv201[v201.NotifyMonitoringReportRes], 540 | "NotifyReport": unmarshalResponsePayloadv201[v201.NotifyReportRes], 541 | "PublishFirmware": unmarshalResponsePayloadv201[v201.PublishFirmwareRes], 542 | "PublishFirmawareStatusNotification": unmarshalResponsePayloadv201[v201.PublishFirmwareStatusNotificationRes], 543 | "ReportChargingProfiles": unmarshalResponsePayloadv201[v201.ReportChargingProfilesRes], 544 | "RequestStartTransaction": unmarshalResponsePayloadv201[v201.RequestStartTransactionRes], 545 | "RequestStopTransaction": unmarshalResponsePayloadv201[v201.RequestStopTransactionRes], 546 | "ReservationStatusUpdate": unmarshalResponsePayloadv201[v201.ReservationStatusUpdateRes], 547 | "ReserveNow": unmarshalResponsePayloadv201[v201.ReserveNowRes], 548 | "Reset": unmarshalResponsePayloadv201[v201.ResetRes], 549 | "SecurityEventNotification": unmarshalResponsePayloadv201[v201.SecurityEventNotificationRes], 550 | "SendLocalList": unmarshalResponsePayloadv201[v201.SendLocalListRes], 551 | "SetChargingProfile": unmarshalResponsePayloadv201[v201.SetChargingProfileRes], 552 | "SetDisplayMessage": unmarshalResponsePayloadv201[v201.SetDisplayMessageRes], 553 | "SetMonitoringBase": unmarshalResponsePayloadv201[v201.SetMonitoringBaseRes], 554 | "SetMonitoringLevel": unmarshalResponsePayloadv201[v201.SetMonitoringLevelRes], 555 | "SetNetworkProfile": unmarshalResponsePayloadv201[v201.SetNetworkProfileRes], 556 | "SetVariableMonitoring": unmarshalResponsePayloadv201[v201.SetVariableMonitoringRes], 557 | "SetVariables": unmarshalResponsePayloadv201[v201.SetVariablesRes], 558 | "SignCertificate": unmarshalResponsePayloadv201[v201.SignCertificateRes], 559 | "StatusNotification": unmarshalResponsePayloadv201[v201.StatusNotificationRes], 560 | "TransactionEvent": unmarshalResponsePayloadv201[v201.TransactionEventRes], 561 | "TriggerMessage": unmarshalResponsePayloadv201[v201.TriggerMessageRes], 562 | "UnlockConnector": unmarshalResponsePayloadv201[v201.UnlockConnectorRes], 563 | "UnpublishFirmware": unmarshalResponsePayloadv201[v201.UnpublishFirmwareRes], 564 | "UpdateFirmware": unmarshalResponsePayloadv201[v201.UpdateFirmwareRes], 565 | } 566 | } 567 | -------------------------------------------------------------------------------- /v201/validation_register.go: -------------------------------------------------------------------------------- 1 | package v201 2 | 3 | import ( 4 | "reflect" 5 | "regexp" 6 | "strings" 7 | 8 | "gopkg.in/go-playground/validator.v9" 9 | ) 10 | 11 | var Validate = validator.New() 12 | 13 | func contains(elems []string, v string) bool { 14 | for _, s := range elems { 15 | if v == s { 16 | return true 17 | } 18 | } 19 | return false 20 | } 21 | 22 | func init() { 23 | 24 | // register function to get tag name from json tags. 25 | Validate.RegisterTagNameFunc(func(fld reflect.StructField) string { 26 | name := strings.SplitN(fld.Tag.Get("json"), ",", 2)[0] 27 | if name == "-" { 28 | return "" 29 | } 30 | return name 31 | }) 32 | Validate.RegisterValidation("ISO8601date", IsISO8601Date) 33 | Validate.RegisterValidation("APNAuthenticationEnumType", isAPNAuthenticationEnumType) 34 | Validate.RegisterValidation("AttributeEnumType", isAttributeEnumType) 35 | Validate.RegisterValidation("AuthorizationStatusEnumType", isAuthorizationStatusEnumType) 36 | Validate.RegisterValidation("AuthorizeCertificateStatusEnumType", isAuthorizeCertificateStatusEnumType) 37 | Validate.RegisterValidation("BootReasonEnumType", isBootReasonEnumType) 38 | Validate.RegisterValidation("CancelReservationStatusEnumType", isCancelReservationStatusEnumType) 39 | Validate.RegisterValidation("CertificateActionEnumType", isCertificateActionEnumType) 40 | Validate.RegisterValidation("CertificateSignedStatusEnumType", isCertificateSignedStatusEnumType) 41 | Validate.RegisterValidation("CertificateSigningUseEnumType", isCertificateSigningUseEnumType) 42 | Validate.RegisterValidation("ChangeAvailabilityStatusEnumType", isChangeAvailabilityStatusEnumType) 43 | Validate.RegisterValidation("ChargingLimitSourceEnumType", isChargingLimitSourceEnumType) 44 | Validate.RegisterValidation("ChargingProfileKindEnumType", isChargingProfileKindEnumType) 45 | Validate.RegisterValidation("ChargingProfilePurposeEnumType", isChargingProfilePurposeEnumType) 46 | Validate.RegisterValidation("ChargingProfileStatusEnumType", isChargingProfileStatusEnumType) 47 | Validate.RegisterValidation("ChargingRateUnitEnumType", isChargingRateUnitEnumType) 48 | Validate.RegisterValidation("ChargingStateEnumType", isChargingStateEnumType) 49 | Validate.RegisterValidation("ClearCacheStatusEnumType", isClearCacheStatusEnumType) 50 | Validate.RegisterValidation("ClearChargingProfileStatusEnumType", isClearChargingProfileStatusEnumType) 51 | Validate.RegisterValidation("ClearMessageStatusEnumType", isClearMessageStatusEnumType) 52 | Validate.RegisterValidation("ClearMonitoringStatusEnumType", isClearMonitoringStatusEnumType) 53 | Validate.RegisterValidation("ComponentCriterionEnumType", isComponentCriterionEnumType) 54 | Validate.RegisterValidation("ConnectorEnumType", isConnectorEnumType) 55 | Validate.RegisterValidation("ConnectorStatusEnumType", isConnectorStatusEnumType) 56 | Validate.RegisterValidation("CostKindEnumType", isCostKindEnumType) 57 | Validate.RegisterValidation("CustomerInformationStatusEnumType", isCustomerInformationStatusEnumType) 58 | Validate.RegisterValidation("DataEnumType", isDataEnumType) 59 | Validate.RegisterValidation("DataTransferStatusEnumType", isDataTransferStatusEnumType) 60 | Validate.RegisterValidation("DeleteCertificateStatusEnumType", isDeleteCertificateStatusEnumType) 61 | Validate.RegisterValidation("DisplayMessageStatusEnumType", isDisplayMessageStatusEnumType) 62 | Validate.RegisterValidation("EnergyTransferModeEnumType", isEnergyTransferModeEnumType) 63 | Validate.RegisterValidation("EventNotificationEnumType", isEventNotificationEnumType) 64 | Validate.RegisterValidation("EventTriggerEnumType", isEventTriggerEnumType) 65 | Validate.RegisterValidation("FirmwareStatusEnumType", isFirmwareStatusEnumType) 66 | Validate.RegisterValidation("GenericDeviceModelStatusEnumType", isGenericDeviceModelStatusEnumType) 67 | Validate.RegisterValidation("GenericStatusEnumType", isGenericStatusEnumType) 68 | Validate.RegisterValidation("GetCertificateIdUseEnumType", isGetCertificateIdUseEnumType) 69 | Validate.RegisterValidation("GetCertificateStatusEnumType", isGetCertificateStatusEnumType) 70 | Validate.RegisterValidation("GetChargingProfileStatusEnumType", isGetChargingProfileStatusEnumType) 71 | Validate.RegisterValidation("GetDisplayMessagesStatusEnumType", isGetDisplayMessagesStatusEnumType) 72 | Validate.RegisterValidation("GetInstalledCertificateStatusEnumType", isGetInstalledCertificateStatusEnumType) 73 | Validate.RegisterValidation("GetVariableStatusEnumType", isGetVariableStatusEnumType) 74 | Validate.RegisterValidation("HashAlgorithmEnumType", isHashAlgorithmEnumType) 75 | Validate.RegisterValidation("IdTokenEnumType", isIdTokenEnumType) 76 | Validate.RegisterValidation("InstallCertificateStatusEnumType", isInstallCertificateStatusEnumType) 77 | Validate.RegisterValidation("InstallCertificateUseEnumType", isInstallCertificateUseEnumType) 78 | Validate.RegisterValidation("Iso15118EVCertificateStatusEnumType", isIso15118EVCertificateStatusEnumType) 79 | Validate.RegisterValidation("LocationEnumType", isLocationEnumType) 80 | Validate.RegisterValidation("LogEnumType", isLogEnumType) 81 | Validate.RegisterValidation("LogStatusEnumType", isLogStatusEnumType) 82 | Validate.RegisterValidation("MeasurandEnumType", isMeasurandEnumType) 83 | Validate.RegisterValidation("MessageFormatEnumType", isMessageFormatEnumType) 84 | Validate.RegisterValidation("MessagePriorityEnumType", isMessagePriorityEnumType) 85 | Validate.RegisterValidation("MessageStateEnumType", isMessageStateEnumType) 86 | Validate.RegisterValidation("MessageTriggerEnumType", isMessageTriggerEnumType) 87 | Validate.RegisterValidation("MonitorEnumType", isMonitorEnumType) 88 | Validate.RegisterValidation("MonitoringBaseEnumType", isMonitoringBaseEnumType) 89 | Validate.RegisterValidation("MonitoringCriterionEnumType", isMonitoringCriterionEnumType) 90 | Validate.RegisterValidation("MutabilityEnumType", isMutabilityEnumType) 91 | Validate.RegisterValidation("NotifyEVChargingNeedsStatusEnumType", isNotifyEVChargingNeedsStatusEnumType) 92 | Validate.RegisterValidation("OCPPInterfaceEnumType", isOCPPInterfaceEnumType) 93 | Validate.RegisterValidation("OCPPTransportEnumType", isOCPPTransportEnumType) 94 | Validate.RegisterValidation("OCPPVersionEnumType", isOCPPVersionEnumType) 95 | Validate.RegisterValidation("OperationalStatusEnumType", isOperationalStatusEnumType) 96 | Validate.RegisterValidation("PhaseEnumType", isPhaseEnumType) 97 | Validate.RegisterValidation("PublishFirmwareStatusEnumType", isPublishFirmwareStatusEnumType) 98 | Validate.RegisterValidation("ReadingContextEnumType", isReadingContextEnumType) 99 | Validate.RegisterValidation("ReasonEnumType", isReasonEnumType) 100 | Validate.RegisterValidation("RecurrencyKindEnumType", isRecurrencyKindEnumType) 101 | Validate.RegisterValidation("RegistrationStatusEnumType", isRegistrationStatusEnumType) 102 | Validate.RegisterValidation("ReportBaseEnumType", isReportBaseEnumType) 103 | Validate.RegisterValidation("RequestStartStopStatusEnumType", isRequestStartStopStatusEnumType) 104 | Validate.RegisterValidation("ReservationUpdateStatusEnumType", isReservationUpdateStatusEnumType) 105 | Validate.RegisterValidation("ReserveNowStatusEnumType", isReserveNowStatusEnumType) 106 | Validate.RegisterValidation("ResetEnumType", isResetEnumType) 107 | Validate.RegisterValidation("ResetStatusEnumType", isResetStatusEnumType) 108 | Validate.RegisterValidation("SendLocalListStatusEnumType", isSendLocalListStatusEnumType) 109 | Validate.RegisterValidation("SetMonitoringStatusEnumType", isSetMonitoringStatusEnumType) 110 | Validate.RegisterValidation("SetNetworkProfileStatusEnumType", isSetNetworkProfileStatusEnumType) 111 | Validate.RegisterValidation("SetVariableStatusEnumType", isSetVariableStatusEnumType) 112 | Validate.RegisterValidation("TransactionEventEnumType", isTransactionEventEnumType) 113 | Validate.RegisterValidation("TriggerMessageStatusEnumType", isTriggerMessageStatusEnumType) 114 | Validate.RegisterValidation("TriggerReasonEnumType", isTriggerReasonEnumType) 115 | Validate.RegisterValidation("UnlockStatusEnumType", isUnlockStatusEnumType) 116 | Validate.RegisterValidation("UnpublishFirmwareStatusEnumType", isUnpublishFirmwareStatusEnumType) 117 | Validate.RegisterValidation("UpdateEnumType", isUpdateEnumType) 118 | Validate.RegisterValidation("UpdateFirmwareStatusEnumType", isUpdateFirmwareStatusEnumType) 119 | Validate.RegisterValidation("UpdateFirmwareStatusEnumType", isUpdateFirmwareStatusEnumType) 120 | Validate.RegisterValidation("UploadLogStatusEnumType", isUploadLogStatusEnumType) 121 | Validate.RegisterValidation("VPNEnumType", isVPNEnumType) 122 | 123 | } 124 | 125 | func IsISO8601Date(fl validator.FieldLevel) bool { 126 | ISO8601DateRegexString := "^(?:[1-9]\\d{3}-(?:(?:0[1-9]|1[0-2])-(?:0[1-9]|1\\d|2[0-8])|(?:0[13-9]|1[0-2])-(?:29|30)|(?:0[13578]|1[02])-31)|(?:[1-9]\\d(?:0[48]|[2468][048]|[13579][26])|(?:[2468][048]|[13579][26])00)-02-29)T(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d{1,9})?(?:Z|[+-][01]\\d:[0-5]\\d)$" 127 | ISO8601DateRegex := regexp.MustCompile(ISO8601DateRegexString) 128 | return ISO8601DateRegex.MatchString(fl.Field().String()) 129 | } 130 | 131 | func isAPNAuthenticationEnumType(fl validator.FieldLevel) bool { 132 | enum := fl.Field().String() 133 | switch enum { 134 | case "CHAP", "NONE", "PAP", "AUTO": 135 | return true 136 | default: 137 | return false 138 | } 139 | } 140 | 141 | func isAttributeEnumType(fl validator.FieldLevel) bool { 142 | enum := fl.Field().String() 143 | switch enum { 144 | case "Actual", "Target", "MinSet", "MaxSet": 145 | return true 146 | default: 147 | return false 148 | } 149 | } 150 | 151 | func isAuthorizationStatusEnumType(fl validator.FieldLevel) bool { 152 | enum := fl.Field().String() 153 | cases := []string{ 154 | "Accepted", 155 | "Blocked", 156 | "ConcurrentTx", 157 | "Expired", 158 | "Invalid", 159 | "NoCredit", 160 | "NotAllowedTypeEVSE", 161 | "NotAtThisLocation", 162 | "NotAtThisTime", 163 | "Unknown", 164 | } 165 | return contains(cases, enum) 166 | } 167 | 168 | func isAuthorizeCertificateStatusEnumType(fl validator.FieldLevel) bool { 169 | enum := fl.Field().String() 170 | cases := []string{ 171 | "Accepted", 172 | "SignatureError", 173 | "CertificateExpired", 174 | "NoCertificateAvailable", 175 | "CertChainError", 176 | "ContractCancelled", 177 | } 178 | return contains(cases, enum) 179 | } 180 | 181 | func isBootReasonEnumType(fl validator.FieldLevel) bool { 182 | enum := fl.Field().String() 183 | cases := []string{ 184 | "ApplicationReset", 185 | "FirmwareUpdate", 186 | "LocalReset", 187 | "PowerUp", 188 | "RemoteReset", 189 | "ScheduledReset", 190 | "Triggered", 191 | "Unknown", 192 | "Watchdog", 193 | } 194 | return contains(cases, enum) 195 | } 196 | 197 | func isCancelReservationStatusEnumType(fl validator.FieldLevel) bool { 198 | enum := fl.Field().String() 199 | switch enum { 200 | case "Accepted", "Rejected": 201 | return true 202 | default: 203 | return false 204 | } 205 | } 206 | 207 | func isCertificateActionEnumType(fl validator.FieldLevel) bool { 208 | enum := fl.Field().String() 209 | switch enum { 210 | case "Install", "Update": 211 | return true 212 | default: 213 | return false 214 | } 215 | } 216 | 217 | func isCertificateSignedStatusEnumType(fl validator.FieldLevel) bool { 218 | enum := fl.Field().String() 219 | switch enum { 220 | case "Accepted", "Rejected": 221 | return true 222 | default: 223 | return false 224 | } 225 | } 226 | 227 | func isCertificateSigningUseEnumType(fl validator.FieldLevel) bool { 228 | enum := fl.Field().String() 229 | switch enum { 230 | case "ChargingStationCertificate", "V2GCertificate": 231 | return true 232 | default: 233 | return false 234 | } 235 | } 236 | 237 | func isChangeAvailabilityStatusEnumType(fl validator.FieldLevel) bool { 238 | enum := fl.Field().String() 239 | switch enum { 240 | case "Accepted", "Rejected", "Scheduled": 241 | return true 242 | default: 243 | return false 244 | } 245 | } 246 | 247 | func isChargingLimitSourceEnumType(fl validator.FieldLevel) bool { 248 | enum := fl.Field().String() 249 | switch enum { 250 | case "EMS", "Other", "SO", "CSO": 251 | return true 252 | default: 253 | return false 254 | } 255 | } 256 | 257 | func isChargingProfileKindEnumType(fl validator.FieldLevel) bool { 258 | enum := fl.Field().String() 259 | switch enum { 260 | case "Absolute", "Recurring", "Relative": 261 | return true 262 | default: 263 | return false 264 | } 265 | } 266 | 267 | func isChargingProfilePurposeEnumType(fl validator.FieldLevel) bool { 268 | enum := fl.Field().String() 269 | switch enum { 270 | case "ChargingStationExternalConstraints", "ChargingStationMaxProfile", "TxDefaultProfile", "TxProfile": 271 | return true 272 | default: 273 | return false 274 | } 275 | } 276 | 277 | func isChargingProfileStatusEnumType(fl validator.FieldLevel) bool { 278 | enum := fl.Field().String() 279 | switch enum { 280 | case "Accepted", "Rejected": 281 | return true 282 | default: 283 | return false 284 | } 285 | } 286 | 287 | func isChargingRateUnitEnumType(fl validator.FieldLevel) bool { 288 | enum := fl.Field().String() 289 | switch enum { 290 | case "A", "W": 291 | return true 292 | default: 293 | return false 294 | } 295 | } 296 | 297 | func isChargingStateEnumType(fl validator.FieldLevel) bool { 298 | enum := fl.Field().String() 299 | cases := []string{ 300 | "Charging", 301 | "EVConnected", 302 | "SuspendedEV", 303 | "SuspendedEVSE", 304 | "Idle", 305 | } 306 | return contains(cases, enum) 307 | } 308 | 309 | func isClearCacheStatusEnumType(fl validator.FieldLevel) bool { 310 | enum := fl.Field().String() 311 | switch enum { 312 | case "Accepted", "Rejected": 313 | return true 314 | default: 315 | return false 316 | } 317 | } 318 | 319 | func isClearChargingProfileStatusEnumType(fl validator.FieldLevel) bool { 320 | enum := fl.Field().String() 321 | switch enum { 322 | case "Accepted", "Unknown": 323 | return true 324 | default: 325 | return false 326 | } 327 | } 328 | 329 | func isClearMessageStatusEnumType(fl validator.FieldLevel) bool { 330 | enum := fl.Field().String() 331 | switch enum { 332 | case "Accepted", "Unknown": 333 | return true 334 | default: 335 | return false 336 | } 337 | } 338 | 339 | func isClearMonitoringStatusEnumType(fl validator.FieldLevel) bool { 340 | enum := fl.Field().String() 341 | switch enum { 342 | case "Accepted", "Rejected", "NotFound": 343 | return true 344 | default: 345 | return false 346 | } 347 | } 348 | 349 | func isComponentCriterionEnumType(fl validator.FieldLevel) bool { 350 | enum := fl.Field().String() 351 | switch enum { 352 | case "Active", "Available", "Enabled", "Problem": 353 | return true 354 | default: 355 | return false 356 | } 357 | } 358 | 359 | func isConnectorEnumType(fl validator.FieldLevel) bool { 360 | enum := fl.Field().String() 361 | cases := []string{ 362 | "cCCS1", 363 | "cCCS2", 364 | "cG105", 365 | "cTesla", 366 | "cType1", 367 | "cType2", 368 | "s309-1P-16A", 369 | "s309-1P-32A", 370 | "s309-3P-16A", 371 | "s309-3P-32A", 372 | "sBS1361", 373 | "sCEE-7-7", 374 | "sType2", 375 | "sType3", 376 | "Other1PhMax16A", 377 | "Other1PhOver16A", 378 | "Other3Ph", 379 | "Pan", 380 | "wInductive", 381 | "wResonant", 382 | "Undetermined", 383 | "Unknown", 384 | } 385 | return contains(cases, enum) 386 | } 387 | 388 | func isConnectorStatusEnumType(fl validator.FieldLevel) bool { 389 | enum := fl.Field().String() 390 | cases := []string{ 391 | "Available", 392 | "Occupied", 393 | "Reserved", 394 | "Unavailable", 395 | "Faulted", 396 | } 397 | return contains(cases, enum) 398 | } 399 | 400 | func isCostKindEnumType(fl validator.FieldLevel) bool { 401 | enum := fl.Field().String() 402 | switch enum { 403 | case "CarbonDioxideEmission", "RelativePricePercentage", "RenewableGenerationPercentage": 404 | return true 405 | default: 406 | return false 407 | } 408 | } 409 | 410 | func isCustomerInformationStatusEnumType(fl validator.FieldLevel) bool { 411 | enum := fl.Field().String() 412 | switch enum { 413 | case "Accepted", "Rejected", "Invalid": 414 | return true 415 | default: 416 | return false 417 | } 418 | } 419 | 420 | func isDataEnumType(fl validator.FieldLevel) bool { 421 | enum := fl.Field().String() 422 | cases := []string{ 423 | "string", 424 | "decimal", 425 | "integer", 426 | "dateTime", 427 | "boolean", 428 | "OptionList", 429 | "SequenceList", 430 | "MemberList", 431 | } 432 | return contains(cases, enum) 433 | } 434 | 435 | func isDataTransferStatusEnumType(fl validator.FieldLevel) bool { 436 | enum := fl.Field().String() 437 | switch enum { 438 | case "Accepted", "Rejected", "UnknownMessageId", "UnknownVendorId": 439 | return true 440 | default: 441 | return false 442 | } 443 | } 444 | 445 | func isDeleteCertificateStatusEnumType(fl validator.FieldLevel) bool { 446 | enum := fl.Field().String() 447 | switch enum { 448 | case "Accepted", "Failed", "NotFound": 449 | return true 450 | default: 451 | return false 452 | } 453 | } 454 | 455 | func isDisplayMessageStatusEnumType(fl validator.FieldLevel) bool { 456 | enum := fl.Field().String() 457 | cases := []string{ 458 | "Accepted", 459 | "NotSupportedMessageFormat", 460 | "Rejected", 461 | "NotSupportedPriority", 462 | "NotSupportedState", 463 | "UnknownTransaction", 464 | } 465 | return contains(cases, enum) 466 | } 467 | 468 | func isEnergyTransferModeEnumType(fl validator.FieldLevel) bool { 469 | enum := fl.Field().String() 470 | switch enum { 471 | case "DC", "AC_single_phase", "AC_two_phase", "AC_three_phase": 472 | return true 473 | default: 474 | return false 475 | } 476 | } 477 | 478 | func isEventNotificationEnumType(fl validator.FieldLevel) bool { 479 | enum := fl.Field().String() 480 | switch enum { 481 | case "HardWiredNotification", "HardWiredMonitor", "PreconfiguredMonitor", "CustomMonitor": 482 | return true 483 | default: 484 | return false 485 | } 486 | } 487 | 488 | func isEventTriggerEnumType(fl validator.FieldLevel) bool { 489 | enum := fl.Field().String() 490 | switch enum { 491 | case "Alerting", "Delta", "Periodic": 492 | return true 493 | default: 494 | return false 495 | } 496 | } 497 | 498 | func isFirmwareStatusEnumType(fl validator.FieldLevel) bool { 499 | enum := fl.Field().String() 500 | cases := []string{ 501 | "Downloaded", 502 | "DownloadFailed", 503 | "Downloading", 504 | "DownloadScheduled", 505 | "DownloadPaused", 506 | "Idle", 507 | "InstallationFailed", 508 | "Installing", 509 | "Installed", 510 | "Installed", 511 | "InstallRebooting", 512 | "InstallScheduled", 513 | "InstallVerificationFailed", 514 | "InvalidSignature", 515 | "SignatureVerified", 516 | } 517 | return contains(cases, enum) 518 | } 519 | 520 | func isGenericDeviceModelStatusEnumType(fl validator.FieldLevel) bool { 521 | enum := fl.Field().String() 522 | switch enum { 523 | case "Accepted", "Rejected", "NotSupported", "EmptyResultSet": 524 | return true 525 | default: 526 | return false 527 | } 528 | } 529 | 530 | func isGenericStatusEnumType(fl validator.FieldLevel) bool { 531 | enum := fl.Field().String() 532 | switch enum { 533 | case "Accepted", "Rejected": 534 | return true 535 | default: 536 | return false 537 | } 538 | } 539 | 540 | func isGetCertificateIdUseEnumType(fl validator.FieldLevel) bool { 541 | enum := fl.Field().String() 542 | cases := []string{ 543 | "V2GRootCertificate", 544 | "MORootCertificate", 545 | "CSMSRootCertificate", 546 | "V2GCertificateChain", 547 | "ManufacturerRootCertificate", 548 | } 549 | return contains(cases, enum) 550 | } 551 | 552 | func isGetCertificateStatusEnumType(fl validator.FieldLevel) bool { 553 | enum := fl.Field().String() 554 | switch enum { 555 | case "Accepted", "Failed": 556 | return true 557 | default: 558 | return false 559 | } 560 | } 561 | 562 | func isGetChargingProfileStatusEnumType(fl validator.FieldLevel) bool { 563 | enum := fl.Field().String() 564 | switch enum { 565 | case "Accepted", "NoProfiles": 566 | return true 567 | default: 568 | return false 569 | } 570 | } 571 | 572 | func isGetDisplayMessagesStatusEnumType(fl validator.FieldLevel) bool { 573 | enum := fl.Field().String() 574 | switch enum { 575 | case "Accepted", "Unknown": 576 | return true 577 | default: 578 | return false 579 | } 580 | } 581 | 582 | func isGetInstalledCertificateStatusEnumType(fl validator.FieldLevel) bool { 583 | enum := fl.Field().String() 584 | switch enum { 585 | case "Accepted", "NotFound": 586 | return true 587 | default: 588 | return false 589 | } 590 | } 591 | 592 | func isGetVariableStatusEnumType(fl validator.FieldLevel) bool { 593 | enum := fl.Field().String() 594 | cases := []string{ 595 | "Accepted", 596 | "Rejected", 597 | "UnknownComponent", 598 | "UnknownVariable", 599 | "NotSupportedAttributeType", 600 | } 601 | return contains(cases, enum) 602 | } 603 | 604 | func isHashAlgorithmEnumType(fl validator.FieldLevel) bool { 605 | enum := fl.Field().String() 606 | switch enum { 607 | case "SHA256", "SHA384", "SHA512": 608 | return true 609 | default: 610 | return false 611 | } 612 | } 613 | 614 | func isIdTokenEnumType(fl validator.FieldLevel) bool { 615 | enum := fl.Field().String() 616 | cases := []string{ 617 | "Central", 618 | "eMAID", 619 | "ISO14443", 620 | "ISO15693", 621 | "KeyCode", 622 | "Local", 623 | "MacAddress", 624 | "NoAuthorization", 625 | } 626 | return contains(cases, enum) 627 | } 628 | 629 | func isInstallCertificateStatusEnumType(fl validator.FieldLevel) bool { 630 | enum := fl.Field().String() 631 | switch enum { 632 | case "Accepted", "Rejected", "Failed": 633 | return true 634 | default: 635 | return false 636 | } 637 | } 638 | 639 | func isInstallCertificateUseEnumType(fl validator.FieldLevel) bool { 640 | enum := fl.Field().String() 641 | switch enum { 642 | case "V2GRootCertificate", "MORootCertificate", "CSMSRootCertificate", "ManufacturerRootCertificate": 643 | return true 644 | default: 645 | return false 646 | } 647 | } 648 | 649 | func isIso15118EVCertificateStatusEnumType(fl validator.FieldLevel) bool { 650 | enum := fl.Field().String() 651 | switch enum { 652 | case "Accepted", "Failed": 653 | return true 654 | default: 655 | return false 656 | } 657 | } 658 | 659 | func isLocationEnumType(fl validator.FieldLevel) bool { 660 | enum := fl.Field().String() 661 | cases := []string{ 662 | "Body", 663 | "Cable", 664 | "EV", 665 | "Inlet", 666 | "Outlet", 667 | } 668 | return contains(cases, enum) 669 | } 670 | 671 | func isLogEnumType(fl validator.FieldLevel) bool { 672 | enum := fl.Field().String() 673 | switch enum { 674 | case "DiagnosticsLog", "SecurityLog": 675 | return true 676 | default: 677 | return false 678 | } 679 | } 680 | 681 | func isLogStatusEnumType(fl validator.FieldLevel) bool { 682 | enum := fl.Field().String() 683 | switch enum { 684 | case "Accepted", "Rejected", "AcceptedCancelled": 685 | return true 686 | default: 687 | return false 688 | } 689 | } 690 | 691 | func isMeasurandEnumType(fl validator.FieldLevel) bool { 692 | enum := fl.Field().String() 693 | cases := []string{ 694 | "Current.Export", 695 | "Current.Import", 696 | "Current.Offered", 697 | "Energy.Active.Export.Register", 698 | "Energy.Active.Import.Register", 699 | "Enerfy.Reactive.Export.Register", 700 | "Energy.Reactive.Import.Register", 701 | "Energy.Active.Export.Interval", 702 | "Energy.Active.Import.Interval", 703 | "Energy.Active.Net", 704 | "Energy.Reactive.Export.Interval", 705 | "Energy.Reactive.Import.Interval", 706 | "Energy.Reactive.Net", 707 | "Energy.Apparent.Net", 708 | "Energy.Apparent.Import", 709 | "Energy.Apparent.Export", 710 | "Frequncy", 711 | "Power.Active.Export", 712 | "Power.Active.Import", 713 | "Power.Factor", 714 | "Power.Offered", 715 | "Power.Reactive.Export", 716 | "Power.Reactive.Import", 717 | "SoC", 718 | "Voltage", 719 | } 720 | return contains(cases, enum) 721 | } 722 | 723 | func isMessageFormatEnumType(fl validator.FieldLevel) bool { 724 | enum := fl.Field().String() 725 | switch enum { 726 | case "ASCII", "HTML", "URI", "UTF8": 727 | return true 728 | default: 729 | return false 730 | } 731 | } 732 | 733 | func isMessagePriorityEnumType(fl validator.FieldLevel) bool { 734 | enum := fl.Field().String() 735 | switch enum { 736 | case "AlwaysFront", "InFront", "NormalCycle": 737 | return true 738 | default: 739 | return false 740 | } 741 | } 742 | 743 | func isMessageStateEnumType(fl validator.FieldLevel) bool { 744 | enum := fl.Field().String() 745 | switch enum { 746 | case "Charging", "Faulted", "Idle", "Unavailable": 747 | return true 748 | default: 749 | return false 750 | } 751 | } 752 | 753 | func isMessageTriggerEnumType(fl validator.FieldLevel) bool { 754 | enum := fl.Field().String() 755 | cases := []string{ 756 | "BootNotification", 757 | "LogStatusNotification", 758 | "FirmawareStatusNotification", 759 | "Heartbeat", 760 | "MeterValues", 761 | "SignChargingStationCertificate", 762 | "SignV2GCertificate", 763 | "StatusNotification", 764 | "TransactionEvent", 765 | "SignCombinedCertificate", 766 | "PublishFirmwareStatusNotification", 767 | } 768 | return contains(cases, enum) 769 | } 770 | 771 | func isMonitorEnumType(fl validator.FieldLevel) bool { 772 | enum := fl.Field().String() 773 | cases := []string{ 774 | "UpperThreshold", 775 | "LowerThreshold", 776 | "Delta", 777 | "Periodic", 778 | "PeriodicClockAligned", 779 | } 780 | return contains(cases, enum) 781 | } 782 | 783 | func isMonitoringBaseEnumType(fl validator.FieldLevel) bool { 784 | enum := fl.Field().String() 785 | switch enum { 786 | case "All", "FactoryDefault", "HardWiredOnly": 787 | return true 788 | default: 789 | return false 790 | } 791 | } 792 | 793 | func isMonitoringCriterionEnumType(fl validator.FieldLevel) bool { 794 | enum := fl.Field().String() 795 | switch enum { 796 | case "ThresholdMonitoring", "DeltaMonitoring", "PeriodicMonitoring": 797 | return true 798 | default: 799 | return false 800 | } 801 | } 802 | 803 | func isMutabilityEnumType(fl validator.FieldLevel) bool { 804 | enum := fl.Field().String() 805 | switch enum { 806 | case "ReadOnly", "WriteOnly", "ReadWrite": 807 | return true 808 | default: 809 | return false 810 | } 811 | } 812 | 813 | func isNotifyEVChargingNeedsStatusEnumType(fl validator.FieldLevel) bool { 814 | enum := fl.Field().String() 815 | switch enum { 816 | case "Accepted", "Rejected", "Processing": 817 | return true 818 | default: 819 | return false 820 | } 821 | } 822 | 823 | func isOCPPInterfaceEnumType(fl validator.FieldLevel) bool { 824 | enum := fl.Field().String() 825 | cases := []string{ 826 | "Wired0", 827 | "Wired1", 828 | "Wired2", 829 | "Wired3", 830 | "Wireless0", 831 | "Wireless1", 832 | "Wireless2", 833 | "Wireless3", 834 | } 835 | return contains(cases, enum) 836 | } 837 | 838 | func isOCPPTransportEnumType(fl validator.FieldLevel) bool { 839 | enum := fl.Field().String() 840 | switch enum { 841 | case "JSON", "SOAP": 842 | return true 843 | default: 844 | return false 845 | } 846 | } 847 | 848 | func isOCPPVersionEnumType(fl validator.FieldLevel) bool { 849 | enum := fl.Field().String() 850 | switch enum { 851 | case "OCPP12", "OCPP15", "OCPP16", "OCPP20": 852 | return true 853 | default: 854 | return false 855 | } 856 | } 857 | 858 | func isOperationalStatusEnumType(fl validator.FieldLevel) bool { 859 | enum := fl.Field().String() 860 | switch enum { 861 | case "InOperative", "Operative": 862 | return true 863 | default: 864 | return false 865 | } 866 | } 867 | 868 | func isPhaseEnumType(fl validator.FieldLevel) bool { 869 | enum := fl.Field().String() 870 | cases := []string{ 871 | "L1", 872 | "L2", 873 | "L3", 874 | "N", 875 | "L1-N", 876 | "L2-N", 877 | "L3-N", 878 | "L1-L2", 879 | "L2-L3", 880 | "L3-L1", 881 | } 882 | return contains(cases, enum) 883 | } 884 | 885 | func isPublishFirmwareStatusEnumType(fl validator.FieldLevel) bool { 886 | enum := fl.Field().String() 887 | cases := []string{ 888 | "Idle", 889 | "DownloadScheduled", 890 | "Downloading", 891 | "Downloaded", 892 | "Published", 893 | "DownloadFailed", 894 | "DownloadPaused", 895 | "InvalidChecksum", 896 | "ChecksumVerified", 897 | "PublishFailed", 898 | } 899 | return contains(cases, enum) 900 | } 901 | 902 | func isReadingContextEnumType(fl validator.FieldLevel) bool { 903 | enum := fl.Field().String() 904 | cases := []string{ 905 | "Interruption.Begin", 906 | "Interruption.End", 907 | "Other", 908 | "Sample.Clock", 909 | "Sample.Periodic", 910 | "Transaction.Begin", 911 | "Transaction.End", 912 | "Trigger", 913 | } 914 | return contains(cases, enum) 915 | } 916 | 917 | func isReasonEnumType(fl validator.FieldLevel) bool { 918 | enum := fl.Field().String() 919 | cases := []string{ 920 | "DeAuthorized", 921 | "EmergencyStop", 922 | "EnergyLimitReached", 923 | "EVDisconnected", 924 | "GroundFault", 925 | "ImmediateReset", 926 | "Local", 927 | "LocalOutOfCredit", 928 | "MeterPass", 929 | "Other", 930 | "OvercurrentFault", 931 | "PowerLoss", 932 | "PowerQuality", 933 | "Reboot", 934 | "Remote", 935 | "SOCLimitReached", 936 | "StoppedByEV", 937 | "TimeLimitReached", 938 | "Timeout", 939 | } 940 | return contains(cases, enum) 941 | } 942 | 943 | func isRecurrencyKindEnumType(fl validator.FieldLevel) bool { 944 | enum := fl.Field().String() 945 | switch enum { 946 | case "Daily", "Weekly": 947 | return true 948 | default: 949 | return false 950 | } 951 | } 952 | 953 | func isRegistrationStatusEnumType(fl validator.FieldLevel) bool { 954 | enum := fl.Field().String() 955 | switch enum { 956 | case "Accepted", "Pending", "Rejected": 957 | return true 958 | default: 959 | return false 960 | } 961 | } 962 | 963 | func isReportBaseEnumType(fl validator.FieldLevel) bool { 964 | enum := fl.Field().String() 965 | switch enum { 966 | case "ConfigurationInventory", "FullInventory", "SummaryInventory": 967 | return true 968 | default: 969 | return false 970 | } 971 | } 972 | 973 | func isRequestStartStopStatusEnumType(fl validator.FieldLevel) bool { 974 | enum := fl.Field().String() 975 | switch enum { 976 | case "Accepted", "Rejected": 977 | return true 978 | default: 979 | return false 980 | } 981 | } 982 | 983 | func isReservationUpdateStatusEnumType(fl validator.FieldLevel) bool { 984 | enum := fl.Field().String() 985 | switch enum { 986 | case "Expired", "Removed": 987 | return true 988 | default: 989 | return false 990 | } 991 | } 992 | 993 | func isReserveNowStatusEnumType(fl validator.FieldLevel) bool { 994 | enum := fl.Field().String() 995 | cases := []string{ 996 | "Accepted", 997 | "Faulted", 998 | "Occupied", 999 | "Rejected", 1000 | "Unavailable", 1001 | } 1002 | return contains(cases, enum) 1003 | } 1004 | 1005 | func isResetEnumType(fl validator.FieldLevel) bool { 1006 | enum := fl.Field().String() 1007 | switch enum { 1008 | case "Immediate", "OnIdle": 1009 | return true 1010 | default: 1011 | return false 1012 | } 1013 | } 1014 | 1015 | func isResetStatusEnumType(fl validator.FieldLevel) bool { 1016 | enum := fl.Field().String() 1017 | switch enum { 1018 | case "Accepted", "Rejected", "Scheduled": 1019 | return true 1020 | default: 1021 | return false 1022 | } 1023 | } 1024 | 1025 | func isSendLocalListStatusEnumType(fl validator.FieldLevel) bool { 1026 | enum := fl.Field().String() 1027 | switch enum { 1028 | case "Accepted", "Rejected", "VersionMismatch": 1029 | return true 1030 | default: 1031 | return false 1032 | } 1033 | } 1034 | 1035 | func isSetMonitoringStatusEnumType(fl validator.FieldLevel) bool { 1036 | enum := fl.Field().String() 1037 | cases := []string{ 1038 | "Accepted", 1039 | "UnkownComponent", 1040 | "UnkownVariable", 1041 | "UnsupportedMonitorType", 1042 | "Rejected", 1043 | "Duplicate", 1044 | } 1045 | return contains(cases, enum) 1046 | } 1047 | 1048 | func isSetNetworkProfileStatusEnumType(fl validator.FieldLevel) bool { 1049 | enum := fl.Field().String() 1050 | switch enum { 1051 | case "Accepted", "Rejected", "Failed": 1052 | return true 1053 | default: 1054 | return false 1055 | } 1056 | } 1057 | 1058 | func isSetVariableStatusEnumType(fl validator.FieldLevel) bool { 1059 | enum := fl.Field().String() 1060 | cases := []string{ 1061 | "Accepted", 1062 | "Rejected", 1063 | "UnkownComponent", 1064 | "UnkownVariable", 1065 | "NotSupportedAttributeType", 1066 | "RebootRequired", 1067 | } 1068 | return contains(cases, enum) 1069 | } 1070 | 1071 | func isTransactionEventEnumType(fl validator.FieldLevel) bool { 1072 | enum := fl.Field().String() 1073 | switch enum { 1074 | case "Ended", "Started", "Updated": 1075 | return true 1076 | default: 1077 | return false 1078 | } 1079 | } 1080 | 1081 | func isTriggerMessageStatusEnumType(fl validator.FieldLevel) bool { 1082 | enum := fl.Field().String() 1083 | switch enum { 1084 | case "Accepted", "Rejected", "NotImplemented": 1085 | return true 1086 | default: 1087 | return false 1088 | } 1089 | } 1090 | 1091 | func isTriggerReasonEnumType(fl validator.FieldLevel) bool { 1092 | enum := fl.Field().String() 1093 | cases := []string{ 1094 | "Authorized", 1095 | "CablePluggedIn", 1096 | "ChargingRateChanged", 1097 | "ChargingStateChanged", 1098 | "Deauthorized", 1099 | "EnergyLimitReached", 1100 | "EVCommunicationLost", 1101 | "EVConnectTimeout", 1102 | "MeterValueClock", 1103 | "MeterValuePeriodic", 1104 | "TimeLimitReached", 1105 | "Trigger", 1106 | "UnlockCommand", 1107 | "StopAuthorized", 1108 | "EVDeparted", 1109 | "EVDetected", 1110 | "RemoteStop", 1111 | "RemoteStart", 1112 | "AbnormalCondition", 1113 | "SignedDataReceived", 1114 | "ResetCommand", 1115 | } 1116 | return contains(cases, enum) 1117 | } 1118 | 1119 | func isUnlockStatusEnumType(fl validator.FieldLevel) bool { 1120 | enum := fl.Field().String() 1121 | switch enum { 1122 | case "Unlocked", "UnlockFailed", "OngoingAuthorizedTransaction", "UnkwownConnector": 1123 | return true 1124 | default: 1125 | return false 1126 | } 1127 | } 1128 | 1129 | func isUnpublishFirmwareStatusEnumType(fl validator.FieldLevel) bool { 1130 | enum := fl.Field().String() 1131 | switch enum { 1132 | case "DownloadOngoing", "NoFirmware", "Unpublished": 1133 | return true 1134 | default: 1135 | return false 1136 | } 1137 | } 1138 | 1139 | func isUpdateEnumType(fl validator.FieldLevel) bool { 1140 | enum := fl.Field().String() 1141 | switch enum { 1142 | case "Differential", "Full": 1143 | return true 1144 | default: 1145 | return false 1146 | } 1147 | } 1148 | 1149 | func isUpdateFirmwareStatusEnumType(fl validator.FieldLevel) bool { 1150 | enum := fl.Field().String() 1151 | cases := []string{ 1152 | "Accepted", 1153 | "Rejected", 1154 | "AcceptedCancelled", 1155 | "InvalidCertificate", 1156 | "RevokedCertificate", 1157 | } 1158 | return contains(cases, enum) 1159 | } 1160 | 1161 | func isUploadLogStatusEnumType(fl validator.FieldLevel) bool { 1162 | enum := fl.Field().String() 1163 | cases := []string{ 1164 | "BadMessage", 1165 | "Idle", 1166 | "NotSupportedOperation", 1167 | "PermissionDenied", 1168 | "Uploaded", 1169 | "UploadFailure", 1170 | "Uploading", 1171 | "AcceptedCancelled", 1172 | } 1173 | return contains(cases, enum) 1174 | } 1175 | 1176 | func isVPNEnumType(fl validator.FieldLevel) bool { 1177 | enum := fl.Field().String() 1178 | switch enum { 1179 | case "IKEv2", "IPSec", "L2TP", "PPTP": 1180 | return true 1181 | default: 1182 | return false 1183 | } 1184 | } 1185 | --------------------------------------------------------------------------------