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