Skip to content

Commit aa7c62e

Browse files
LPC & LPP usecase improvements (#86)
- Controllable System LPC & LPP - If a write approval request is not for the usecase, approve it - clean up pending write approval list - Energy Guard LPC & LPP - send read request for heartbeats - allow remote CEM entity - Demo controlbox improvements - also supports LPP, but only sends a LPC limit - Demo HEMS improvements - support LPC & LPP as Energy guard and controllable system - comment SPINE message logging
2 parents 8e9c451 + 50b769c commit aa7c62e

File tree

10 files changed

+162
-25
lines changed

10 files changed

+162
-25
lines changed

cmd/controlbox/main.go

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818
"github.com/enbility/eebus-go/service"
1919
ucapi "github.com/enbility/eebus-go/usecases/api"
2020
"github.com/enbility/eebus-go/usecases/eg/lpc"
21+
"github.com/enbility/eebus-go/usecases/eg/lpp"
2122
shipapi "github.com/enbility/ship-go/api"
2223
"github.com/enbility/ship-go/cert"
2324
spineapi "github.com/enbility/spine-go/api"
@@ -30,6 +31,7 @@ type controlbox struct {
3031
myService *service.Service
3132

3233
uclpc ucapi.EgLPCInterface
34+
uclpp ucapi.EgLPPInterface
3335

3436
isConnected bool
3537
}
@@ -94,6 +96,9 @@ func (h *controlbox) run() {
9496
h.uclpc = lpc.NewLPC(localEntity, h.OnLPCEvent)
9597
h.myService.AddUseCase(h.uclpc)
9698

99+
h.uclpp = lpp.NewLPP(localEntity, h.OnLPPEvent)
100+
h.myService.AddUseCase(h.uclpp)
101+
97102
if len(remoteSki) == 0 {
98103
os.Exit(0)
99104
}
@@ -154,7 +159,7 @@ func (h *controlbox) sendLimit(entity spineapi.EntityRemoteInterface) {
154159
if *msg.ErrorNumber == model.ErrorNumberTypeNoError {
155160
fmt.Println("Limit accepted.")
156161
} else {
157-
fmt.Println("Limit rejected. Code", msg.ErrorNumber, "Description", msg.Description)
162+
fmt.Println("Limit rejected. Code", *msg.ErrorNumber, "Description", *msg.Description)
158163
}
159164
}
160165
msgCounter, err := h.uclpc.WriteConsumptionLimit(entity, limit, resultCB)
@@ -165,7 +170,6 @@ func (h *controlbox) sendLimit(entity spineapi.EntityRemoteInterface) {
165170
fmt.Println("Sent limit to", entity.Device().Ski(), "with msgCounter", msgCounter)
166171
})
167172
}
168-
169173
func (h *controlbox) OnLPCEvent(ski string, device spineapi.DeviceRemoteInterface, entity spineapi.EntityRemoteInterface, event api.EventType) {
170174
if !h.isConnected {
171175
return
@@ -183,6 +187,23 @@ func (h *controlbox) OnLPCEvent(ski string, device spineapi.DeviceRemoteInterfac
183187
}
184188
}
185189

190+
func (h *controlbox) OnLPPEvent(ski string, device spineapi.DeviceRemoteInterface, entity spineapi.EntityRemoteInterface, event api.EventType) {
191+
if !h.isConnected {
192+
return
193+
}
194+
195+
switch event {
196+
case lpc.UseCaseSupportUpdate:
197+
h.sendLimit(entity)
198+
case lpc.DataUpdateLimit:
199+
if currentLimit, err := h.uclpc.ConsumptionLimit(entity); err == nil {
200+
fmt.Println("New Limit received", currentLimit.Value, "W")
201+
}
202+
default:
203+
return
204+
}
205+
}
206+
186207
// main app
187208
func usage() {
188209
fmt.Println("First Run:")

cmd/hems/main.go

Lines changed: 71 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,27 @@ import (
1515

1616
"github.com/enbility/eebus-go/api"
1717
"github.com/enbility/eebus-go/service"
18-
"github.com/enbility/eebus-go/usecases/eg/lpc"
18+
ucapi "github.com/enbility/eebus-go/usecases/api"
19+
"github.com/enbility/eebus-go/usecases/cs/lpc"
20+
cslpc "github.com/enbility/eebus-go/usecases/cs/lpc"
21+
cslpp "github.com/enbility/eebus-go/usecases/cs/lpp"
22+
eglpc "github.com/enbility/eebus-go/usecases/eg/lpc"
23+
eglpp "github.com/enbility/eebus-go/usecases/eg/lpp"
1924
shipapi "github.com/enbility/ship-go/api"
2025
"github.com/enbility/ship-go/cert"
26+
spineapi "github.com/enbility/spine-go/api"
2127
"github.com/enbility/spine-go/model"
2228
)
2329

2430
var remoteSki string
2531

2632
type hems struct {
2733
myService *service.Service
34+
35+
uccslpc ucapi.CsLPCInterface
36+
uccslpp ucapi.CsLPPInterface
37+
uceglpc ucapi.EgLPCInterface
38+
uceglpp ucapi.EgLPPInterface
2839
}
2940

3041
func (h *hems) run() {
@@ -84,8 +95,14 @@ func (h *hems) run() {
8495
}
8596

8697
localEntity := h.myService.LocalDevice().EntityForType(model.EntityTypeTypeCEM)
87-
uclpc := lpc.NewLPC(localEntity, nil)
88-
h.myService.AddUseCase(uclpc)
98+
h.uccslpc = cslpc.NewLPC(localEntity, h.OnLPCEvent)
99+
h.myService.AddUseCase(h.uccslpc)
100+
h.uccslpp = cslpp.NewLPP(localEntity, h.OnLPPEvent)
101+
h.myService.AddUseCase(h.uccslpp)
102+
h.uceglpc = eglpc.NewLPC(localEntity, nil)
103+
h.myService.AddUseCase(h.uceglpc)
104+
h.uceglpp = eglpp.NewLPP(localEntity, nil)
105+
h.myService.AddUseCase(h.uceglpp)
89106

90107
if len(remoteSki) == 0 {
91108
os.Exit(0)
@@ -97,6 +114,54 @@ func (h *hems) run() {
97114
// defer h.myService.Shutdown()
98115
}
99116

117+
// Controllable System LPC Event Handler
118+
119+
func (h *hems) OnLPCEvent(ski string, device spineapi.DeviceRemoteInterface, entity spineapi.EntityRemoteInterface, event api.EventType) {
120+
if entity.EntityType() != model.EntityTypeTypeGridGuard {
121+
return
122+
}
123+
124+
switch event {
125+
case lpc.WriteApprovalRequired:
126+
// get pending writes
127+
pendingWrites := h.uccslpc.PendingConsumptionLimits()
128+
129+
// approve any write
130+
for msgCounter, write := range pendingWrites {
131+
fmt.Println("Approving LPC write with msgCounter", msgCounter, "and limit", write.Value, "W")
132+
h.uccslpc.ApproveOrDenyConsumptionLimit(msgCounter, true, "")
133+
}
134+
case lpc.DataUpdateLimit:
135+
if currentLimit, err := h.uccslpc.ConsumptionLimit(); err != nil {
136+
fmt.Println("New LPC Limit set to", currentLimit.Value, "W")
137+
}
138+
}
139+
}
140+
141+
// Controllable System LPP Event Handler
142+
143+
func (h *hems) OnLPPEvent(ski string, device spineapi.DeviceRemoteInterface, entity spineapi.EntityRemoteInterface, event api.EventType) {
144+
if entity.EntityType() != model.EntityTypeTypeGridGuard {
145+
return
146+
}
147+
148+
switch event {
149+
case lpc.WriteApprovalRequired:
150+
// get pending writes
151+
pendingWrites := h.uccslpp.PendingProductionLimits()
152+
153+
// approve any write
154+
for msgCounter, write := range pendingWrites {
155+
fmt.Println("Approving LPP write with msgCounter", msgCounter, "and limit", write.Value, "W")
156+
h.uccslpp.ApproveOrDenyProductionLimit(msgCounter, true, "")
157+
}
158+
case lpc.DataUpdateLimit:
159+
if currentLimit, err := h.uccslpp.ProductionLimit(); err != nil {
160+
fmt.Println("New LPP Limit set to", currentLimit.Value, "W")
161+
}
162+
}
163+
}
164+
100165
// EEBUSServiceHandler
101166

102167
func (h *hems) RemoteSKIConnected(service api.ServiceInterface, ski string) {}
@@ -158,22 +223,18 @@ func main() {
158223

159224
func (h *hems) Trace(args ...interface{}) {
160225
// h.print("TRACE", args...)
161-
h.print("TRACE", args...)
162226
}
163227

164228
func (h *hems) Tracef(format string, args ...interface{}) {
165-
// h.print("TRACE", args...)
166-
h.printFormat("TRACE", format, args...)
229+
// h.printFormat("TRACE", format, args...)
167230
}
168231

169232
func (h *hems) Debug(args ...interface{}) {
170-
// h.print("TRACE", args...)
171-
h.print("DEBUG", args...)
233+
// h.print("DEBUG", args...)
172234
}
173235

174236
func (h *hems) Debugf(format string, args ...interface{}) {
175-
// h.print("TRACE", args...)
176-
h.printFormat("DEBUG", format, args...)
237+
// h.printFormat("DEBUG", format, args...)
177238
}
178239

179240
func (h *hems) Info(args ...interface{}) {

usecases/cs/lpc/public.go

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"github.com/enbility/eebus-go/api"
88
"github.com/enbility/eebus-go/features/server"
99
ucapi "github.com/enbility/eebus-go/usecases/api"
10+
spineapi "github.com/enbility/spine-go/api"
1011
"github.com/enbility/spine-go/model"
1112
"github.com/enbility/spine-go/util"
1213
)
@@ -94,7 +95,7 @@ func (e *LPC) PendingConsumptionLimits() map[model.MsgCounterType]ucapi.LoadLimi
9495
data := msg.Cmd.LoadControlLimitListData
9596

9697
// elements are only added to the map if all required fields exist
97-
// therefor not check for these are needed here
98+
// therefor no checks for these are needed here
9899

99100
// find the item which contains the limit for this usecase
100101
for _, item := range data.LoadControlLimitData {
@@ -133,21 +134,31 @@ func (e *LPC) ApproveOrDenyConsumptionLimit(msgCounter model.MsgCounterType, app
133134
e.pendingMux.Lock()
134135
defer e.pendingMux.Unlock()
135136

136-
msg, ok := e.pendingLimits[msgCounter]
137-
if !ok {
138-
return
139-
}
140-
141137
f := e.LocalEntity.FeatureOfTypeAndRole(model.FeatureTypeTypeLoadControl, model.RoleTypeServer)
142138

143139
result := model.ErrorType{
144140
ErrorNumber: model.ErrorNumberType(0),
145141
}
142+
143+
msg, ok := e.pendingLimits[msgCounter]
144+
if !ok {
145+
// it is not a limit of this usecase, so approve it
146+
newMsg := &spineapi.Message{
147+
RequestHeader: &model.HeaderType{
148+
MsgCounter: &msgCounter,
149+
},
150+
}
151+
f.ApproveOrDenyWrite(newMsg, result)
152+
return
153+
}
154+
146155
if !approve {
147156
result.ErrorNumber = model.ErrorNumberType(7)
148157
result.Description = util.Ptr(model.DescriptionType(reason))
149158
}
150159
f.ApproveOrDenyWrite(msg, result)
160+
161+
delete(e.pendingLimits, msgCounter)
151162
}
152163

153164
// Scenario 2

usecases/cs/lpc/public_test.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,12 @@ func (s *CsLPCSuite) Test_PendingConsumptionLimits() {
4949
Value: model.NewScaledNumberType(1000),
5050
TimePeriod: model.NewTimePeriodTypeWithRelativeEndTime(time.Minute * 2),
5151
},
52+
{
53+
LimitId: util.Ptr(model.LoadControlLimitIdType(1)),
54+
IsLimitActive: util.Ptr(true),
55+
Value: model.NewScaledNumberType(1000),
56+
TimePeriod: model.NewTimePeriodTypeWithRelativeEndTime(time.Minute * 2),
57+
},
5258
},
5359
},
5460
},
@@ -63,7 +69,13 @@ func (s *CsLPCSuite) Test_PendingConsumptionLimits() {
6369

6470
s.sut.ApproveOrDenyConsumptionLimit(model.MsgCounterType(499), true, "")
6571

72+
data = s.sut.PendingConsumptionLimits()
73+
assert.Equal(s.T(), 1, len(data))
74+
6675
s.sut.ApproveOrDenyConsumptionLimit(msgCounter, false, "leave me alone")
76+
77+
data = s.sut.PendingConsumptionLimits()
78+
assert.Equal(s.T(), 0, len(data))
6779
}
6880

6981
func (s *CsLPCSuite) Test_Failsafe() {

usecases/cs/lpp/public.go

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"github.com/enbility/eebus-go/api"
88
"github.com/enbility/eebus-go/features/server"
99
ucapi "github.com/enbility/eebus-go/usecases/api"
10+
spineapi "github.com/enbility/spine-go/api"
1011
"github.com/enbility/spine-go/model"
1112
"github.com/enbility/spine-go/util"
1213
)
@@ -94,7 +95,7 @@ func (e *LPP) PendingProductionLimits() map[model.MsgCounterType]ucapi.LoadLimit
9495
data := msg.Cmd.LoadControlLimitListData
9596

9697
// elements are only added to the map if all required fields exist
97-
// therefor not check for these are needed here
98+
// therefor no checks for these are needed here
9899

99100
// find the item which contains the limit for this usecase
100101
for _, item := range data.LoadControlLimitData {
@@ -133,21 +134,31 @@ func (e *LPP) ApproveOrDenyProductionLimit(msgCounter model.MsgCounterType, appr
133134
e.pendingMux.Lock()
134135
defer e.pendingMux.Unlock()
135136

136-
msg, ok := e.pendingLimits[msgCounter]
137-
if !ok {
138-
return
139-
}
140-
141137
f := e.LocalEntity.FeatureOfTypeAndRole(model.FeatureTypeTypeLoadControl, model.RoleTypeServer)
142138

143139
result := model.ErrorType{
144140
ErrorNumber: model.ErrorNumberType(0),
145141
}
142+
143+
msg, ok := e.pendingLimits[msgCounter]
144+
if !ok {
145+
// it is not a limit of this usecase, so approve it
146+
newMsg := &spineapi.Message{
147+
RequestHeader: &model.HeaderType{
148+
MsgCounter: &msgCounter,
149+
},
150+
}
151+
f.ApproveOrDenyWrite(newMsg, result)
152+
return
153+
}
154+
146155
if !approve {
147156
result.ErrorNumber = model.ErrorNumberType(7)
148157
result.Description = util.Ptr(model.DescriptionType(reason))
149158
}
150159
f.ApproveOrDenyWrite(msg, result)
160+
161+
delete(e.pendingLimits, msgCounter)
151162
}
152163

153164
// Scenario 2

usecases/cs/lpp/public_test.go

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,12 @@ func (s *CsLPPSuite) Test_PendingProductionLimits() {
4949
Value: model.NewScaledNumberType(1000),
5050
TimePeriod: model.NewTimePeriodTypeWithRelativeEndTime(time.Minute * 2),
5151
},
52-
},
52+
{
53+
LimitId: util.Ptr(model.LoadControlLimitIdType(1)),
54+
IsLimitActive: util.Ptr(true),
55+
Value: model.NewScaledNumberType(1000),
56+
TimePeriod: model.NewTimePeriodTypeWithRelativeEndTime(time.Minute * 2),
57+
}},
5358
},
5459
},
5560
DeviceRemote: s.remoteDevice,
@@ -63,7 +68,13 @@ func (s *CsLPPSuite) Test_PendingProductionLimits() {
6368

6469
s.sut.ApproveOrDenyProductionLimit(model.MsgCounterType(499), true, "")
6570

71+
data = s.sut.PendingProductionLimits()
72+
assert.Equal(s.T(), 1, len(data))
73+
6674
s.sut.ApproveOrDenyProductionLimit(msgCounter, false, "leave me alone")
75+
76+
data = s.sut.PendingProductionLimits()
77+
assert.Equal(s.T(), 0, len(data))
6778
}
6879

