Skip to content

Commit 378492c

Browse files
committed
get and store all configs of the station (instead of just CpoName)
we currently GetConfig from stations (after station connecting) for the CpoName key and store it in database. we can generalize this flow to get all config keys/values from stations and store them in database.
1 parent 9d3104e commit 378492c

12 files changed

Lines changed: 215 additions & 66 deletions

File tree

pom.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -399,6 +399,10 @@
399399
<name>JSON</name>
400400
<includeExpression>.*\.WEB_USER\.(AUTHORITIES)</includeExpression>
401401
</forcedType>
402+
<forcedType>
403+
<name>JSON</name>
404+
<includeExpression>.*\.CHARGE_BOX\.(OCPP_CONFIGURATION)</includeExpression>
405+
</forcedType>
402406
<forcedType>
403407
<userType>org.joda.time.DateTime</userType>
404408
<converter>de.rwth.idsg.steve.utils.DateTimeConverter</converter>

src/main/java/de/rwth/idsg/steve/ocpp/task/ChangeConfigurationTask.java

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@
2929
import java.util.HexFormat;
3030

3131
import static de.rwth.idsg.steve.web.dto.ocpp.ConfigurationKeyEnum.AuthorizationKey;
32-
import static de.rwth.idsg.steve.web.dto.ocpp.ConfigurationKeyEnum.CpoName;
3332
import static de.rwth.idsg.steve.web.dto.ocpp.ConfigurationKeyEnum.SecurityProfile;
3433
import static ocpp.cp._2015._10.ConfigurationStatus.ACCEPTED;
3534

@@ -122,14 +121,14 @@ public AsyncHandler<ocpp.cp._2015._10.ChangeConfigurationResponse> getOcpp16Hand
122121
}
123122

124123
private void updateDatabaseAfterAccepted(String chargeBoxId) {
125-
if (CpoName.name().equals(params.getKey())) {
126-
chargePointService.updateCpoName(chargeBoxId, params.getValue());
127-
128-
} else if (AuthorizationKey.name().equals(params.getKey())) {
124+
if (AuthorizationKey.name().equals(params.getKey())) {
129125
chargePointService.updateBasicAuthPassword(chargeBoxId, params.getValue());
126+
}
130127

131-
} else if (SecurityProfile.name().equals(params.getKey())) {
128+
if (SecurityProfile.name().equals(params.getKey())) {
132129
chargePointService.updateSecurityProfile(chargeBoxId, params.getValue());
133130
}
131+
132+
chargePointService.updateOcppConfigurationAfterChange(chargeBoxId, params.getKey(), params.getValue());
134133
}
135134
}

src/main/java/de/rwth/idsg/steve/ocpp/task/GetConfigurationTask.java

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,15 @@
2121
import de.rwth.idsg.steve.ocpp.Ocpp15AndAboveTask;
2222
import de.rwth.idsg.steve.ocpp.OcppCallback;
2323
import de.rwth.idsg.steve.ocpp.RequestResult;
24+
import de.rwth.idsg.steve.ocpp.ws.JsonObjectMapper;
2425
import de.rwth.idsg.steve.service.ChargePointService;
25-
import de.rwth.idsg.steve.web.dto.ocpp.ConfigurationKeyEnum;
2626
import de.rwth.idsg.steve.web.dto.ocpp.GetConfigurationParams;
2727
import lombok.Getter;
2828
import lombok.RequiredArgsConstructor;
2929
import lombok.extern.slf4j.Slf4j;
3030
import ocpp.cp._2012._06.GetConfigurationRequest;
3131
import ocpp.cp._2012._06.GetConfigurationResponse;
32+
import org.springframework.util.CollectionUtils;
3233

3334
import jakarta.xml.ws.AsyncHandler;
3435
import java.util.List;
@@ -82,6 +83,8 @@ public AsyncHandler<ocpp.cp._2012._06.GetConfigurationResponse> getOcpp15Handler
8283
.map(k -> new KeyValue(k.getKey(), k.getValue(), k.isReadonly()))
8384
.collect(Collectors.toList());
8485

