Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactored OCMF powermeter transaction handling #640

Merged
merged 15 commits into from
May 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 13 additions & 11 deletions modules/EvseManager/Charger.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,6 @@
*/

#include "Charger.hpp"

#include <boost/uuid/random_generator.hpp>
#include <boost/uuid/uuid.hpp>
#include <boost/uuid/uuid_io.hpp>
#include <generated/types/powermeter.hpp>
#include <math.h>
#include <string.h>
Expand All @@ -23,10 +19,7 @@

#include "everest/logging.hpp"
#include "scoped_lock_timeout.hpp"

std::string generate_session_uuid() {
return boost::uuids::to_string(boost::uuids::random_generator()());
}
#include "utils.hpp"

namespace module {

Expand Down Expand Up @@ -1062,7 +1055,7 @@ bool Charger::cancel_transaction(const types::evse_manager::StopTransactionReque
void Charger::start_session(bool authfirst) {
shared_context.session_active = true;
shared_context.authorized = false;
shared_context.session_uuid = generate_session_uuid();
shared_context.session_uuid = utils::generate_session_uuid();
std::optional<types::authorization::ProvidedIdToken> provided_id_token;
if (authfirst) {
shared_context.last_start_session_reason = types::evse_manager::StartSessionReason::Authorized;
Expand All @@ -1084,8 +1077,17 @@ bool Charger::start_transaction() {
shared_context.stop_transaction_id_token.reset();
shared_context.transaction_active = true;

const types::powermeter::TransactionReq req{
evse_id, shared_context.session_uuid, shared_context.id_token.id_token.value, 0, 0, ""};
types::powermeter::TransactionReq req;
req.evse_id = evse_id;
req.transaction_id = shared_context.session_uuid;
req.identification_status =
types::powermeter::OCMFUserIdentificationStatus::ASSIGNED; // currently user is always assigned
req.identification_flags = {}; // TODO: Collect IF. Not all known in EVerest
req.identification_type = utils::convert_to_ocmf_identification_type(shared_context.id_token.id_token.type);
req.identification_level = std::nullopt; // TODO: Not yet known to EVerest
req.identification_data = shared_context.id_token.id_token.value;
req.tariff_text = std::nullopt; // TODO: Not yet known to EVerest

for (const auto& meter : r_powermeter_billing) {
const auto response = meter->call_start_transaction(req);
// If we want to start the session but the meter fail, we stop the charging since
Expand Down
40 changes: 40 additions & 0 deletions modules/EvseManager/utils.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Pionix GmbH and Contributors to EVerest

#include <boost/uuid/random_generator.hpp>
#include <boost/uuid/uuid.hpp>
#include <boost/uuid/uuid_io.hpp>
#include <generated/types/powermeter.hpp>

namespace module {
namespace utils {

std::string generate_session_uuid() {
return boost::uuids::to_string(boost::uuids::random_generator()());
}

types::powermeter::OCMFIdentificationType
convert_to_ocmf_identification_type(const types::authorization::IdTokenType& id_token_type) {
switch (id_token_type) {
case types::authorization::IdTokenType::Central:
return types::powermeter::OCMFIdentificationType::CENTRAL;
case types::authorization::IdTokenType::eMAID:
return types::powermeter::OCMFIdentificationType::EMAID;
case types::authorization::IdTokenType::ISO14443:
return types::powermeter::OCMFIdentificationType::ISO14443;
case types::authorization::IdTokenType::ISO15693:
return types::powermeter::OCMFIdentificationType::ISO15693;
case types::authorization::IdTokenType::KeyCode:
return types::powermeter::OCMFIdentificationType::KEY_CODE;
case types::authorization::IdTokenType::Local:
return types::powermeter::OCMFIdentificationType::LOCAL;
case types::authorization::IdTokenType::NoAuthorization:
return types::powermeter::OCMFIdentificationType::NONE;
case types::authorization::IdTokenType::MacAddress:
return types::powermeter::OCMFIdentificationType::UNDEFINED;
}
return types::powermeter::OCMFIdentificationType::UNDEFINED;
}

} // namespace utils
} // namespace module
2 changes: 2 additions & 0 deletions modules/LemDCBM400600/LemDCBM400600.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@
int resilience_initial_connection_retry_delay;
int resilience_transaction_request_retries;
int resilience_transaction_request_retry_delay;
int cable_id;

Check notice on line 34 in modules/LemDCBM400600/LemDCBM400600.hpp

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

modules/LemDCBM400600/LemDCBM400600.hpp#L34

struct member 'Conf::cable_id' is never used.
int tariff_id;

Check notice on line 35 in modules/LemDCBM400600/LemDCBM400600.hpp

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

modules/LemDCBM400600/LemDCBM400600.hpp#L35

struct member 'Conf::tariff_id' is never used.
};

class LemDCBM400600 : public Everest::ModuleBase {
Expand Down
15 changes: 10 additions & 5 deletions modules/LemDCBM400600/main/lem_dcbm_400600_controller.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,8 @@ void LemDCBM400600Controller::request_device_to_start_transaction(const types::p
this->time_sync_helper->sync(*this->http_client);

auto response = this->http_client->post(
"/v1/legal", module::main::LemDCBM400600Controller::transaction_start_request_to_dcbm_payload(value));
"/v1/legal", module::main::LemDCBM400600Controller::transaction_start_request_to_dcbm_payload(
value, this->config.cable_id, this->config.tariff_id));
if (response.status_code != 201) {
throw UnexpectedDCBMResponseCode("/v1/legal", 201, response);
}
Expand Down Expand Up @@ -175,10 +176,14 @@ void LemDCBM400600Controller::convert_livemeasure_to_powermeter(const std::strin
powermeter.power_W.emplace(types::units::Power{data.at("power")});
}
std::string
LemDCBM400600Controller::transaction_start_request_to_dcbm_payload(const types::powermeter::TransactionReq& request) {
return nlohmann::ordered_json{{"evseId", request.evse_id}, {"transactionId", request.transaction_id},
{"clientId", request.client_id}, {"tariffId", request.tariff_id},
{"cableId", request.cable_id}, {"userData", request.user_data}}
LemDCBM400600Controller::transaction_start_request_to_dcbm_payload(const types::powermeter::TransactionReq& request,
const int cable_id, const int tariff_id) {
return nlohmann::ordered_json{{"evseId", request.evse_id},
{"transactionId", request.transaction_id},
{"clientId", request.transaction_id},
{"tariffId", tariff_id},
{"cableId", cable_id},
{"userData", ""}}
.dump();
}

Expand Down
8 changes: 7 additions & 1 deletion modules/LemDCBM400600/main/lem_dcbm_400600_controller.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@
const int transaction_number_of_http_retries;
// wait time before each retry for transaction start/stop requests
const int transaction_retry_wait_in_milliseconds;
// The cable loss compensation level to use. This allows compensating the measurements of the DCBM with a
// resistance.
const int cable_id;

Check notice on line 31 in modules/LemDCBM400600/main/lem_dcbm_400600_controller.hpp

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

modules/LemDCBM400600/main/lem_dcbm_400600_controller.hpp#L31

struct member 'Conf::cable_id' is never used.
// Used for a unique transaction tariff designation
const int tariff_id;

Check notice on line 33 in modules/LemDCBM400600/main/lem_dcbm_400600_controller.hpp

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

modules/LemDCBM400600/main/lem_dcbm_400600_controller.hpp#L33

struct member 'Conf::tariff_id' is never used.
};

class DCBMUnexpectedResponseException : public std::exception {
Expand Down Expand Up @@ -82,7 +87,8 @@
void request_device_to_stop_transaction(const std::string& transaction_id);
std::string fetch_ocmf_result(const std::string& transaction_id);
void convert_livemeasure_to_powermeter(const std::string& livemeasure, types::powermeter::Powermeter& powermeter);
static std::string transaction_start_request_to_dcbm_payload(const types::powermeter::TransactionReq& request);
static std::string transaction_start_request_to_dcbm_payload(const types::powermeter::TransactionReq& request,
const int cable_id, const int tariff_id);
static std::pair<std::string, std::string> get_transaction_stop_time_bounds();

template <typename Callable>
Expand Down
8 changes: 8 additions & 0 deletions modules/LemDCBM400600/manifest.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,14 @@ config:
description: For the controller resilience, the delay in milliseconds before a retry attempt at a transaction start or stop request.
type: integer
default: 250
cable_id:
description: The cable loss compensation level to use. This allows compensating the measurements of the DCBM with a resistance.
type: integer
default: 0
tariff_id:
description: Used for a unique transaction tariff designation
type: integer
default: 0
provides:
main:
description: This is the main unit of the module
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,8 @@ class StartTransactionSuccessResponse(BaseModel, extra=Extra.forbid):
transaction_min_stop_time: datetime


class StopTransactionSuccessResponse(BaseModel, extra=Extra.forbid):
class StopTransactionSuccessResponse(BaseModel, extra=Extra.allow):
status: str = Field("OK", const=True, strict=True)
ocmf: str = Field(regex=r"^OCMF|.*|.*$", strict=True)


class LemDCBMStandaloneEverestInstance(contextlib.ContextDecorator):
Expand Down
13 changes: 10 additions & 3 deletions modules/LemDCBM400600/tests/test_lem_dcbm_400600_controller.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,17 @@ class LemDCBM400600ControllerTest : public ::testing::Test {
})"};

const types::powermeter::TransactionReq transaction_request{
"mock_evse_id", "mock_transaction_id", "mock_client_id", 42, 43, "mock_user_data"};
"mock_evse_id",
"mock_transaction_id",
types::powermeter::OCMFUserIdentificationStatus::ASSIGNED,
{},
types::powermeter::OCMFIdentificationType::ISO14443,
std::nullopt,
std::nullopt,
std::nullopt};

const std::string expected_start_transaction_request_body{
R"({"evseId":"mock_evse_id","transactionId":"mock_transaction_id","clientId":"mock_client_id","tariffId":42,"cableId":43,"userData":"mock_user_data"})"};
R"({"evseId":"mock_evse_id","transactionId":"mock_transaction_id","clientId":"mock_transaction_id","tariffId":0,"cableId":0,"userData":""})"};

const std::string put_legal_response = R"({
"paginationCounter": 6,
Expand Down Expand Up @@ -85,7 +92,7 @@ class LemDCBM400600ControllerTest : public ::testing::Test {
"publicKey": "A80F10D968E1122F8820F288B23C4E1C0DA912F35B48481274ADFEFE66D7E87E130C7CF2B8047C45CF105041C8C3A57DD242782F755C9443F42DABA9404A67BF"
})";

const LemDCBM400600Controller::Conf controller_config{0, 0, 1, 0};
const LemDCBM400600Controller::Conf controller_config{0, 0, 1, 0, 0, 0};

void SetUp() override {
this->http_client = std::make_unique<HTTPClientMock>();
Expand Down
46 changes: 22 additions & 24 deletions modules/LemDCBM400600/tests/test_lem_dcbm_400_600_e2e.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,8 @@ async def test_lem_dcbm_e2e_powermeter_does_regular_publish(everest_test_instanc
@pytest.mark.asyncio
async def test_lem_dcbm_e2e_powermeter_meterid_correct(everest_test_instance):
power_meter: Powermeter = await everest_test_instance.probe_module.poll_next_powermeter(1.25)
assert re.match(r"^\d+$", power_meter.meter_id), f"got unexpected meter_id {power_meter.meter_id}"
assert re.match(
r"^\d+$", power_meter.meter_id), f"got unexpected meter_id {power_meter.meter_id}"


@pytest.mark.asyncio
Expand All @@ -104,15 +105,13 @@ async def test_lem_dcbm_e2e_start_stop_transaction(everest_test_instance, dcbm):
{"value": {
"evse_id": "mock_evse_id",
"transaction_id": "e2e_test_transaction",
"client_id": "mock_client_id",
"tariff_id": 1,
"cable_id": 0,
"user_data": "mock_user_data"
"identification_status": "ASSIGNED",
"identification_flags": []
}})

parsed_start_result = StartTransactionSuccessResponse(**start_result)
assert 48 * 60 - 3.1 < ((
parsed_start_result.transaction_max_stop_time - parsed_start_result.transaction_min_stop_time).total_seconds() / 60) <= 48 * 60 - 2.9
parsed_start_result.transaction_max_stop_time - parsed_start_result.transaction_min_stop_time).total_seconds() / 60) <= 48 * 60 - 2.9

logging.info("started transaction 'e2e_test_transaction'")

Expand Down Expand Up @@ -140,14 +139,13 @@ async def test_lem_dcbm_e2e_time_sync(everest_test_instance, dcbm):

# start transaction to enforce early sync; tidied up by fixture
assert everest_test_instance.probe_module.call_powermeter_command('start_transaction',
{"value": {
"evse_id": "mock_evse_id",
"transaction_id": "e2e_test_transaction",
"client_id": "mock_client_id",
"tariff_id": 1,
"cable_id": 0,
"user_data": "mock_user_data"
}})["status"] == "OK"
{"value": {
"evse_id": "mock_evse_id",
"transaction_id": "e2e_test_transaction",
"identification_status": "ASSIGNED",
"identification_flags": [],
"identification_type": "ISO14443"
}})["status"] == "OK"

dcbm.set_time(datetime.now() - timedelta(days=365))

Expand All @@ -164,14 +162,13 @@ async def test_lem_dcbm_e2e_ntp_setup(everest_test_instance_ntp_configured, dcbm

# start transaction to enforce early sync; tidied up by fixture
assert everest_test_instance_ntp_configured.probe_module.call_powermeter_command('start_transaction',
{"value": {
"evse_id": "mock_evse_id",
"transaction_id": "e2e_test_transaction",
"client_id": "mock_client_id",
"tariff_id": 1,
"cable_id": 0,
"user_data": "mock_user_data"
}})["status"] == "OK"
{"value": {
"evse_id": "mock_evse_id",
"transaction_id": "e2e_test_transaction",
"identification_status": "ASSIGNED",
"identification_flags": [],
"identification_type": "ISO14443"
}})["status"] == "OK"

async def check():
while not (ntp_settings := dcbm.get_ntp_settings()).ntpActivated:
Expand All @@ -187,10 +184,11 @@ async def check():
{
"ipAddress": "test_ntp_2",
"port": 125
}]
}]


