Skip to content
This repository was archived by the owner on Jun 29, 2024. It is now read-only.

Commit 27b9b62

Browse files
committed
Add write approval API to UCLPCServer
1 parent db87fb3 commit 27b9b62

File tree

6 files changed

+195
-2
lines changed

6 files changed

+195
-2
lines changed

uclpcserver/api.go

+12
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"time"
55

66
"github.com/enbility/cemd/api"
7+
"github.com/enbility/spine-go/model"
78
)
89

910
//go:generate mockery
@@ -27,6 +28,17 @@ type UCLPCServerInterface interface {
2728
// set the current loadcontrol limit data
2829
SetConsumptionLimit(limit api.LoadLimit) (resultErr error)
2930

31+
// return the currently pending incoming consumption write limits
32+
PendingConsumptionLimits() map[model.MsgCounterType]api.LoadLimit
33+
34+
// accept or deny an incoming consumption write limit
35+
//
36+
// parameters:
37+
// - msg: the incoming write message
38+
// - approve: if the write limit for msg should be approved or not
39+
// - reason: the reason why the approval is denied, otherwise an empty string
40+
ApproveOrDenyConsumptionLimit(msgCounter model.MsgCounterType, approve bool, reason string)
41+
3042
// Scenario 2
3143

3244
// return Failsafe limit for the consumed active (real) power of the

uclpcserver/public.go

+71
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,77 @@ func (e *UCLPCServer) SetConsumptionLimit(limit api.LoadLimit) (resultErr error)
103103
return nil
104104
}
105105

106+
// return the currently pending incoming consumption write limits
107+
func (e *UCLPCServer) PendingConsumptionLimits() map[model.MsgCounterType]api.LoadLimit {
108+
e.pendingMux.Lock()
109+
defer e.pendingMux.Unlock()
110+
111+
result := make(map[model.MsgCounterType]api.LoadLimit)
112+
113+
descriptions := util.GetLocalLimitDescriptionsForTypeCategoryDirectionScope(
114+
e.service,
115+
model.LoadControlLimitTypeTypeSignDependentAbsValueLimit,
116+
model.LoadControlCategoryTypeObligation,
117+
model.EnergyDirectionTypeConsume,
118+
model.ScopeTypeTypeActivePowerLimit,
119+
)
120+
if len(descriptions) != 1 || descriptions[0].LimitId == nil {
121+
return result
122+
}
123+
description := descriptions[0]
124+
125+
for key, msg := range e.pendingLimits {
126+
data := msg.Cmd.LoadControlLimitListData
127+
128+
if data == nil || data.LoadControlLimitData == nil || len(data.LoadControlLimitData) == 0 {
129+
continue
130+
}
131+
132+
// we assume there is always only one limit
133+
element := data.LoadControlLimitData[0]
134+
135+
if description.LimitId == nil || element.LimitId == nil || *description.LimitId != *element.LimitId {
136+
continue
137+
}
138+
139+
limit := api.LoadLimit{}
140+
141+
if element.TimePeriod != nil {
142+
if duration, err := element.TimePeriod.GetDuration(); err == nil {
143+
limit.Duration = duration
144+
}
145+
}
146+
147+
if element.IsLimitActive != nil {
148+
limit.IsActive = *element.IsLimitActive
149+
}
150+
151+
if element.Value != nil {
152+
limit.Value = element.Value.GetValue()
153+
}
154+
155+
result[key] = limit
156+
}
157+
158+
return result
159+
}
160+
161+
// accept or deny an incoming consumption write limit
162+
func (e *UCLPCServer) ApproveOrDenyConsumptionLimit(msgCounter model.MsgCounterType, approve bool, reason string) {
163+
e.pendingMux.Lock()
164+
defer e.pendingMux.Unlock()
165+
166+
msg, ok := e.pendingLimits[msgCounter]
167+
if !ok {
168+
return
169+
}
170+
171+
localEntity := e.service.LocalDevice().EntityForType(model.EntityTypeTypeCEM)
172+
173+
f := localEntity.FeatureOfTypeAndRole(model.FeatureTypeTypeLoadControl, model.RoleTypeServer)
174+
f.ApproveOrDenyWrite(msg, approve, reason)
175+
}
176+
106177
// Scenario 2
107178

