Skip to content

Commit ecdb0b4

Browse files
committed
connectorId SHALL be > 0 in RemoteStartTransaction (TC_027_CS)
1 parent 0455ec0 commit ecdb0b4

File tree

5 files changed

+178
-1
lines changed

5 files changed

+178
-1
lines changed

.github/workflows/tests.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ jobs:
2323
uses: actions/checkout@v3
2424
with:
2525
repository: Mbed-TLS/mbedtls
26-
ref: v2.28.1
26+
ref: v2.28.10
2727
path: lib/mbedtls
2828
- name: Get build tools
2929
run: |

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
.pio
2+
.vscode
23
build
34
lib
45
mo_store

CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,7 @@ set(MO_SRC_UNIT
150150
tests/LocalAuthList.cpp
151151
tests/Variables.cpp
152152
tests/Transactions.cpp
153+
tests/RemoteStartTransaction.cpp
153154
tests/Certificates.cpp
154155
tests/FirmwareManagement.cpp
155156
tests/ChargePointError.cpp

src/MicroOcpp/Operations/RemoteStartTransaction.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,13 @@ const char* RemoteStartTransaction::getOperationType() {
2525
void RemoteStartTransaction::processReq(JsonObject payload) {
2626
int connectorId = payload["connectorId"] | -1;
2727

28+
// OCPP 1.6 specification: connectorId SHALL be > 0 (TC_027_CS)
29+
if (connectorId == 0) {
30+
MO_DBG_INFO("RemoteStartTransaction rejected: connectorId SHALL not be 0");
31+
accepted = false;
32+
return;
33+
}
34+
2835
if (!payload.containsKey("idTag")) {
2936
errorCode = "FormationViolation";
3037
return;

tests/RemoteStartTransaction.cpp

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
// matth-x/MicroOcpp
2+
// Copyright Matthias Akstaller 2019 - 2024
3+
// MIT License
4+
5+
#include <MicroOcpp.h>
6+
#include <MicroOcpp/Core/Connection.h>
7+
#include <MicroOcpp/Core/Context.h>
8+
#include <MicroOcpp/Core/Request.h>
9+
#include <MicroOcpp/Core/Configuration.h>
10+
#include <MicroOcpp/Core/OperationRegistry.h>
11+
#include <MicroOcpp/Operations/CustomOperation.h>
12+
#include <MicroOcpp/Model/Model.h>
13+
#include <MicroOcpp/Model/ConnectorBase/Connector.h>
14+
#include <MicroOcpp/Operations/RemoteStartTransaction.h>
15+
#include <MicroOcpp/Debug.h>
16+
#include <catch2/catch.hpp>
17+
#include "./helpers/testHelper.h"
18+
19+
using namespace MicroOcpp;
20+
21+
TEST_CASE("RemoteStartTransaction") {
22+
printf("\nRun %s\n", "RemoteStartTransaction");
23+
24+
LoopbackConnection loopback;
25+
mocpp_initialize(loopback, ChargerCredentials("test-runner1234"));
26+
mocpp_set_timer(custom_timer_cb);
27+
loop();
28+
29+
auto context = getOcppContext();
30+
auto connector = context->getModel().getConnector(1);
31+
32+
SECTION("Basic remote start accepted") {
33+
// Ensure connector idle
34+
REQUIRE(connector->getStatus() == ChargePointStatus_Available);
35+
36+
context->initiateRequest(makeRequest(new Ocpp16::CustomOperation(
37+
"RemoteStartTransaction",
38+
[] () {
39+
auto doc = makeJsonDoc(UNIT_MEM_TAG, JSON_OBJECT_SIZE(2));
40+
auto payload = doc->to<JsonObject>();
41+
payload["idTag"] = "mIdTag";
42+
return doc;},
43+
[] (JsonObject) {}
44+
)));
45+
46+
loop();
47+
REQUIRE(connector->getStatus() == ChargePointStatus_Charging);
48+
endTransaction();
49+
loop();
50+
REQUIRE(connector->getStatus() == ChargePointStatus_Available);
51+
}
52+
53+
SECTION("Same connectorId rejected when transaction active") {
54+
// Start with connector 1 busy so remote start with connectorId=1 should not auto-assign
55+
beginTransaction("anotherId");
56+
loop();
57+
REQUIRE(connector->getStatus() == ChargePointStatus_Charging);
58+
59+
bool checkProcessed = false;
60+
61+
context->initiateRequest(makeRequest(new Ocpp16::CustomOperation(
62+
"RemoteStartTransaction",
63+
[] () {
64+
auto doc = makeJsonDoc(UNIT_MEM_TAG, JSON_OBJECT_SIZE(3));
65+
auto payload = doc->to<JsonObject>();
66+
payload["idTag"] = "mIdTag";
67+
payload["connectorId"] = 1; // the same connector already in use
68+
return doc;},
69+
[&checkProcessed] (JsonObject response) {
70+
checkProcessed = true;
71+
REQUIRE( !strcmp(response["status"] | "_Undefined", "Rejected") );
72+
}
73+
)));
74+
75+
loop();
76+
77+
// Transaction should still be the original one only
78+
REQUIRE(checkProcessed);
79+
REQUIRE(connector->getTransaction());
80+
REQUIRE(strcmp(connector->getTransaction()->getIdTag(), "anotherId") == 0);
81+
REQUIRE(connector->getStatus() == ChargePointStatus_Charging);
82+
83+
endTransaction();
84+
loop();
85+
REQUIRE(connector->getStatus() == ChargePointStatus_Available);
86+
}
87+
88+
SECTION("ConnectorId 0 rejected per spec") {
89+
// RemoteStartTransaction response status is Rejected when connectorId == 0
90+
REQUIRE(connector->getStatus() == ChargePointStatus_Available);
91+
92+
bool checkProcessed = false;
93+
94+
context->initiateRequest(makeRequest(new Ocpp16::CustomOperation(
95+
"RemoteStartTransaction",
96+
[] () {
97+
auto doc = makeJsonDoc(UNIT_MEM_TAG, JSON_OBJECT_SIZE(3));
98+
auto payload = doc->to<JsonObject>();
99+
payload["idTag"] = "mIdTag";
100+
payload["connectorId"] = 0; // invalid per spec
101+
return doc;},
102+
[&checkProcessed] (JsonObject response) {
103+
checkProcessed = true;
104+
REQUIRE( !strcmp(response["status"] | "_Undefined", "Rejected") );
105+
}
106+
)));
107+
108+
loop();
109+
110+
REQUIRE(checkProcessed);
111+
REQUIRE(connector->getStatus() == ChargePointStatus_Available);
112+
}
113+
114+
SECTION("No free connector so rejected") {
115+
// Occupy all connectors (limit defined by MO_NUMCONNECTORS)
116+
for (unsigned cId = 1; cId < context->getModel().getNumConnectors(); cId++) {
117+
auto c = context->getModel().getConnector(cId);
118+
if (c) {
119+
c->beginTransaction_authorized("busyId");
120+
}
121+
}
122+
loop();
123+
124+
bool checkProcessed = false;
125+
auto freeFound = false;
126+
for (unsigned cId = 1; cId < context->getModel().getNumConnectors(); cId++) {
127+
auto c = context->getModel().getConnector(cId);
128+
if (c && !c->getTransaction()) freeFound = true;
129+
}
130+
REQUIRE(!freeFound); // ensure all are busy
131+
132+
context->initiateRequest(makeRequest(new Ocpp16::CustomOperation(
133+
"RemoteStartTransaction",
134+
[] () {
135+
auto doc = makeJsonDoc(UNIT_MEM_TAG, JSON_OBJECT_SIZE(2));
136+
auto payload = doc->to<JsonObject>();
137+
payload["idTag"] = "mIdTag";
138+
return doc;},
139+
[&checkProcessed] (JsonObject response) {
140+
checkProcessed = true;
141+
REQUIRE( !strcmp(response["status"] | "_Undefined", "Rejected") );
142+
}
143+
)));
144+
145+
loop();
146+
REQUIRE(checkProcessed);
147+
148+
// No new transaction should be created; keep statuses
149+
int activeTx = 0;
150+
for (unsigned cId = 1; cId < context->getModel().getNumConnectors(); cId++) {
151+
auto c = context->getModel().getConnector(cId);
152+
if (c && c->getTransaction()) activeTx++;
153+
}
154+
REQUIRE(activeTx == (int)context->getModel().getNumConnectors() - 1); // all occupied
155+
156+
// cleanup
157+
for (unsigned cId = 1; cId < context->getModel().getNumConnectors(); cId++) {
158+
auto c = context->getModel().getConnector(cId);
159+
if (c && c->getTransaction()) {
160+
c->endTransaction();
161+
}
162+
}
163+
loop();
164+
REQUIRE(connector->getStatus() == ChargePointStatus_Available);
165+
}
166+
167+
mocpp_deinitialize();
168+
}

0 commit comments

Comments
 (0)