@pytest.mark.asyncio
async def test_lem_dcbm_2e_get_powermeter_tls(everest_test_instance_tls):
power_meter: Powermeter = await everest_test_instance_tls.probe_module.poll_next_powermeter(1.25)
assert re.match(r"^\d+$", power_meter.meter_id), f"got unexpected meter_id {power_meter.meter_id}"
assert re.match(
r"^\d+$", power_meter.meter_id), f"got unexpected meter_id {power_meter.meter_id}"
14 changes: 6 additions & 8 deletions modules/LemDCBM400600/tests/test_lem_dcbm_400_600_sil.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,17 +28,15 @@ def test_start_transaction(everest_test_instance):
res = everest_test_instance.probe_module.call_powermeter_command('start_transaction',
{"value": {
"evse_id": "mock_evse_id",
"transaction_id": "mock_transaction_id",
"client_id": "mock_client_id",
"tariff_id": 42,
"cable_id": 43,
"user_data": "mock_user_data"
}
})
"transaction_id": "e2e_test_transaction",
"identification_status": "ASSIGNED",
"identification_flags": [],
"identification_type": "ISO14443"
}})

parsed_start_result = StartTransactionSuccessResponse(**res)
assert 48 * 60 - 3.1 < ((
parsed_start_result.transaction_max_stop_time - parsed_start_result.transaction_min_stop_time).total_seconds() / 60) <= 48 * 60 - 2.9
parsed_start_result.transaction_max_stop_time - parsed_start_result.transaction_min_stop_time).total_seconds() / 60) <= 48 * 60 - 2.9


def test_stop_transaction(everest_test_instance):
Expand Down
Loading
Loading