86+
updateCache(chargeBoxId, keyValues);
87+
8588
success(chargeBoxId, new ConfigurationKeyValues(keyValues, response.getUnknownKey()));
8689
} catch (Exception e) {
8790
failed(chargeBoxId, e);
@@ -95,26 +98,44 @@ public AsyncHandler<ocpp.cp._2015._10.GetConfigurationResponse> getOcpp16Handler
9598
try {
9699
ocpp.cp._2015._10.GetConfigurationResponse response = res.get();
97100

98-
for (var conf : response.getConfigurationKey()) {
99-
if (ConfigurationKeyEnum.CpoName.name().equals(conf.getKey())) {
100-
log.info("Updating CpoName={} for chargeBoxId={}", conf.getValue(), chargeBoxId);
101-
chargePointService.updateCpoName(chargeBoxId, conf.getValue());
102-
break;
103-
}
104-
}
105-
106101
List<KeyValue> keyValues = response.getConfigurationKey()
107102
.stream()
108103
.map(k -> new KeyValue(k.getKey(), k.getValue(), k.isReadonly()))
109104
.collect(Collectors.toList());
110105

106+
updateCache(chargeBoxId, keyValues);
107+
111108
success(chargeBoxId, new ConfigurationKeyValues(keyValues, response.getUnknownKey()));
112109
} catch (Exception e) {
113110
failed(chargeBoxId, e);
114111
}
115112
};
116113
}
117114

