Files
Eric F d398a6ced2 Add extracted tools: CitrineOS, OpenOCPP, ShapeShifter
- CitrineOS core extracted (CSMS OCPP 2.0.1)
- OpenOCPP extracted (firmware OCPP 1.6J/2.0.1)
- ShapeShifter library installed (pip install -e)
- ShapeShifter specification extracted
- EVerest extracted

TODO updated with progress
2026-06-08 00:38:27 -04:00

273 lines
14 KiB
C++

// SPDX-License-Identifier: Apache-2.0
// Copyright Pionix GmbH and Contributors to EVerest
#include "powermeterImpl.hpp"
#include "httpClient.hpp"
#include <chrono>
#include <string>
#include <thread>
namespace module {
namespace main {
void powermeterImpl::init() {
// Check Config values (essential plausibility checks)
check_config();
// Dependency injection pattern: Create the HTTP client first,
// then move it into the controller as a constructor argument
auto http_client = std::make_unique<HttpClient>(mod->config.ip_address, mod->config.port_http);
// Create controller object
this->controller = std::make_unique<IsaIemDcrController>(
std::move(http_client),
IsaIemDcrController::SnapshotConfig{
mod->config.timezone, mod->config.timezone_handle_DST, mod->config.datetime_resync_interval,
mod->config.resilience_initial_connection_retry_delay, mod->config.resilience_transaction_request_retries,
mod->config.resilience_transaction_request_retry_delay, mod->config.CT, mod->config.CI,
mod->config.TT_initial, mod->config.US});
}
void powermeterImpl::ready() {
// Start the live_measure_publisher thread, which periodically publishes the live measurements of the device
this->live_measure_publisher_thread = std::thread([this] {
while (true) {
try {
// Wait for at least one second (more if handle_start_transaction() or handle_stop_transaction() active)
do {
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
} while (start_transaction_running.load() == true || stop_transaction_running.load() == true);
// Init if needed
if (is_initialized.load() == false) {
is_initialized.store(this->controller->init());
if (is_initialized.load() == true) {
// Publish public key once after init
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
this->publish_public_key_ocmf(this->controller->get_publickey(false));
} else {
if (!this->error_state_monitor->is_error_active("powermeter/CommunicationFault",
"Communication timed out")) {
auto error = this->error_factory->create_error(
"powermeter/CommunicationFault", "Communication timed out",
"This error is raised due to communication timeout");
raise_error(error);
}
EVLOG_warning << "Connecting to IEM-DCR failed. Retry in "
<< mod->config.resilience_initial_connection_retry_delay << " milliseconds";
std::this_thread::sleep_for(
std::chrono::milliseconds(mod->config.resilience_initial_connection_retry_delay));
}
} else {
// Publish metervalue node (named powermeter in EVerest) and update status information
auto meter_value_response = this->controller->get_metervalue();
types::powermeter::Powermeter tmp_powermeter;
std::string tmp_status;
bool tmp_transaction_active;
std::tie(tmp_powermeter, tmp_status, tmp_transaction_active) = meter_value_response;
this->publish_powermeter(tmp_powermeter);
dcr_status.store(tmp_status);
transaction_active.store(tmp_transaction_active);
// Debug output :)
// EVLOG_info << this->controller->get_datetime();
// Update datetime in specified interval
if (transaction_active.load() == false) {
this->controller->refresh_datetime_if_required();
}
// Reset previous error (if active)
if (this->error_state_monitor->is_error_active("powermeter/CommunicationFault",
"Communication timed out")) {
clear_error("powermeter/CommunicationFault", "Communication timed out");
}
}
} catch (HttpClientError& client_error) {
is_initialized.store(false);
if (!this->error_state_monitor->is_error_active("powermeter/CommunicationFault",
"Communication timed out")) {
EVLOG_error << "Failed to communicate with the powermeter due to http error: "
<< client_error.what();
auto error =
this->error_factory->create_error("powermeter/CommunicationFault", "Communication timed out",
"This error is raised due to communication timeout");
raise_error(error);
}
} catch (const std::exception& e) {
is_initialized.store(false);
EVLOG_error << "Exception in cyclic IEM-DCR communication: " << e.what();
}
}
});
}
types::powermeter::TransactionStartResponse
powermeterImpl::handle_start_transaction(types::powermeter::TransactionReq& value) {
// your code for cmd start_transaction goes here
types::powermeter::TransactionStartResponse return_value;
start_transaction_running.store(true);
// Check preconditions
if (value.evse_id != mod->config.CI && value.evse_id.length() > 0) {
return_value.status = types::powermeter::TransactionRequestStatus::NOT_SUPPORTED;
return_value.error = "config: CI does not match evse_id. This is not allowed.";
EVLOG_error << "Aborted: " << *return_value.error;
return return_value;
}
if (is_initialized.load() == false) {
return_value.status = types::powermeter::TransactionRequestStatus::UNEXPECTED_ERROR;
return_value.error = "Init of communication not finished yet. Please try again later.";
EVLOG_error << "Aborted: " << *return_value.error;
return return_value;
}
if (dcr_status.load() != "0x0000, 0x00000000, 0x00, 0x00") {
return_value.status = types::powermeter::TransactionRequestStatus::UNEXPECTED_ERROR;
return_value.error = "IEM-DCR is in error state. XC: " + dcr_status.load();
EVLOG_error << "Aborted: " << *return_value.error;
return return_value;
}
// Perform action
try {
// Check if gw information is already set
if (this->controller->check_gw_is_empty()) {
return_value.status = types::powermeter::TransactionRequestStatus::UNEXPECTED_ERROR;
return_value.error = "Init seems to be missing. Re-Init triggered. Please try again later.";
is_initialized.store(false);
} else {
// Stop transaction (if a transaction is still running)
if (transaction_active.load() == true) {
this->controller->call_with_retry([this]() { this->controller->post_receipt("E"); },
mod->config.resilience_transaction_request_retries,
mod->config.resilience_transaction_request_retry_delay);
} else {
// Try to end transaction at least once.
try {
this->controller->post_receipt("E");
} catch (...) {
// Nothing to do here
}
}
// Wait for being surely in idle mode
std::this_thread::sleep_for(std::chrono::milliseconds(250));
// Create user
if ((static_cast<std::string>(value.identification_data.value_or(""))).length() <= 0) {
this->controller->call_with_retry(
[this, value]() {
this->controller->post_user(value.identification_status, value.identification_level,
value.identification_flags, value.identification_type,
value.transaction_id, value.tariff_text);
},
mod->config.resilience_transaction_request_retries,
mod->config.resilience_transaction_request_retry_delay);
} else {
this->controller->call_with_retry(
[this, value]() {
this->controller->post_user(value.identification_status, value.identification_level,
value.identification_flags, value.identification_type,
value.identification_data, value.tariff_text);
},
mod->config.resilience_transaction_request_retries,
mod->config.resilience_transaction_request_retry_delay);
}
// Start transaction
this->controller->call_with_retry([this]() { this->controller->post_receipt("B"); },
mod->config.resilience_transaction_request_retries,
mod->config.resilience_transaction_request_retry_delay);
// Prepare positive response
transaction_active.store(true);
return_value.status = types::powermeter::TransactionRequestStatus::OK;
return_value.error = "";
}
} catch (const std::exception& e) {
return_value.status = types::powermeter::TransactionRequestStatus::UNEXPECTED_ERROR;
return_value.error = e.what();
EVLOG_error << "Aborted: " << return_value.error.value_or("");
}
start_transaction_running.store(false);
return return_value;
}
types::powermeter::TransactionStopResponse powermeterImpl::handle_stop_transaction(std::string& transaction_id) {
// your code for cmd stop_transaction goes here
types::powermeter::TransactionStopResponse return_value;
stop_transaction_running.store(true);
if (is_initialized.load() == false) {
return_value.status = types::powermeter::TransactionRequestStatus::UNEXPECTED_ERROR;
return_value.error = "Init of communication not finished yet.";
EVLOG_error << "Aborted: " << *return_value.error;
} else if (transaction_active.load() == true) {
try {
// Stop transaction
this->controller->call_with_retry([this]() { this->controller->post_receipt("E"); },
mod->config.resilience_transaction_request_retries,
mod->config.resilience_transaction_request_retry_delay);
// Wait for signature calculation
std::this_thread::sleep_for(std::chrono::milliseconds(250));
// Read receipt
return_value.signed_meter_value =
this->controller->call_with_retry([this]() { return this->controller->get_receipt(); },
mod->config.resilience_transaction_request_retries,
mod->config.resilience_transaction_request_retry_delay);
// Prepare positive response
transaction_active.store(false);
return_value.status = types::powermeter::TransactionRequestStatus::OK;
return_value.error = "";
} catch (const std::exception& e) {
return_value.status = types::powermeter::TransactionRequestStatus::UNEXPECTED_ERROR;
return_value.error = e.what();
EVLOG_error << "Aborted: " << return_value.error.value_or("");
}
} else {
// No transaction running. So return last transaction (if available)
try {
return_value.signed_meter_value = this->controller->get_transaction();
return_value.status = types::powermeter::TransactionRequestStatus::OK;
return_value.error = "";
} catch (std::exception& e) {
return_value.status = types::powermeter::TransactionRequestStatus::UNEXPECTED_ERROR;
return_value.error = std::string(e.what()) + " Maybe no transaction to return?";
EVLOG_warning << "Aborted: " << return_value.error.value_or("");
}
}
stop_transaction_running.store(false);
return return_value;
}
void powermeterImpl::check_config() {
// Numeric range checks are aready covered by manifest minimum and maximum declaration
if (mod->config.ip_address.length() <= 0) {
EVLOG_error << "Incorrect module config: parameter ip_address is empty." << std::endl;
throw std::runtime_error("ip_address invalid. Please check configuration.");
}
if (mod->config.timezone.length() != 5) {
EVLOG_error
<< "Incorrect module config: parameter timezone has invalid length. 5 characters expected, e.g. +0200"
<< std::endl;
throw std::runtime_error("Timezone invalid. Please check configuration.");
}
if (mod->config.timezone[0] != '+' && mod->config.timezone[0] != '-') {
EVLOG_error << "Incorrect module config: parameter timezone has invalid format. It must begin with + or - char."
<< std::endl;
throw std::runtime_error("Timezone invalid. Please check configuration.");
}
if (mod->config.CT.length() <= 0) {
EVLOG_error << "Incorrect module config: parameter CT is empty." << std::endl;
throw std::runtime_error("CT invalid. Please check configuration.");
}
if (mod->config.CI.length() <= 0) {
EVLOG_error << "Incorrect module config: parameter CI is empty." << std::endl;
throw std::runtime_error("CI invalid. Please check configuration.");
}
}
} // namespace main
} // namespace module