6980
func (s *CsLPPSuite) Test_Failsafe() {

usecases/eg/lpc/events.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,10 @@ func (e *LPC) connected(entity spineapi.EntityRemoteInterface) {
8282
if _, err := deviceDiagnosis.Subscribe(); err != nil {
8383
logging.Log().Debug(err)
8484
}
85+
86+
if _, err := deviceDiagnosis.RequestHeartbeat(); err != nil {
87+
logging.Log().Debug(err)
88+
}
8589
}
8690
}
8791

usecases/eg/lpc/usecase.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ var _ ucapi.EgLPCInterface = (*LPC)(nil)
1818
func NewLPC(localEntity spineapi.EntityLocalInterface, eventCB api.EntityEventCallback) *LPC {
1919
validActorTypes := []model.UseCaseActorType{model.UseCaseActorTypeControllableSystem}
2020
validEntityTypes := []model.EntityTypeType{
21+
model.EntityTypeTypeCEM,
2122
model.EntityTypeTypeCompressor,
2223
model.EntityTypeTypeEVSE,
2324
model.EntityTypeTypeHeatPumpAppliance,

usecases/eg/lpp/events.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,10 @@ func (e *LPP) connected(entity spineapi.EntityRemoteInterface) {
8383
if _, err := deviceDiagnosis.Subscribe(); err != nil {
8484
logging.Log().Debug(err)
8585
}
86+
87+
if _, err := deviceDiagnosis.RequestHeartbeat(); err != nil {
88+
logging.Log().Debug(err)
89+
}
8690
}
8791
}
8892

usecases/eg/lpp/usecase.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ var _ ucapi.EgLPPInterface = (*LPP)(nil)
1818
func NewLPP(localEntity spineapi.EntityLocalInterface, eventCB api.EntityEventCallback) *LPP {
1919
validActorTypes := []model.UseCaseActorType{model.UseCaseActorTypeControllableSystem}
2020
validEntityTypes := []model.EntityTypeType{
21+
model.EntityTypeTypeCEM,
2122
model.EntityTypeTypeEVSE,
2223
model.EntityTypeTypeInverter,
2324
model.EntityTypeTypeSmartEnergyAppliance,

0 commit comments

Comments
 (0)