108179
// return Failsafe limit for the consumed active (real) power of the

uclpcserver/public_test.go

+70
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ import (
44
"time"
55

66
"github.com/enbility/cemd/api"
7+
eebusutil "github.com/enbility/eebus-go/util"
8+
spineapi "github.com/enbility/spine-go/api"
9+
"github.com/enbility/spine-go/model"
710
"github.com/stretchr/testify/assert"
811
)
912

@@ -26,6 +29,73 @@ func (s *UCLPCServerSuite) Test_ConsumptionLimit() {
2629
assert.Nil(s.T(), err)
2730
}
2831

32+
func (s *UCLPCServerSuite) Test_PendingConsumptionLimits() {
33+
data := s.sut.PendingConsumptionLimits()
34+
assert.Equal(s.T(), 0, len(data))
35+
36+
msgCounter := model.MsgCounterType(500)
37+
38+
msg := &spineapi.Message{
39+
RequestHeader: &model.HeaderType{
40+
MsgCounter: eebusutil.Ptr(msgCounter),
41+
},
42+
Cmd: model.CmdType{
43+
LoadControlLimitListData: &model.LoadControlLimitListDataType{},
44+
},
45+
}
46+
47+
s.sut.loadControlWriteCB(msg)
48+
49+
data = s.sut.PendingConsumptionLimits()
50+
assert.Equal(s.T(), 0, len(data))
51+
52+
msg.Cmd = model.CmdType{
53+
LoadControlLimitListData: &model.LoadControlLimitListDataType{
54+
LoadControlLimitData: []model.LoadControlLimitDataType{},
55+
},
56+
}
57+
58+
s.sut.loadControlWriteCB(msg)
59+
60+
data = s.sut.PendingConsumptionLimits()
61+
assert.Equal(s.T(), 0, len(data))
62+
63+
msg.Cmd = model.CmdType{
64+
LoadControlLimitListData: &model.LoadControlLimitListDataType{
65+
LoadControlLimitData: []model.LoadControlLimitDataType{
66+
{},
67+
},
68+
},
69+
}
70+
71+
s.sut.loadControlWriteCB(msg)
72+
73+
data = s.sut.PendingConsumptionLimits()
74+
assert.Equal(s.T(), 0, len(data))
75+
76+
msg.Cmd = model.CmdType{
77+
LoadControlLimitListData: &model.LoadControlLimitListDataType{
78+
LoadControlLimitData: []model.LoadControlLimitDataType{
79+
{
80+
LimitId: eebusutil.Ptr(model.LoadControlLimitIdType(0)),
81+
IsLimitActive: eebusutil.Ptr(true),
82+
Value: model.NewScaledNumberType(1000),
83+
TimePeriod: model.NewTimePeriodTypeWithRelativeEndTime(time.Minute * 2),
84+
},
85+
},
86+
},
87+
}
88+
89+
s.sut.loadControlWriteCB(msg)
90+
91+
data = s.sut.PendingConsumptionLimits()
92+
assert.Equal(s.T(), 1, len(data))
93+
94+
s.sut.ApproveOrDenyConsumptionLimit(model.MsgCounterType(499), true, "")
95+
96+
s.sut.ApproveOrDenyConsumptionLimit(msgCounter, true, "")
97+
}
98+
2999
func (s *UCLPCServerSuite) Test_Failsafe() {
30100
limit, changeable, err := s.sut.FailsafeConsumptionActivePowerLimit()
31101
assert.Equal(s.T(), 0.0, limit)

uclpcserver/types.go

+5
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@ const (
1212
// Use Case LPC, Scenario 1
1313
DataUpdateLimit api.EventType = "DataUpdateLimit"
1414

15+
// An incoming load control obligation limit needs to be approved or denied
16+
//
17+
// Use Case LPC, Scenario 1
18+
WriteApprovalRequired api.EventType = "WriteApprovalRequired"
19+
1520
// Failsafe limit for the consumed active (real) power of the
1621
// Controllable System data update received
1722
//

uclpcserver/uclpc.go

+22-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package uclpcserver
22

33
import (
4+
"sync"
5+
46
"github.com/enbility/cemd/api"
57
"github.com/enbility/cemd/util"
68
eebusapi "github.com/enbility/eebus-go/api"
@@ -17,15 +19,19 @@ type UCLPCServer struct {
1719

1820
validEntityTypes []model.EntityTypeType
1921

22+
pendingMux sync.Mutex
23+
pendingLimits map[model.MsgCounterType]*spineapi.Message
24+
2025
heartbeatKeoWorkaround bool // required because KEO Stack uses multiple identical entities for the same functionality, and it is not clear which to use
2126
}
2227

2328
var _ UCLPCServerInterface = (*UCLPCServer)(nil)
2429

2530
func NewUCLPC(service eebusapi.ServiceInterface, eventCB api.EntityEventCallback) *UCLPCServer {
2631
uc := &UCLPCServer{
27-
service: service,
28-
eventCB: eventCB,
32+
service: service,
33+
eventCB: eventCB,
34+
pendingLimits: make(map[model.MsgCounterType]*spineapi.Message),
2935
}
3036

3137
uc.validEntityTypes = []model.EntityTypeType{
@@ -42,6 +48,19 @@ func (c *UCLPCServer) UseCaseName() model.UseCaseNameType {
4248
return model.UseCaseNameTypeLimitationOfPowerConsumption
4349
}
4450

51+
func (e *UCLPCServer) loadControlWriteCB(msg *spineapi.Message) {
52+
e.pendingMux.Lock()
53+
defer e.pendingMux.Unlock()
54+
55+
if msg.RequestHeader == nil || msg.RequestHeader.MsgCounter == nil {
56+
return
57+
}
58+
59+
if _, ok := e.pendingLimits[*msg.RequestHeader.MsgCounter]; !ok {
60+
e.pendingLimits[*msg.RequestHeader.MsgCounter] = msg
61+
}
62+
}
63+
4564
func (e *UCLPCServer) AddFeatures() {
4665
localEntity := e.service.LocalDevice().EntityForType(model.EntityTypeTypeCEM)
4766

@@ -52,6 +71,7 @@ func (e *UCLPCServer) AddFeatures() {
5271
f := localEntity.GetOrAddFeature(model.FeatureTypeTypeLoadControl, model.RoleTypeServer)
5372
f.AddFunctionType(model.FunctionTypeLoadControlLimitDescriptionListData, true, false)
5473
f.AddFunctionType(model.FunctionTypeLoadControlLimitListData, true, true)
74+
_ = f.AddWriteApprovalCallback(e.loadControlWriteCB)
5575

5676
var limitId model.LoadControlLimitIdType = 0
5777
// get the highest limitId

uclpcserver/uclpc_test.go

+15
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,25 @@ package uclpcserver
22

33
import (
44
eebusutil "github.com/enbility/eebus-go/util"
5+
spineapi "github.com/enbility/spine-go/api"
56
"github.com/enbility/spine-go/model"
67
"github.com/stretchr/testify/assert"
78
)
89

10+
func (s *UCLPCServerSuite) Test_loadControlWriteCB() {
11+
msg := &spineapi.Message{}
12+
13+
s.sut.loadControlWriteCB(msg)
14+
15+
msg = &spineapi.Message{
16+
RequestHeader: &model.HeaderType{
17+
MsgCounter: eebusutil.Ptr(model.MsgCounterType(500)),
18+
},
19+
}
20+
21+
s.sut.loadControlWriteCB(msg)
22+
}
23+
924
func (s *UCLPCServerSuite) Test_UpdateUseCaseAvailability() {
1025
s.sut.UpdateUseCaseAvailability(true)
1126
}

0 commit comments

Comments
 (0)