diff --git a/manager/api/API.md b/manager/api/API.md index f246093..e050f12 100644 --- a/manager/api/API.md +++ b/manager/api/API.md @@ -271,6 +271,49 @@ This operation does not require authentication This operation does not require authentication +## setChargingProfile + + + +`POST /cs/{csId}/setchargingprofile` + +> Body parameter + +```json +{ + "" +} +``` + +

Parameters

+ +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|csId|path|string|true|The charge station identifier| +|body|body|[ChargineProfileType](#schemaChargingProfileType)|true|none| + +> Example responses + +> default Response + +```json +{ + "status": "string", + "error": "string" +} +``` + +

Responses

+ +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|OK|None| +|default|Default|Unexpected error|[Status](#schemastatus)| + + + ## setToken @@ -838,6 +881,46 @@ Trigger a charge station action |trigger|SignChargingStationCertificate| |trigger|SignCombinedCertificate| +

ChargingProfileType

+ + + + + + +```json + +{ + "id": int, + "stackLevel": int, + "chargingProfilePurpose": "ChargingProfilePurposeEnumType", + "chargingProfileKind": "ChargingProfileKindEnumType", + "recurrencyKind": "RecurrencyKindEnumType", + "validFrom": "dateTime", + "validTo": "dateTime", + "transactionId": "identifierString[0..36]", + "chargingSchedule": "ChargingScheduleType" +} + +``` + +Send charging profile to charging station + +### Properties ### +See OCPP 2.0.1 Specification for ChargingProfileType + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|id|int|true|none|none| +|stackLevel|int|true|none|none| +|chargingProfilePurpose|ChargingProfilePurposeEnumType|true|none|none| +|chargingProfileKind|ChargingProfileKindEnumType|true|none|none| +|recurrencyKind|RecurrencyKindEnumType|false|none|none| +|validFrom|dateTime|false|none|none| +|validTo|dateTime|false|none|none| +|transactionId|identifierString[0..36]|false|none|none| +|chargingSchedule|ChargingScheduleType|true|none|none| +

Token

diff --git a/manager/api/api.gen.go b/manager/api/api.gen.go index 6f8c987..f65beea 100644 --- a/manager/api/api.gen.go +++ b/manager/api/api.gen.go @@ -13,10 +13,12 @@ import ( "path" "strings" "time" + "golang.org/x/exp/slog" "github.com/deepmap/oapi-codegen/pkg/runtime" "github.com/getkin/kin-openapi/openapi3" "github.com/go-chi/chi/v5" + "github.com/thoughtworks/maeve-csms/manager/ocpp/ocpp201" ) // Defines values for ChargeStationInstallCertificatesCertificatesStatus. @@ -299,6 +301,8 @@ type Token struct { VisualNumber *string `json:"visualNumber,omitempty"` } +type ChargingProfileType ocpp201.ChargingProfileType + // TokenCacheMode Indicates what type of token caching is allowed type TokenCacheMode string @@ -361,6 +365,10 @@ type ServerInterface interface { // (POST /cs/{csId}/trigger) TriggerChargeStation(w http.ResponseWriter, r *http.Request, csId string) + + // (POST /cs/{csId}/setchargingprofile) + SetChargingProfile(w http.ResponseWriter, r *http.Request, csId string) + // Registers a location with the CSMS // (POST /location/{locationId}) RegisterLocation(w http.ResponseWriter, r *http.Request, locationId string) @@ -584,6 +592,33 @@ func (siw *ServerInterfaceWrapper) TriggerChargeStation(w http.ResponseWriter, r handler.ServeHTTP(w, r.WithContext(ctx)) } +// SetChargingProfile operation middleware +func (siw *ServerInterfaceWrapper) SetChargingProfile(w http.ResponseWriter, r *http.Request) { + slog.Debug("[API TRACE] in charging middleware!") + ctx := r.Context() + + var err error + + // ------------- Path parameter "csId" ------------- + var csId string + + err = runtime.BindStyledParameterWithLocation("simple", false, "csId", runtime.ParamLocationPath, chi.URLParam(r, "csId"), &csId) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "csId", Err: err}) + return + } + + var handler http.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.SetChargingProfile(w, r, csId) + }) + + for i := len(siw.HandlerMiddlewares) - 1; i >= 0; i-- { + handler = siw.HandlerMiddlewares[i](handler) + } + + handler.ServeHTTP(w, r.WithContext(ctx)) +} + // RegisterLocation operation middleware func (siw *ServerInterfaceWrapper) RegisterLocation(w http.ResponseWriter, r *http.Request) { ctx := r.Context() @@ -839,6 +874,9 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl r.Group(func(r chi.Router) { r.Post(options.BaseURL+"/cs/{csId}/trigger", wrapper.TriggerChargeStation) }) + r.Group(func(r chi.Router) { + r.Post(options.BaseURL+"/cs/{csId}/setchargingprofile", wrapper.SetChargingProfile) + }) r.Group(func(r chi.Router) { r.Post(options.BaseURL+"/location/{locationId}", wrapper.RegisterLocation) }) diff --git a/manager/api/render.go b/manager/api/render.go index 9a422e6..5481b5c 100644 --- a/manager/api/render.go +++ b/manager/api/render.go @@ -24,6 +24,10 @@ func (c ChargeStationTrigger) Bind(r *http.Request) error { return nil } +func (c ChargingProfileType) Bind(r *http.Request) error { + return nil +} + func (t Token) Bind(r *http.Request) error { return nil } diff --git a/manager/api/server.go b/manager/api/server.go index 2b2867d..fe55641 100644 --- a/manager/api/server.go +++ b/manager/api/server.go @@ -8,11 +8,14 @@ import ( "github.com/thoughtworks/maeve-csms/manager/ocpi" "net/http" "time" - + "context" "github.com/getkin/kin-openapi/openapi3" "github.com/go-chi/render" "github.com/thoughtworks/maeve-csms/manager/ocpp" + "github.com/thoughtworks/maeve-csms/manager/ocpp/ocpp201" "github.com/thoughtworks/maeve-csms/manager/store" + "github.com/thoughtworks/maeve-csms/manager/config" + "golang.org/x/exp/slog" "k8s.io/utils/clock" ) @@ -149,6 +152,7 @@ func (s *Server) LookupChargeStationAuth(w http.ResponseWriter, r *http.Request, } func (s *Server) TriggerChargeStation(w http.ResponseWriter, r *http.Request, csId string) { + slog.Debug("[API TRACE] in TriggerChargeStation!") req := new(ChargeStationTrigger) if err := render.Bind(r, req); err != nil { _ = render.Render(w, r, ErrInvalidRequest(err)) @@ -167,6 +171,32 @@ func (s *Server) TriggerChargeStation(w http.ResponseWriter, r *http.Request, cs w.WriteHeader(http.StatusCreated) } +func (s *Server) SetChargingProfile(w http.ResponseWriter, r *http.Request, csId string) { + slog.Debug("[API TRACE] In server.go, SetChargingProfile()") + req := new(ChargingProfileType) + + if err := render.Bind(r, req); err != nil { + _ = render.Render(w, r, ErrInvalidRequest(err)) + return + } + + slog.Debug("[API TRACE] req:", req) + + // Get the transport.Emitter so that we can send messages + cfg := config.DefaultConfig + settings, _ := config.Configure(context.Background(), &cfg) + + // Define the call maker + v201CallMaker := handlers.NewCallMaker(settings.MsgEmitter) + + request := ocpp201.SetChargingProfileRequestJson{EvseId: 0, ChargingProfile: ocpp201.ChargingProfileType(*req)} + + // Send the call + v201CallMaker.Send(context.Background(), csId, &request) + + w.WriteHeader(http.StatusCreated) +} + func (s *Server) SetToken(w http.ResponseWriter, r *http.Request) { req := new(Token) if err := render.Bind(r, req); err != nil { diff --git a/manager/handlers/call_maker.go b/manager/handlers/call_maker.go index f9e3982..de70bcf 100644 --- a/manager/handlers/call_maker.go +++ b/manager/handlers/call_maker.go @@ -5,7 +5,6 @@ package handlers import ( "context" "encoding/json" - "fmt" "github.com/google/uuid" "github.com/thoughtworks/maeve-csms/manager/ocpp" "github.com/thoughtworks/maeve-csms/manager/transport" @@ -22,8 +21,10 @@ type OcppCallMaker struct { func (b OcppCallMaker) Send(ctx context.Context, chargeStationId string, request ocpp.Request) error { action, ok := b.Actions[reflect.TypeOf(request)] + slog.Debug("[API TRACE] we are in Send() in call_maker.go", "action", action) if !ok { - return fmt.Errorf("unknown request type: %T", request) + slog.Error("unknown request type", request) + return nil } requestBytes, err := json.Marshal(request) diff --git a/manager/handlers/ocpp201/routing.go b/manager/handlers/ocpp201/routing.go index d4759f6..06b0d2f 100644 --- a/manager/handlers/ocpp201/routing.go +++ b/manager/handlers/ocpp201/routing.go @@ -306,6 +306,7 @@ func NewCallMaker(e transport.Emitter) *handlers.OcppCallMaker { reflect.TypeOf(&ocpp201.SetVariablesRequestJson{}): "SetVariables", reflect.TypeOf(&ocpp201.TriggerMessageRequestJson{}): "TriggerMessage", reflect.TypeOf(&ocpp201.UnlockConnectorRequestJson{}): "UnlockConnector", + reflect.TypeOf(&ocpp201.SetChargingProfileRequestJson{}): "SetChargingProfile", }, } } diff --git a/manager/handlers/ocpp201/set_charging_profile_result.go b/manager/handlers/ocpp201/set_charging_profile_result.go new file mode 100644 index 0000000..bb17b18 --- /dev/null +++ b/manager/handlers/ocpp201/set_charging_profile_result.go @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: Apache-2.0 + +package ocpp201 + +import ( + "context" + "github.com/thoughtworks/maeve-csms/manager/ocpp" + types "github.com/thoughtworks/maeve-csms/manager/ocpp/ocpp201" + "golang.org/x/exp/slog" +) + +type SetChargingProfileResultHandler struct{} + +func (h SetChargingProfileResultHandler) HandleCallResult(ctx context.Context, chargeStationId string, request ocpp.Request, response ocpp.Response, state any) error { + req := request.(*types.SetChargingProfileRequestJson) + resp := response.(*types.SetChargingProfileResponseJson) + + slog.Debug("[API TRACE] in scp_result.go, got response:", resp, "[API TRACE] From request:", req) + + return nil +} diff --git a/manager/handlers/router.go b/manager/handlers/router.go index c60fc71..50e23b9 100644 --- a/manager/handlers/router.go +++ b/manager/handlers/router.go @@ -29,6 +29,9 @@ func (r Router) Handle(ctx context.Context, chargeStationId string, msg *transpo span := trace.SpanFromContext(ctx) err := r.route(ctx, chargeStationId, msg) + + // TODO: Handle setChargingProfile response here if we want to do more on CSMS side! + if err != nil { slog.Error("unable to route message", slog.String("chargeStationId", chargeStationId), slog.String("action", msg.Action), "err", err) span.SetStatus(codes.Error, "routing request failed") @@ -54,9 +57,11 @@ func (r Router) Handle(ctx context.Context, chargeStationId string, msg *transpo } func (r Router) route(ctx context.Context, chargeStationId string, message *transport.Message) error { + slog.Debug("[API TRACE] we are in route() in router.go", "action", message.Action) switch message.MessageType { case transport.MessageTypeCall: route, ok := r.CallRoutes[message.Action] + slog.Debug("[API TRACE] we are in route() in router.go, in MessageTypeCall", "route", route) if !ok { return fmt.Errorf("routing request: %w", transport.NewError(transport.ErrorNotImplemented, fmt.Errorf("%s not implemented", message.Action))) } @@ -101,6 +106,7 @@ func (r Router) route(ctx context.Context, chargeStationId string, message *tran } case transport.MessageTypeCallResult: route, ok := r.CallResultRoutes[message.Action] + slog.Debug("[API TRACE] we are in route() in router.go, in MessageTypeCallResult", "route", route) if !ok { return fmt.Errorf("routing request: %w", transport.NewError(transport.ErrorNotImplemented, fmt.Errorf("%s result not implemented", message.Action))) } diff --git a/manager/ocpp/ocpp201/set_charging_profile_request.go b/manager/ocpp/ocpp201/set_charging_profile_request.go new file mode 100644 index 0000000..71bac3a --- /dev/null +++ b/manager/ocpp/ocpp201/set_charging_profile_request.go @@ -0,0 +1,8 @@ +package ocpp201 + +type SetChargingProfileRequestJson struct { + EvseId int `json:"evseId"` + ChargingProfile ChargingProfileType `json:"chargingProfile"` +} + +func (*SetChargingProfileRequestJson) IsRequest() {} diff --git a/manager/ocpp/ocpp201/set_charging_profile_response.go b/manager/ocpp/ocpp201/set_charging_profile_response.go new file mode 100644 index 0000000..af1ee97 --- /dev/null +++ b/manager/ocpp/ocpp201/set_charging_profile_response.go @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: Apache-2.0 + +package ocpp201 + +type ChargingProfileStatusEnumType string + +const ( + Accepted ChargingProfileStatusEnumType = "Accepted" + Rejected ChargingProfileStatusEnumType = "Rejected" +) + +type SetChargingProfileResponseJson struct { + Status ChargingProfileStatusEnumType `json:"status"` + StatusInfo StatusInfoType `json:"statusInfo"` +} + +func (*SetChargingProfileResponseJson) IsResponse() {} diff --git a/manager/sync/triggers.go b/manager/sync/triggers.go index 42939b4..64f5514 100644 --- a/manager/sync/triggers.go +++ b/manager/sync/triggers.go @@ -37,6 +37,7 @@ func SyncTriggers(ctx context.Context, trace.WithAttributes(attribute.String("sync.trigger.previous", previousChargeStationId))) defer span.End() triggerMessages, err := engine.ListChargeStationTriggerMessages(ctx, 50, previousChargeStationId) + slog.Debug("[API TRACE] we are in SyncTriggers() in triggers.go", "triggerMessages", triggerMessages) if err != nil { span.RecordError(err) return