115+
/**
116+
* Whenever we ask for all config keys (i.e. when no config key was selected), use the opportunity to update
117+
* the database.
118+
*/
119+
private void updateCache(String chargeBoxId, List<KeyValue> keyValues) {
120+
boolean askedForAllConfigs = CollectionUtils.isEmpty(params.getAllKeys());
121+
if (!askedForAllConfigs) {
122+
return;
123+
}
124+
125+
try {
126+
var mapper = JsonObjectMapper.INSTANCE.getMapper();
127+
128+
var node = mapper.createObjectNode();
129+
for (var entry : keyValues) {
130+
node.set(entry.getKey(), mapper.stringNode(entry.getValue()));
131+
}
132+
133+
chargePointService.updateOcppConfiguration(chargeBoxId, mapper.writeValueAsString(node));
134+
} catch (Exception e) {
135+
log.error("Failed during updateOcppConfiguration", e);
136+
}
137+
}
138+
118139
@RequiredArgsConstructor
119140
@Getter
120141
public static class ConfigurationKeyValues {

src/main/java/de/rwth/idsg/steve/repository/ChargePointRepository.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import de.rwth.idsg.steve.web.dto.ChargePointForm;
2828
import de.rwth.idsg.steve.web.dto.ChargePointQueryForm;
2929
import de.rwth.idsg.steve.web.dto.ConnectorStatusForm;
30+
import org.jetbrains.annotations.NotNull;
3031
import org.jetbrains.annotations.Nullable;
3132

3233
import java.util.List;
@@ -39,9 +40,10 @@
3940
*/
4041
public interface ChargePointRepository {
4142
Optional<ChargePointRegistration> getRegistration(String chargeBoxId);
42-
void updateCpoName(String chargeBoxId, String cpoName);
4343
void updateBasicAuthPassword(String chargeBoxId, String encodedPwd);
4444
void updateSecurityProfile(String chargeBoxId, OcppSecurityProfile ocppSecurityProfile);
45+
void updateOcppConfiguration(String chargeBoxId, String jsonNode);
46+
void updateOcppConfigurationAfterChange(String chargeBoxId, @NotNull String key, String value);
4547

4648
List<ChargePointSelect> getChargePointSelect(OcppProtocol protocol, List<String> inStatusFilter, List<String> chargeBoxIdFilter);
4749

src/main/java/de/rwth/idsg/steve/repository/dto/ChargePointRegistration.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,22 @@
1919
package de.rwth.idsg.steve.repository.dto;
2020

2121
import de.rwth.idsg.steve.ocpp.OcppSecurityProfile;
22+
import de.rwth.idsg.steve.web.dto.ocpp.ConfigurationKeyEnum;
23+
import org.jetbrains.annotations.NotNull;
24+
import tools.jackson.databind.node.ObjectNode;
2225

2326
public record ChargePointRegistration(
2427
int chargeBoxPk,
2528
String chargeBoxId,
2629
String registrationStatus,
2730
OcppSecurityProfile securityProfile,
2831
String hashedAuthPassword,
29-
String cpoName,
32+
@NotNull ObjectNode ocppConfiguration,
3033
String serialNumber
3134
) {
35+
36+
public String cpoName() {
37+
var node = ocppConfiguration.get(ConfigurationKeyEnum.CpoName.name());
38+
return node == null ? null : node.asString();
39+
}
3240
}

src/main/java/de/rwth/idsg/steve/repository/impl/ChargePointRepositoryImpl.java

Lines changed: 50 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import de.rwth.idsg.steve.SteveException;
2222
import de.rwth.idsg.steve.ocpp.OcppProtocol;
2323
import de.rwth.idsg.steve.ocpp.OcppSecurityProfile;
24+
import de.rwth.idsg.steve.ocpp.ws.JsonObjectMapper;
2425
import de.rwth.idsg.steve.repository.AddressRepository;
2526
import de.rwth.idsg.steve.repository.ChargePointRepository;
2627
import de.rwth.idsg.steve.repository.dto.ChargePoint;
@@ -37,10 +38,12 @@
3738
import lombok.extern.slf4j.Slf4j;
3839
import ocpp.cs._2015._10.RegistrationStatus;
3940
import org.apache.commons.lang3.StringUtils;
41+
import org.jetbrains.annotations.NotNull;
4042
import org.joda.time.DateTime;
4143
import org.jooq.Condition;
4244
import org.jooq.DSLContext;
4345
import org.jooq.Field;
46+
import org.jooq.JSON;
4447
import org.jooq.Record1;
4548
import org.jooq.Record5;
4649
import org.jooq.Result;
@@ -51,6 +54,7 @@
5154
import org.jooq.impl.DSL;
5255
import org.springframework.stereotype.Repository;
5356
import org.springframework.util.CollectionUtils;
57+
import tools.jackson.databind.node.ObjectNode;
5458

5559
import java.util.List;
5660
import java.util.Map;
@@ -83,7 +87,7 @@ public Optional<ChargePointRegistration> getRegistration(String chargeBoxId) {
8387
CHARGE_BOX.REGISTRATION_STATUS,
8488
CHARGE_BOX.SECURITY_PROFILE,
8589
CHARGE_BOX.AUTH_PASSWORD,
86-
CHARGE_BOX.CPO_NAME,
90+
CHARGE_BOX.OCPP_CONFIGURATION,
8791
CHARGE_BOX.CHARGE_POINT_SERIAL_NUMBER)
8892
.from(CHARGE_BOX)
8993
.where(CHARGE_BOX.CHARGE_BOX_ID.eq(chargeBoxId))
@@ -94,7 +98,7 @@ public Optional<ChargePointRegistration> getRegistration(String chargeBoxId) {
9498
rec.value3(),
9599
OcppSecurityProfile.fromValue(rec.value4()),
96100
rec.value5(),
97-
rec.value6(),
101+
toObjectNode(rec.value6()),
98102
rec.value7()
99103
));
100104

@@ -103,17 +107,6 @@ public Optional<ChargePointRegistration> getRegistration(String chargeBoxId) {
103107
: Optional.ofNullable(status.getFirst());
104108
}
105109

106-
@Override
107-
public void updateCpoName(String chargeBoxId, String cpoName) {
108-
if (StringUtils.isEmpty(cpoName)) {
109-
return;
110-
}
111-
ctx.update(CHARGE_BOX)
112-
.set(CHARGE_BOX.CPO_NAME, cpoName)
113-
.where(CHARGE_BOX.CHARGE_BOX_ID.equal(chargeBoxId))
114-
.execute();
115-
}
116-
117110
@Override
118111
public void updateBasicAuthPassword(String chargeBoxId, String encodedPwd) {
119112
ctx.update(CHARGE_BOX)
@@ -133,6 +126,36 @@ public void updateSecurityProfile(String chargeBoxId, OcppSecurityProfile ocppSe
133126
.execute();
134127
}
135128

129+
@Override
130+
public void updateOcppConfiguration(String chargeBoxId, String jsonNode) {
131+
ctx.update(CHARGE_BOX)
132+
.set(CHARGE_BOX.OCPP_CONFIGURATION, JSON.json(jsonNode))
133+
.where(CHARGE_BOX.CHARGE_BOX_ID.equal(chargeBoxId))
134+
.execute();
135+
}
136+
137+
@Override
138+
public void updateOcppConfigurationAfterChange(String chargeBoxId, @NotNull String key, String value) {
139+
ctx.transaction(conf -> {
140+
DSLContext tx = DSL.using(conf);
141+
142+
var storedConfig = tx.select(CHARGE_BOX.OCPP_CONFIGURATION)
143+
.from(CHARGE_BOX)
144+
.where(CHARGE_BOX.CHARGE_BOX_ID.equal(chargeBoxId))
145+
.forUpdate()
146+
.fetchOne(CHARGE_BOX.OCPP_CONFIGURATION);
147+
148+
var mapper = JsonObjectMapper.INSTANCE.getMapper();
149+
var node = toObjectNode(storedConfig);
150+
node.set(key, value == null ? mapper.nullNode() : mapper.stringNode(value));
151+
152+
tx.update(CHARGE_BOX)
153+
.set(CHARGE_BOX.OCPP_CONFIGURATION, JSON.json(mapper.writeValueAsString(node)))
154+
.where(CHARGE_BOX.CHARGE_BOX_ID.equal(chargeBoxId))
155+
.execute();
156+
});
157+
}
158+
136159
@Override
137160
public List<ChargePointSelect> getChargePointSelect(OcppProtocol protocol, List<String> inStatusFilter, List<String> chargeBoxIdFilter) {
138161
Condition chargeBoxIdCondition = CollectionUtils.isEmpty(chargeBoxIdFilter)
@@ -437,4 +460,18 @@ private void deleteChargePointInternal(DSLContext ctx, int chargeBoxPk) {
437460
.where(CHARGE_BOX.CHARGE_BOX_PK.equal(chargeBoxPk))
438461
.execute();
439462
}
463+
464+
private static ObjectNode toObjectNode(JSON value) {
465+
var mapper = JsonObjectMapper.INSTANCE.getMapper();
466+
467+
if (value == null) {
468+
return mapper.createObjectNode();
469+
}
470+
471+
var node = mapper.readTree(value.data());
472+
if (!node.isObject()) {
473+
throw new SteveException("Existing OCPP configuration is not a JSON object"); // should not happen
474+
}
475+
return (ObjectNode) node;
476+
}
440477
}

src/main/java/de/rwth/idsg/steve/service/ChargePointConfigUpdater.java

Lines changed: 6 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -19,16 +19,14 @@
1919
package de.rwth.idsg.steve.service;
2020

2121
import de.rwth.idsg.steve.SteveException;
22-
import de.rwth.idsg.steve.ocpp.OcppProtocol;
23-
import de.rwth.idsg.steve.ocpp.OcppSecurityProfile;
22+
import de.rwth.idsg.steve.ocpp.OcppTransport;
2423
import de.rwth.idsg.steve.ocpp.OcppVersion;
2524
import de.rwth.idsg.steve.repository.dto.ChargePointSelect;
2625
import de.rwth.idsg.steve.service.notification.OcppStationSecurityBasicAuthChanged;
2726
import de.rwth.idsg.steve.service.notification.OcppStationSecurityProfileChanged;
2827
import de.rwth.idsg.steve.service.notification.OcppStationWebSocketConnected;
2928
import de.rwth.idsg.steve.web.dto.RestCallback;
3029
import de.rwth.idsg.steve.web.dto.ocpp.ChangeConfigurationParams;
31-
import de.rwth.idsg.steve.web.dto.ocpp.ConfigurationKeyEnum;
3230
import de.rwth.idsg.steve.web.dto.ocpp.GetConfigurationParams;
3331
import lombok.RequiredArgsConstructor;
3432
import lombok.extern.slf4j.Slf4j;
@@ -59,29 +57,18 @@ public class ChargePointConfigUpdater {
5957
private final ChargePointServiceClient chargePointServiceClient;
6058

6159
@EventListener
62-
public void getAndUpdateCpoName(OcppStationWebSocketConnected notification) {
60+
public void getAllOcppConfigs(OcppStationWebSocketConnected notification) {
6361
try {
64-
var ocppVersion = notification.getOcppVersion();
65-
if (ocppVersion == OcppVersion.V_12 || ocppVersion == OcppVersion.V_15) {
62+
// GetConfiguration was not there in Ocpp 1.2
63+
if (notification.getOcppVersion() == OcppVersion.V_12) {
6664
return;
6765
}
6866

6967
var chargeBoxId = notification.getChargeBoxId();
70-
var registration = chargePointService.getRegistrationDirect(chargeBoxId);
71-
if (registration.isEmpty()) {
72-
return;
73-
}
74-
75-
// CpoName came with ocpp 1.6 security extension, which also introduced the security profiles. profile 0
76-
// represents the old world before the security extension. since it is not relevant, skip this task.
77-
if (registration.get().securityProfile() == OcppSecurityProfile.Profile_0) {
78-
return;
79-
}
68+
var protocol = notification.getOcppVersion().toProtocol(OcppTransport.JSON);
8069

81-
// Send request to get CpoName. Default impl of GetConfigurationTask takes care of updating the database.
8270
var params = new GetConfigurationParams();
83-
params.setChargePointSelectList(List.of(new ChargePointSelect(OcppProtocol.V_16_JSON, chargeBoxId)));
84-
params.setConfKeyList(List.of(ConfigurationKeyEnum.CpoName.name()));
71+
params.setChargePointSelectList(List.of(new ChargePointSelect(protocol, chargeBoxId)));
8572
chargePointServiceClient.getConfiguration(params);
8673
} catch (Exception e) {
8774
log.error("Failed", e);

src/main/java/de/rwth/idsg/steve/service/ChargePointService.java

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,8 @@
6767
import java.util.stream.Collectors;
6868
import java.util.stream.Stream;
6969

70+
import static de.rwth.idsg.steve.web.dto.ocpp.ConfigurationKeyEnum.AuthorizationKey;
71+
7072
/**
7173
* @author Sevket Goekay <sevketgokay@gmail.com>
7274
* @since 29.10.2025
@@ -105,14 +107,6 @@ public List<Integer> getNonZeroConnectorIds(String chargeBoxId) {
105107
return chargePointRepository.getNonZeroConnectorIds(chargeBoxId);
106108
}
107109

108-
public void updateCpoName(String chargeBoxId, String cpoName) {
109-
try {
110-
chargePointRepository.updateCpoName(chargeBoxId, cpoName);
111-
} catch (Exception e) {
112-
log.error("Failed during updateCpoName, because: {}", e.getMessage(), e);
113-
}
114-
}
115-
116110
public void updateBasicAuthPassword(String chargeBoxId, String authPassword) {
117111
String encodedPwd = StringUtils.isEmpty(authPassword)
118112
? null
@@ -126,6 +120,26 @@ public void updateSecurityProfile(String chargeBoxId, String securityProfile) {
126120
chargePointRepository.updateSecurityProfile(chargeBoxId, ocppSecurityProfile);
127121
}
128122

123+
public void updateOcppConfiguration(String chargeBoxId, String jsonNode) {
124+
try {
125+
chargePointRepository.updateOcppConfiguration(chargeBoxId, jsonNode);
126+
} catch (Exception e) {
127+
log.error("Failed during updateOcppConfiguration, because: {}", e.getMessage(), e);
128+
}
129+
}
130+
131+
public void updateOcppConfigurationAfterChange(String chargeBoxId, String key, String value) {
132+
if (StringUtils.isEmpty(key) || AuthorizationKey.name().equals(key)) {
133+
return; // AuthorizationKey is write-only, we should not store it like that
134+
}
135+
136+
try {
137+
chargePointRepository.updateOcppConfigurationAfterChange(chargeBoxId, key, value);
138+
} catch (Exception e) {
139+
log.error("Failed during updateOcppConfigurationAfterChange, because: {}", e.getMessage(), e);
140+
}
141+
}
142+
129143
public void addChargePointList(List<String> chargeBoxIdList) {
130144
chargePointRepository.addChargePointList(chargeBoxIdList);
131145
}

0 commit comments

Comments
 (0)