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

1048 lines
49 KiB
C++

// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
#include "powermeterImpl.hpp"
#include "Timeout.hpp"
#include <fmt/core.h>
namespace {
inline std::uint16_t get_u16(const std::vector<std::uint8_t>& vec, std::uint8_t start_index) {
if (vec.size() < start_index + 1)
return 0;
return (static_cast<std::uint16_t>(vec[start_index + 1]) << 8) | static_cast<std::uint16_t>(vec[start_index]);
}
inline std::uint16_t get_u16(const std::vector<std::uint8_t>& vec) {
return get_u16(vec, 0);
}
inline std::uint32_t get_u32(const std::vector<std::uint8_t>& vec, std::uint8_t start_index) {
if (vec.size() < start_index + 3)
return 0;
return (static_cast<std::uint32_t>(vec[start_index + 3]) << 24) |
(static_cast<std::uint32_t>(vec[start_index + 2]) << 16) |
(static_cast<std::uint32_t>(vec[start_index + 1]) << 8) | static_cast<std::uint32_t>(vec[start_index]);
}
inline std::uint32_t get_u32(const std::vector<std::uint8_t>& vec) {
return get_u32(vec, 0);
}
inline std::uint64_t get_u64(const std::vector<std::uint8_t>& vec, std::uint8_t start_index) {
if (vec.size() < start_index + 7)
return 0;
return (static_cast<std::uint64_t>(vec[start_index + 7]) << 56) |
(static_cast<std::uint64_t>(vec[start_index + 6]) << 48) |
(static_cast<std::uint64_t>(vec[start_index + 5]) << 40) |
(static_cast<std::uint64_t>(vec[start_index + 4]) << 32) |
(static_cast<std::uint64_t>(vec[start_index + 3]) << 24) |
(static_cast<std::uint64_t>(vec[start_index + 2]) << 16) |
(static_cast<std::uint64_t>(vec[start_index + 1]) << 8) | static_cast<std::uint64_t>(vec[start_index]);
}
inline std::uint64_t get_u64(const std::vector<std::uint8_t>& vec) {
return get_u64(vec, 0);
}
inline std::string get_str(const std::vector<std::uint8_t>& vec, std::uint16_t start_index, std::uint16_t length) {
std::string str = "";
if ((start_index + length) <= vec.size()) {
for (std::uint16_t n = start_index; n < (start_index + length); n++) {
if (vec[n] == 0x00)
break;
str += vec[n];
}
}
return str;
}
} // namespace
namespace module {
namespace main {
void powermeterImpl::init() {
if (!serial_device.open_device(config.serial_port, config.baudrate, config.ignore_echo)) {
EVLOG_AND_THROW(Everest::EverestConfigError(fmt::format("Cannot open serial port {}.", config.serial_port)));
}
init_default_values();
request_device_type();
set_device_time();
}
void powermeterImpl::ready() {
std::thread([this] {
while (true) {
read_powermeter_values();
std::this_thread::sleep_for(std::chrono::seconds(1));
}
}).detach();
// create device_data publisher thread
if (config.publish_device_data) {
std::thread([this] {
while (true) {
read_device_data();
std::this_thread::sleep_for(std::chrono::seconds(1));
publish_device_data_topic();
std::this_thread::sleep_for(std::chrono::seconds(5));
}
}).detach();
}
// create device_diagnostics publisher thread
if (config.publish_device_diagnostics) {
std::thread([this] {
while (true) {
read_diagnostics_data();
std::this_thread::sleep_for(std::chrono::seconds(1));
publish_device_diagnostics_topic();
std::this_thread::sleep_for(std::chrono::seconds(10));
}
}).detach();
}
// create logging publisher thread
if (config.publish_device_diagnostics) {
std::thread([this] {
while (true) {
publish_logging_topic();
std::this_thread::sleep_for(std::chrono::seconds(20));
}
}).detach();
}
// create error diagnostics thread
if (config.publish_device_diagnostics) {
std::thread([this] {
while (true) {
if (error_diagnostics_target != 0) {
error_diagnostics(error_diagnostics_target);
error_diagnostics_target = 0;
std::this_thread::sleep_for(std::chrono::seconds(1));
}
}
}).detach();
}
}
void powermeterImpl::set_device_time() {
std::chrono::time_point<std::chrono::system_clock> timepoint = std::chrono::system_clock::now();
int8_t gmt_offset_quarters_of_an_hour = app_layer.get_utc_offset_in_quarter_hours(timepoint);
std::vector<std::uint8_t> set_device_time_cmd{};
app_layer.create_command_set_time(date::utc_clock::from_sys(timepoint), gmt_offset_quarters_of_an_hour,
set_device_time_cmd);
std::vector<std::uint8_t> slip_msg_set_device_time =
slip.package_single(config.powermeter_device_id, set_device_time_cmd);
serial_device.tx(slip_msg_set_device_time);
receive_response();
}
void powermeterImpl::set_device_charge_point_id(ast_app_layer::UserIdType id_type, std::string charge_point_id) {
std::vector<std::uint8_t> set_charge_point_id_cmd{};
app_layer.create_command_set_charge_point_id(id_type, charge_point_id, set_charge_point_id_cmd);
std::vector<std::uint8_t> slip_msg_set_charge_point_id =
slip.package_single(config.powermeter_device_id, set_charge_point_id_cmd);
serial_device.tx(slip_msg_set_charge_point_id);
receive_response();
}
void powermeterImpl::publish_device_data_topic() {
if (config.publish_device_data) {
json j;
to_json(j, device_data_obj);
std::string dev_data_topic = pm_last_values.meter_id.value() + std::string("/device_data");
mod->mqtt.publish(dev_data_topic, j.dump());
}
}
void powermeterImpl::publish_device_diagnostics_topic() {
if (config.publish_device_diagnostics) {
json j;
to_json(j, device_diagnostics_obj);
std::string dev_diagnostics_topic = pm_last_values.meter_id.value() + std::string("/device_diagnostics");
mod->mqtt.publish(dev_diagnostics_topic, j.dump());
}
}
void powermeterImpl::publish_logging_topic() {
if (config.publish_device_diagnostics) {
json j;
to_json(j, logging_obj);
std::string logging_topic = pm_last_values.meter_id.value() + std::string("/logging");
mod->mqtt.publish(logging_topic, j.dump());
}
}
void powermeterImpl::read_device_data() {
{
std::vector<std::uint8_t> get_time_cmd{};
app_layer.create_command_get_time(get_time_cmd);
std::vector<std::uint8_t> get_total_start_import_energy_cmd{};
app_layer.create_command_get_total_start_import_energy(get_total_start_import_energy_cmd);
std::vector<std::uint8_t> get_total_stop_import_energy_cmd{};
app_layer.create_command_get_total_stop_import_energy(get_total_stop_import_energy_cmd);
std::vector<std::uint8_t> get_total_start_export_energy_cmd{};
app_layer.create_command_get_total_start_export_energy(get_total_start_export_energy_cmd);
std::vector<std::uint8_t> get_total_stop_export_energy_cmd{};
app_layer.create_command_get_total_stop_export_energy(get_total_stop_export_energy_cmd);
std::vector<std::uint8_t> get_ocmf_stats_cmd{};
app_layer.create_command_get_ocmf_stats(get_ocmf_stats_cmd);
std::vector<std::uint8_t> get_last_transaction_ocmf_cmd{};
app_layer.create_command_get_last_transaction_ocmf(get_last_transaction_ocmf_cmd);
std::vector<std::uint8_t> get_ocmf_info_cmd{};
app_layer.create_command_get_ocmf_info(get_ocmf_info_cmd);
std::vector<std::uint8_t> get_ocmf_config_cmd{};
app_layer.create_command_get_ocmf_config(get_ocmf_config_cmd);
std::vector<std::uint8_t> slip_msg_read_device_data = slip.package_multi(
config.powermeter_device_id,
{get_time_cmd, get_total_start_import_energy_cmd, get_total_stop_import_energy_cmd,
get_total_start_export_energy_cmd, get_total_stop_export_energy_cmd,
// get_total_transaction_duration_cmd,
get_ocmf_stats_cmd, get_last_transaction_ocmf_cmd, get_ocmf_info_cmd, get_ocmf_config_cmd});
serial_device.tx(slip_msg_read_device_data);
receive_response();
}
{
std::vector<std::uint8_t> get_total_dev_import_energy_cmd{};
app_layer.create_command_get_total_dev_import_energy(get_total_dev_import_energy_cmd);
std::vector<std::uint8_t> get_total_dev_export_energy_cmd{};
app_layer.create_command_get_total_dev_export_energy(get_total_dev_export_energy_cmd);
std::vector<std::uint8_t> get_application_board_status_cmd{};
app_layer.create_command_get_application_board_status(get_application_board_status_cmd);
std::vector<std::uint8_t> slip_msg_read_device_data_2 = slip.package_multi(
config.powermeter_device_id,
{get_total_dev_import_energy_cmd, get_total_dev_export_energy_cmd, get_application_board_status_cmd});
serial_device.tx(slip_msg_read_device_data_2);
receive_response();
}
}
void powermeterImpl::get_device_public_key() {
{
std::vector<std::uint8_t> get_device_public_key_cmd{};
app_layer.create_command_get_meter_pubkey(get_device_public_key_cmd);
std::vector<std::uint8_t> get_device_public_key_asn1_cmd{};
app_layer.create_command_get_pubkey_asn1(get_device_public_key_asn1_cmd);
std::vector<std::uint8_t> get_device_public_key_str16_cmd{};
app_layer.create_command_get_pubkey_str16(get_device_public_key_str16_cmd);
std::vector<std::uint8_t> slip_msg_get_device_public_keys =
slip.package_multi(config.powermeter_device_id, {get_device_public_key_cmd, get_device_public_key_asn1_cmd,
get_device_public_key_str16_cmd});
serial_device.tx(slip_msg_get_device_public_keys);
receive_response();
}
}
void powermeterImpl::request_device_type() {
std::vector<std::uint8_t> data_vect{};
app_layer.create_command_get_device_type(data_vect);
std::vector<std::uint8_t> slip_msg_device_type = slip.package_single(config.powermeter_device_id, data_vect);
serial_device.tx(slip_msg_device_type);
receive_response();
}
void powermeterImpl::request_error_diagnostics(std::uint8_t addr) {
error_diagnostics_target = addr;
}
/* retrieve last error objects from device */
void powermeterImpl::error_diagnostics(std::uint8_t addr) {
std::vector<std::uint8_t> last_log_entry_cmd{};
app_layer.create_command_get_last_log_entry(last_log_entry_cmd);
std::vector<std::uint8_t> slip_msg_last_log_entry =
slip.package_single(config.powermeter_device_id, last_log_entry_cmd);
serial_device.tx(slip_msg_last_log_entry);
receive_response();
std::vector<std::uint8_t> last_system_errors_cmd{};
app_layer.create_command_get_errors(ast_app_layer::ErrorCategory::LAST, ast_app_layer::ErrorSource::SYSTEM,
last_system_errors_cmd);
category_requested = ast_app_layer::ErrorCategory::LAST;
source_requested = ast_app_layer::ErrorSource::SYSTEM;
std::vector<std::uint8_t> slip_msg_last_system_errors =
slip.package_single(config.powermeter_device_id, last_system_errors_cmd);
serial_device.tx(slip_msg_last_system_errors);
receive_response();
std::vector<std::uint8_t> last_critical_system_errors_cmd{};
app_layer.create_command_get_errors(ast_app_layer::ErrorCategory::LAST_CRITICAL, ast_app_layer::ErrorSource::SYSTEM,
last_critical_system_errors_cmd);
category_requested = ast_app_layer::ErrorCategory::LAST_CRITICAL;
source_requested = ast_app_layer::ErrorSource::SYSTEM;
std::vector<std::uint8_t> slip_msg_last_critical_system_errors =
slip.package_single(config.powermeter_device_id, last_critical_system_errors_cmd);
serial_device.tx(slip_msg_last_critical_system_errors);
receive_response();
std::vector<std::uint8_t> last_comm_errors_cmd{};
app_layer.create_command_get_errors(ast_app_layer::ErrorCategory::LAST, ast_app_layer::ErrorSource::COMMUNICATION,
last_comm_errors_cmd);
category_requested = ast_app_layer::ErrorCategory::LAST;
source_requested = ast_app_layer::ErrorSource::COMMUNICATION;
std::vector<std::uint8_t> slip_msg_last_communication_errors =
slip.package_single(config.powermeter_device_id, last_comm_errors_cmd);
serial_device.tx(slip_msg_last_communication_errors);
receive_response();
std::vector<std::uint8_t> last_critical_comm_errors_cmd{};
app_layer.create_command_get_errors(ast_app_layer::ErrorCategory::LAST_CRITICAL,
ast_app_layer::ErrorSource::COMMUNICATION, last_critical_comm_errors_cmd);
category_requested = ast_app_layer::ErrorCategory::LAST_CRITICAL;
source_requested = ast_app_layer::ErrorSource::COMMUNICATION;
std::vector<std::uint8_t> slip_msg_last_critical_communication_errors =
slip.package_single(config.powermeter_device_id, last_critical_comm_errors_cmd);
serial_device.tx(slip_msg_last_critical_communication_errors);
receive_response();
}
void powermeterImpl::read_diagnostics_data() {
// part 1 - basic info
{
std::vector<std::uint8_t> get_charge_point_id_cmd{};
app_layer.create_command_get_charge_point_id(get_charge_point_id_cmd);
std::vector<std::uint8_t> get_device_type_cmd{};
app_layer.create_command_get_device_type(get_device_type_cmd);
std::vector<std::uint8_t> get_hardware_version_cmd{};
app_layer.create_command_get_hardware_version(get_hardware_version_cmd);
std::vector<std::uint8_t> get_application_board_server_id_cmd{};
app_layer.create_command_get_application_board_server_id(get_application_board_server_id_cmd);
std::vector<std::uint8_t> get_application_board_mode_cmd{};
app_layer.create_command_get_application_board_mode(get_application_board_mode_cmd);
std::vector<std::uint8_t> slip_msg_get_diagnostics_data_1 = slip.package_multi(
config.powermeter_device_id, {get_charge_point_id_cmd, get_device_type_cmd, get_hardware_version_cmd,
get_application_board_server_id_cmd, get_application_board_mode_cmd});
serial_device.tx(slip_msg_get_diagnostics_data_1);
receive_response();
}
// part 2 - log stats
{
std::vector<std::uint8_t> get_log_stats_cmd{};
app_layer.create_command_get_log_stats(get_log_stats_cmd);
std::vector<std::uint8_t> slip_msg_get_diagnostics_data_2 =
slip.package_single(config.powermeter_device_id, get_log_stats_cmd);
serial_device.tx(slip_msg_get_diagnostics_data_2);
receive_response();
}
// part 3 - HW/SW info
{
std::vector<std::uint8_t> get_application_board_serial_number_cmd{};
app_layer.create_command_get_application_board_serial_number(get_application_board_serial_number_cmd);
std::vector<std::uint8_t> get_application_board_software_version_cmd{};
app_layer.create_command_get_application_board_software_version(get_application_board_software_version_cmd);
std::vector<std::uint8_t> get_application_board_fw_checksum_cmd{};
app_layer.create_command_get_application_board_fw_checksum(get_application_board_fw_checksum_cmd);
std::vector<std::uint8_t> get_application_board_fw_hash_cmd{};
app_layer.create_command_get_application_board_fw_hash(get_application_board_fw_hash_cmd);
std::vector<std::uint8_t> get_metering_board_software_version_cmd{};
app_layer.create_command_get_metering_board_software_version(get_metering_board_software_version_cmd);
std::vector<std::uint8_t> get_metering_board_fw_checksum_cmd{};
app_layer.create_command_get_metering_board_fw_checksum(get_metering_board_fw_checksum_cmd);
std::vector<std::uint8_t> get_ocmf_config_cmd{};
app_layer.create_command_get_ocmf_config(get_ocmf_config_cmd);
std::vector<std::uint8_t> slip_msg_get_diagnostics_data_3 = slip.package_multi(
config.powermeter_device_id,
{get_application_board_serial_number_cmd, get_application_board_software_version_cmd,
get_application_board_fw_checksum_cmd, get_application_board_fw_hash_cmd,
get_metering_board_software_version_cmd, get_metering_board_fw_checksum_cmd, get_ocmf_config_cmd});
serial_device.tx(slip_msg_get_diagnostics_data_3);
receive_response();
}
// part 4 - public key
get_device_public_key();
}
void powermeterImpl::read_powermeter_values() {
readRegisters();
pm_last_values.timestamp = Everest::Date::to_rfc3339(date::utc_clock::now());
}
void powermeterImpl::init_default_values() {
pm_last_values.timestamp = Everest::Date::to_rfc3339(date::utc_clock::now());
pm_last_values.meter_id = "AST_Powermeter_addr_" + std::to_string(config.powermeter_device_id);
pm_last_values.energy_Wh_import.total = 0.0f;
types::units::Energy energy_Wh;
energy_Wh.total = 0.0f;
pm_last_values.energy_Wh_export = energy_Wh;
types::units::Power power_W;
power_W.total = 0.0f;
pm_last_values.power_W = power_W;
types::units::Voltage voltage_V;
voltage_V.DC = 0.0f;
pm_last_values.voltage_V = voltage_V;
types::units::Current current_A;
current_A.DC = 0.0f;
pm_last_values.current_A = current_A;
}
void powermeterImpl::readRegisters() {
std::vector<std::uint8_t> get_voltage_cmd{};
app_layer.create_command_get_voltage(get_voltage_cmd);
std::vector<std::uint8_t> get_current_cmd{};
app_layer.create_command_get_current(get_current_cmd);
std::vector<std::uint8_t> get_import_power_cmd{};
app_layer.create_command_get_import_power(get_import_power_cmd);
std::vector<std::uint8_t> export_power_cmd{};
app_layer.create_command_get_export_power(export_power_cmd);
std::vector<std::uint8_t> get_total_power_cmd{};
app_layer.create_command_get_total_power(get_total_power_cmd);
std::vector<std::uint8_t> slip_msg_read_registers =
slip.package_multi(config.powermeter_device_id, {get_voltage_cmd, get_current_cmd, get_import_power_cmd,
export_power_cmd, get_total_power_cmd});
serial_device.tx(slip_msg_read_registers);
receive_response();
}
// ############################################################################################################################################
// ############################################################################################################################################
ast_app_layer::CommandResult powermeterImpl::process_response(const std::vector<std::uint8_t>& response_message) {
ast_app_layer::CommandResult response_status{ast_app_layer::CommandResult::OK};
size_t response_size = response_message.size();
// split into multiple command responses
std::uint8_t dest_addr = response_message.at(0);
std::uint16_t i = 1;
while ((i + 4) <= response_size) {
std::uint16_t part_cmd = (static_cast<std::uint16_t>(response_message.at(i + 1)) << 8) | response_message.at(i);
std::uint16_t part_len =
(static_cast<std::uint16_t>(response_message.at(i + 3)) << 8) | response_message.at(i + 2);
std::uint16_t part_data_len = part_len - 5;
ast_app_layer::CommandResult part_status =
static_cast<ast_app_layer::CommandResult>(response_message.at(i + 4));
if (response_status ==
ast_app_layer::CommandResult::OK) { // only update response status if not already error present
response_status = part_status;
}
if ((i + part_len - 1) <= response_size) {
std::vector<std::uint8_t> part_data((response_message.begin() + i + 5),
(response_message.begin() + i + part_len));
if (part_status != ast_app_layer::CommandResult::OK) {
EVLOG_error << "Powermeter at address " << static_cast<int>(dest_addr) << " ("
<< module::conversions::hexdump(dest_addr) << ")"
<< " has signaled an error (status: (" << static_cast<int>(part_status) << ") \""
<< ast_app_layer::command_result_to_string(part_status) << "\") at response "
<< module::conversions::hexdump(part_cmd) << " !";
// skip error diagnostics for transaction or error diagnostics related commands,
// request detailed error report for others
if ((part_cmd != static_cast<std::uint16_t>(ast_app_layer::CommandType::START_TRANSACTION)) &&
(part_cmd != static_cast<std::uint16_t>(ast_app_layer::CommandType::STOP_TRANSACTION)) &&
(part_cmd != static_cast<std::uint16_t>(ast_app_layer::CommandType::GET_LAST_LOG_ENTRY)) &&
(part_cmd != static_cast<std::uint16_t>(ast_app_layer::CommandType::GET_ERRORS)) &&
(part_cmd != static_cast<std::uint16_t>(ast_app_layer::CommandType::GET_LAST_OCMF))) {
EVLOG_info << "Retrieving diagnostics data for error at command "
<< module::conversions::hexdump(part_cmd) << "...";
request_error_diagnostics(dest_addr);
i += part_len; // skip remaining data and go to next command in message
continue;
}
}
// process response
switch (part_cmd) {
// operational values
case static_cast<int>(ast_app_layer::CommandType::START_TRANSACTION): {
start_transaction_msg_status = MessageStatus::RECEIVED;
start_transact_result = part_status;
EVLOG_info << "START_TRANSACTION received.";
} break;
case static_cast<int>(ast_app_layer::CommandType::STOP_TRANSACTION): {
stop_transaction_msg_status = MessageStatus::RECEIVED;
stop_transact_result = part_status;
EVLOG_info << "STOP_TRANSACTION received.";
} break;
case static_cast<int>(ast_app_layer::CommandType::TIME): {
if (part_data_len < 5)
break;
device_data_obj.utc_time_s = get_u32(part_data);
device_data_obj.gmt_offset_quarterhours = part_data[4];
} break;
case static_cast<int>(ast_app_layer::CommandType::GET_VOLTAGE_L1): {
if (part_data_len < 4)
break;
types::units::Voltage volt = pm_last_values.voltage_V.value();
volt.DC = (float)get_u32(part_data) / 100.0; // powermeter reports in 100 * [V]
pm_last_values.voltage_V = volt;
} break;
case static_cast<int>(ast_app_layer::CommandType::GET_CURRENT_L1): {
if (part_data_len < 4)
break;
types::units::Current amp = pm_last_values.current_A.value();
amp.DC = (float)get_u32(part_data) / 1000.0; // powermeter reports in [mA]
pm_last_values.current_A = amp;
} break;
case static_cast<int>(ast_app_layer::CommandType::GET_IMPORT_DEV_POWER): {
if (part_data_len < 4)
break;
types::units::Power power = pm_last_values.power_W.value();
power.total = (float)get_u32(part_data) / 100.0; // powermeter reports in [W * 100]
pm_last_values.power_W = power;
} break;
case static_cast<int>(ast_app_layer::CommandType::GET_EXPORT_DEV_POWER): {
EVLOG_info << "(GET_EXPORT_DEV_POWER) Not yet implemented. ["
<< (float)get_u32(part_data) / 100.0 /* powermeter reports in [W * 100] */ << " W]";
} break;
case static_cast<int>(ast_app_layer::CommandType::GET_TOTAL_DEV_POWER): {
EVLOG_info << "(GET_TOTAL_DEV_POWER) Not yet implemented. ["
<< (float)get_u32(part_data) / 100.0 /* powermeter reports in [W * 100] */ << " W]";
} break;
case static_cast<int>(ast_app_layer::CommandType::GET_TOTAL_IMPORT_DEV_ENERGY): {
if (part_data_len < 8)
break;
types::units::Energy energy_in = pm_last_values.energy_Wh_import;
energy_in.total = (float)get_u64(part_data) / 10.0; // powermeter reports in [Wh * 10]
pm_last_values.energy_Wh_import = energy_in;
device_data_obj.total_dev_import_energy_Wh =
get_u64(part_data) / 10.0; // powermeter reports in [Wh * 10]
} break;
case static_cast<int>(ast_app_layer::CommandType::GET_TOTAL_EXPORT_DEV_ENERGY): {
if (part_data_len < 8)
break;
types::units::Energy energy_out{};
if (pm_last_values.energy_Wh_export.has_value()) {
energy_out = pm_last_values.energy_Wh_export.value();
}
energy_out.total = (float)get_u64(part_data) / 10.0; // powermeter reports in [Wh * 10]
pm_last_values.energy_Wh_export = energy_out;
device_data_obj.total_dev_export_energy_Wh =
get_u64(part_data) / 10.0; // powermeter reports in [Wh * 10]
} break;
case static_cast<int>(ast_app_layer::CommandType::GET_TOTAL_START_IMPORT_DEV_ENERGY): {
if (part_data_len < 8)
break;
device_data_obj.total_start_import_energy_Wh =
get_u64(part_data) / 10.0; // powermeter reports in [Wh * 10]
} break;
case static_cast<int>(ast_app_layer::CommandType::GET_TOTAL_STOP_IMPORT_DEV_ENERGY): {
if (part_data_len < 8)
break;
device_data_obj.total_stop_import_energy_Wh =
get_u64(part_data) / 10.0; // powermeter reports in [Wh * 10]
} break;
case static_cast<int>(ast_app_layer::CommandType::GET_TOTAL_START_EXPORT_DEV_ENERGY): {
if (part_data_len < 8)
break;
device_data_obj.total_start_export_energy_Wh =
get_u64(part_data) / 10.0; // powermeter reports in [Wh * 10]
} break;
case static_cast<int>(ast_app_layer::CommandType::GET_TOTAL_STOP_EXPORT_DEV_ENERGY): {
if (part_data_len < 8)
break;
device_data_obj.total_stop_export_energy_Wh =
get_u64(part_data) / 10.0; // powermeter reports in [Wh * 10]
} break;
case static_cast<int>(ast_app_layer::CommandType::GET_TRANSACT_TOTAL_DURATION): {
if (part_data_len < 4)
break;
device_data_obj.total_transaction_duration_s = get_u32(part_data);
} break;
case static_cast<int>(ast_app_layer::CommandType::GET_PUBKEY_STR16): {
if (part_data_len < 130)
break;
device_diagnostics_obj.pubkey_str16_format = part_data[0];
device_diagnostics_obj.pubkey_str16 = module::conversions::hexdump(part_data, 1, 129);
} break;
case static_cast<int>(ast_app_layer::CommandType::GET_PUBKEY_ASN1): {
if (part_data_len < 176)
break;
device_diagnostics_obj.pubkey_asn1 = module::conversions::hexdump(part_data, 0, 176);
} break;
case static_cast<int>(ast_app_layer::CommandType::REQUEST_METER_PUBKEY): {
if (part_data_len < 65)
break;
device_diagnostics_obj.pubkey_format = part_data[0];
device_diagnostics_obj.pubkey = module::conversions::hexdump(part_data, 1, 64);
} break;
// diagnostics
case static_cast<int>(ast_app_layer::CommandType::OCMF_STATS): {
if (part_data_len < 16)
break;
device_data_obj.ocmf_stats.number_transactions = get_u32(part_data);
device_data_obj.ocmf_stats.timestamp_first_transaction = get_u32(part_data, 4);
device_data_obj.ocmf_stats.timestamp_last_transaction = get_u32(part_data, 8);
device_data_obj.ocmf_stats.max_number_of_transactions = get_u32(part_data, 12);
} break;
case static_cast<int>(ast_app_layer::CommandType::GET_OCMF): {
if (part_data_len < 16)
break;
device_data_obj.requested_ocmf = get_str(part_data, 0, part_data_len);
} break;
case static_cast<int>(ast_app_layer::CommandType::GET_LAST_OCMF): {
if (get_transaction_values_msg_status == MessageStatus::SENT) {
get_transaction_values_msg_status = MessageStatus::RECEIVED;
}
if (part_status == ast_app_layer::CommandResult::OK) {
device_data_obj.last_ocmf_transaction = get_str(part_data, 0, part_data_len);
last_ocmf_str = device_data_obj.last_ocmf_transaction;
} else {
last_ocmf_str = "";
}
} break;
case static_cast<int>(ast_app_layer::CommandType::OCMF_INFO): {
if (part_data_len < 1)
break;
// gateway_id
if (part_data_len < part_data[0])
break; // error, data too short
std::uint8_t length_gateway_id = part_data[0];
if (length_gateway_id > 18)
length_gateway_id = 18; // max length
if (length_gateway_id > 0) {
device_data_obj.ocmf_info.gateway_id = get_str(part_data, 1, length_gateway_id);
length_gateway_id++; // add length info byte for following calculations
} else {
device_data_obj.ocmf_info.gateway_id = "";
length_gateway_id = 1; // length info always requires at least one byte
}
// manufacturer
if (part_data_len < (length_gateway_id + part_data[length_gateway_id] + 1))
break; // error, data too short
std::uint8_t length_manufacturer = part_data[length_gateway_id];
if (length_manufacturer > 4)
length_manufacturer = 4; // max length
if (length_manufacturer > 0) {
device_data_obj.ocmf_info.manufacturer =
get_str(part_data, length_gateway_id + 1, length_manufacturer);
length_manufacturer++; // add length info byte for following calculations
} else {
device_data_obj.ocmf_info.manufacturer = "";
length_manufacturer = 1; // length info always requires at least one byte
}
// model
if (part_data_len <
(length_gateway_id + length_manufacturer + part_data[length_gateway_id + length_manufacturer]))
break; // error, data too short
std::uint8_t length_model = part_data[length_gateway_id + length_manufacturer];
if (length_model > 18)
length_model = 18; // max length
if (length_model > 0) {
device_data_obj.ocmf_info.model =
get_str(part_data, (length_gateway_id + length_manufacturer + 1), length_model);
} else {
device_data_obj.ocmf_info.model = "";
}
} break;
case static_cast<int>(ast_app_layer::CommandType::OCMF_CONFIG): {
if (part_data_len < 16)
break;
device_diagnostics_obj.ocmf_config_table.clear();
for (std::uint16_t n = 0; n < part_data_len; n++) {
device_diagnostics_obj.ocmf_config_table.push_back(part_data[n]);
}
} break;
case static_cast<int>(ast_app_layer::CommandType::CHARGE_POINT_ID): {
if (part_data_len < 14)
break;
device_diagnostics_obj.charge_point_id_type = part_data[0];
device_diagnostics_obj.charge_point_id = get_str(part_data, 1, 13);
} break;
case static_cast<int>(ast_app_layer::CommandType::GET_ERRORS): {
if (part_data_len < 50)
break;
std::uint8_t j = 0;
for (std::uint8_t i = 0; i < 5; i++) {
j = i * 10;
logging_obj.source[(std::uint8_t)source_requested]
.category[(std::uint8_t)category_requested]
.error[i]
.id = get_u32(part_data, j);
logging_obj.source[(std::uint8_t)source_requested]
.category[(std::uint8_t)category_requested]
.error[i]
.priority = get_u16(part_data, j + 4);
logging_obj.source[(std::uint8_t)source_requested]
.category[(std::uint8_t)category_requested]
.error[i]
.counter = get_u32(part_data, j + 6);
EVLOG_info << "Error #" << static_cast<int>(i) << " for source ("
<< static_cast<int>(source_requested) << ") and category ("
<< static_cast<int>(category_requested) << "):\nID: " << get_u32(part_data, j)
<< "\nPrio: " << get_u16(part_data, j + 4)
<< "\nCounter: " << get_u32(part_data, j + 6);
}
} break;
case static_cast<int>(ast_app_layer::CommandType::GET_LOG_STATS): {
if (part_data_len < 16)
break;
device_diagnostics_obj.log_stats.number_log_entries = get_u32(part_data);
device_diagnostics_obj.log_stats.timestamp_first_log = get_u32(part_data, 4);
device_diagnostics_obj.log_stats.timestamp_last_log = get_u32(part_data, 8);
device_diagnostics_obj.log_stats.max_number_of_logs = get_u32(part_data, 12);
} break;
case static_cast<int>(ast_app_layer::CommandType::GET_LAST_LOG_ENTRY): {
if (part_data_len < 104)
break;
logging_obj.last_log.type = static_cast<ast_app_layer::LogType>(part_data[0]);
logging_obj.last_log.second_index = get_u32(part_data, 1);
logging_obj.last_log.utc_time = get_u32(part_data, 5);
logging_obj.last_log.utc_offset = part_data[9];
for (std::uint8_t n = 10; n < 20; n++) {
logging_obj.last_log.old_value.push_back(part_data[n]);
}
for (std::uint8_t n = 20; n < 30; n++) {
logging_obj.last_log.new_value.push_back(part_data[n]);
}
for (std::uint8_t n = 30; n < 40; n++) {
logging_obj.last_log.server_id.push_back(part_data[n]);
}
for (std::uint8_t n = 40; n < 104; n++) {
logging_obj.last_log.signature.push_back(part_data[n]);
}
} break;
// device parameters
case static_cast<int>(ast_app_layer::CommandType::AB_MODE_SET): {
if (part_data_len < 1)
break;
device_diagnostics_obj.app_board.mode = part_data[0];
} break;
case static_cast<int>(ast_app_layer::CommandType::AB_HW_VERSION): {
std::uint16_t delimiter_pos = 0;
for (std::uint16_t i = 0; i < part_data_len; i++) {
if (part_data[i] == '|') {
delimiter_pos = i;
}
}
device_diagnostics_obj.app_board.hw_ver = get_str(part_data, 0, delimiter_pos);
device_diagnostics_obj.m_board.hw_ver =
get_str(part_data, delimiter_pos + 1, (part_data_len - delimiter_pos - 1));
} break;
case static_cast<int>(ast_app_layer::CommandType::AB_SERVER_ID): {
if (part_data_len < 10)
break;
device_diagnostics_obj.app_board.server_id = get_str(part_data, 0, 10);
} break;
case static_cast<int>(ast_app_layer::CommandType::AB_SERIAL_NR): {
if (part_data_len < 4)
break;
device_diagnostics_obj.app_board.serial_number = get_u32(part_data);
} break;
case static_cast<int>(ast_app_layer::CommandType::AB_SW_VERSION): {
if (part_data_len < 20)
break;
device_diagnostics_obj.app_board.sw_ver = get_str(part_data, 0, 20);
} break;
case static_cast<int>(ast_app_layer::CommandType::AB_FW_CHECKSUM): {
if (part_data_len < 2)
break;
device_diagnostics_obj.app_board.fw_crc = get_u16(part_data);
} break;
case static_cast<int>(ast_app_layer::CommandType::AB_FW_HASH): {
if (part_data_len < 2)
break;
device_diagnostics_obj.app_board.fw_hash = get_u16(part_data);
} break;
case static_cast<int>(ast_app_layer::CommandType::MB_SW_VERSION): {
if (part_data_len < 20)
break;
device_diagnostics_obj.m_board.sw_ver = get_str(part_data, 0, 20);
} break;
case static_cast<int>(ast_app_layer::CommandType::MB_FW_CHECKSUM): {
if (part_data_len < 2)
break;
device_diagnostics_obj.m_board.fw_crc = get_u16(part_data);
} break;
case static_cast<int>(ast_app_layer::CommandType::AB_DEVICE_TYPE): {
if (part_data_len < 18)
break;
device_diagnostics_obj.app_board.type = get_str(part_data, 0, 18);
} break;
case static_cast<int>(ast_app_layer::CommandType::AB_STATUS): {
if (part_data_len < 8)
break;
device_data_obj.ab_status = get_u64(part_data);
} break;
// not (yet) implemented
case static_cast<int>(ast_app_layer::CommandType::RESET_DC_METER):
case static_cast<int>(ast_app_layer::CommandType::MEASUREMENT_MODE):
case static_cast<int>(ast_app_layer::CommandType::GET_NORMAL_VOLTAGE):
case static_cast<int>(ast_app_layer::CommandType::GET_NORMAL_CURRENT):
case static_cast<int>(ast_app_layer::CommandType::GET_MAX_CURRENT):
case static_cast<int>(ast_app_layer::CommandType::LINE_LOSS_IMPEDANCE):
case static_cast<int>(ast_app_layer::CommandType::LINE_LOSS_MEAS_MODE):
case static_cast<int>(ast_app_layer::CommandType::TEMPERATURE):
case static_cast<int>(ast_app_layer::CommandType::METER_BUS_ADDR):
case static_cast<int>(ast_app_layer::CommandType::GET_OCMF_REVERSE):
case static_cast<int>(ast_app_layer::CommandType::GET_PUBKEY_BIN):
case static_cast<int>(ast_app_layer::CommandType::GET_TRANSACT_IMPORT_LINE_LOSS_ENERGY):
case static_cast<int>(ast_app_layer::CommandType::GET_TRANSACT_EXPORT_LINE_LOSS_ENERGY):
case static_cast<int>(ast_app_layer::CommandType::GET_TRANSACT_TOTAL_IMPORT_DEV_ENERGY):
case static_cast<int>(ast_app_layer::CommandType::GET_TRANSACT_TOTAL_EXPORT_DEV_ENERGY):
case static_cast<int>(ast_app_layer::CommandType::GET_TRANSACT_TOTAL_IMPORT_MAINS_ENERGY):
case static_cast<int>(ast_app_layer::CommandType::GET_TRANSACT_TOTAL_EXPORT_MAINS_ENERGY):
case static_cast<int>(ast_app_layer::CommandType::GET_TOTAL_START_IMPORT_LINE_LOSS_ENERGY):
case static_cast<int>(ast_app_layer::CommandType::GET_TOTAL_START_EXPORT_LINE_LOSS_ENERGY):
case static_cast<int>(ast_app_layer::CommandType::GET_TOTAL_START_IMPORT_MAINS_ENERGY):
case static_cast<int>(ast_app_layer::CommandType::GET_TOTAL_START_EXPORT_MAINS_ENERGY):
case static_cast<int>(ast_app_layer::CommandType::GET_TOTAL_STOP_IMPORT_LINE_LOSS_ENERGY):
case static_cast<int>(ast_app_layer::CommandType::GET_TOTAL_STOP_EXPORT_LINE_LOSS_ENERGY):
case static_cast<int>(ast_app_layer::CommandType::GET_TOTAL_STOP_IMPORT_MAINS_ENERGY):
case static_cast<int>(ast_app_layer::CommandType::GET_TOTAL_STOP_EXPORT_MAINS_ENERGY):
case static_cast<int>(ast_app_layer::CommandType::GET_LOG_ENTRY):
case static_cast<int>(ast_app_layer::CommandType::GET_LOG_ENTRY_REVERSE):
case static_cast<int>(ast_app_layer::CommandType::REGISTER_DISPLAY_PUBKEY):
case static_cast<int>(ast_app_layer::CommandType::REQUEST_CHALLENGE):
case static_cast<int>(ast_app_layer::CommandType::SET_SIGNED_CHALLENGE):
case static_cast<int>(ast_app_layer::CommandType::GET_TOTAL_IMPORT_MAINS_ENERGY):
case static_cast<int>(ast_app_layer::CommandType::GET_TOTAL_EXPORT_MAINS_ENERGY):
case static_cast<int>(ast_app_layer::CommandType::GET_TOTAL_IMPORT_MAINS_POWER):
case static_cast<int>(ast_app_layer::CommandType::GET_TOTAL_EXPORT_MAINS_POWER):
case static_cast<int>(ast_app_layer::CommandType::GET_DEV_VOLTAGE_L1):
case static_cast<int>(ast_app_layer::CommandType::GET_IMPORT_LINE_LOSS_POWER):
case static_cast<int>(ast_app_layer::CommandType::GET_EXPORT_LINE_LOSS_POWER):
case static_cast<int>(ast_app_layer::CommandType::GET_TOTAL_IMPORT_LINE_LOSS_ENERGY):
case static_cast<int>(ast_app_layer::CommandType::GET_TOTAL_EXPORT_LINE_LOSS_ENERGY):
case static_cast<int>(ast_app_layer::CommandType::GET_SECOND_INDEX):
case static_cast<int>(ast_app_layer::CommandType::GET_PUBKEY_STR32):
case static_cast<int>(ast_app_layer::CommandType::GET_PUBKEY_CSTR16):
case static_cast<int>(ast_app_layer::CommandType::GET_PUBKEY_CSTR32):
case static_cast<int>(ast_app_layer::CommandType::REPEAT_DATA):
case static_cast<int>(ast_app_layer::CommandType::AB_DMC):
case static_cast<int>(ast_app_layer::CommandType::AB_PROD_DATE):
case static_cast<int>(ast_app_layer::CommandType::SET_REQUEST_CHALLENGE): {
EVLOG_error << "Command not (yet) implemented. (" << module::conversions::hexdump(part_cmd) << ")";
} break;
default: {
EVLOG_error << "Command ID invalid. (" << module::conversions::hexdump(part_cmd) << ")";
} break;
}
} else {
EVLOG_error << "Error: Malformed data.";
}
i += part_len;
}
// publish powermeter values
publish_powermeter(pm_last_values);
return response_status;
}
ast_app_layer::CommandResult powermeterImpl::receive_response() {
ast_app_layer::CommandResult retval = ast_app_layer::CommandResult::OK;
std::vector<std::uint8_t> response{};
response.reserve(ast_app_layer::PM_AST_MAX_RX_LENGTH);
serial_device.rx(response, ast_app_layer::PM_AST_SERIAL_RX_INITIAL_TIMEOUT_MS,
ast_app_layer::PM_AST_SERIAL_RX_WITHIN_MESSAGE_TIMEOUT_MS);
EVLOG_critical << "\n\nRECEIVE: " << module::conversions::hexdump(response) << " length: " << response.size()
<< "\n\n";
if (response.size() >= 5) {
ast_app_layer::CommandResult result{};
slip.unpack(response, config.powermeter_device_id);
while (slip.get_message_counter() > 0) {
std::vector<std::uint8_t> message_from_queue = slip.retrieve_single_message();
result = process_response(message_from_queue);
if (result !=
ast_app_layer::CommandResult::OK) { // always report (at least one) error instead of OK, if available
retval = result;
}
}
} else {
EVLOG_info << "Received partial message. Skipping. [" << module::conversions::hexdump(response) << "]";
return ast_app_layer::CommandResult::COMMUNICATION_FAILED;
}
return retval;
}
// ############################################################################################################################################
// ############################################################################################################################################
// ############################################################################################################################################
types::powermeter::TransactionStartResponse
powermeterImpl::handle_start_transaction(types::powermeter::TransactionReq& value) {
types::powermeter::TransactionStartResponse r;
start_transact_result = ast_app_layer::CommandResult::PENDING;
ast_app_layer::UserIdStatus user_id_status = ast_app_layer::UserIdStatus::USER_NOT_ASSIGNED;
if (value.identification_status == types::powermeter::OCMFUserIdentificationStatus::ASSIGNED) {
user_id_status = ast_app_layer::UserIdStatus::USER_ASSIGNED;
}
ast_app_layer::UserIdType user_id_type = ast_app_layer::UserIdType::NONE;
std::string user_id_data = "unidentified_user";
if (value.identification_data.has_value()) {
user_id_data = value.identification_data.value();
}
std::vector<std::uint8_t> data_vect{};
app_layer.create_command_start_transaction(user_id_status, user_id_type, user_id_data,
config.gmt_offset_quarter_hours, data_vect);
std::vector<std::uint8_t> slip_msg_start_transaction = slip.package_single(config.powermeter_device_id, data_vect);
serial_device.tx(slip_msg_start_transaction);
start_transaction_msg_status = MessageStatus::SENT;
Timeout timeout(TIMEOUT_2s);
while (start_transaction_msg_status != MessageStatus::RECEIVED) {
receive_response();
if (timeout.reached()) {
start_transact_result = ast_app_layer::CommandResult::TIMEOUT;
break;
}
}
if (start_transact_result != ast_app_layer::CommandResult::OK) {
r.status = types::powermeter::TransactionRequestStatus::UNEXPECTED_ERROR;
} else {
r.status = types::powermeter::TransactionRequestStatus::OK;
}
return r;
}
types::powermeter::TransactionStopResponse powermeterImpl::handle_stop_transaction(std::string& transaction_id) {
types::powermeter::TransactionStopResponse r;
stop_transact_result = ast_app_layer::CommandResult::PENDING;
std::vector<std::uint8_t> data_vect{};
app_layer.create_command_stop_transaction(data_vect);
std::vector<std::uint8_t> slip_msg_stop_transaction = slip.package_single(config.powermeter_device_id, data_vect);
serial_device.tx(slip_msg_stop_transaction);
stop_transaction_msg_status = MessageStatus::SENT;
Timeout timeout(TIMEOUT_2s);
while (stop_transaction_msg_status != MessageStatus::RECEIVED) {
receive_response();
if (timeout.reached()) {
stop_transact_result = ast_app_layer::CommandResult::TIMEOUT;
break;
}
}
if (stop_transact_result != ast_app_layer::CommandResult::OK) {
r.status = types::powermeter::TransactionRequestStatus::UNEXPECTED_ERROR;
} else {
std::string ocmf_result = get_meter_ocmf();
std::string error_substring{"Error: command failed with code "};
if (ocmf_result.find(error_substring) != std::string::npos) {
r.status = types::powermeter::TransactionRequestStatus::UNEXPECTED_ERROR;
} else {
r.status = types::powermeter::TransactionRequestStatus::OK;
r.signed_meter_value = types::units_signed::SignedMeterValue{ocmf_result, "", "OCMF"};
}
}
return r;
}
std::string powermeterImpl::get_meter_ocmf() {
std::vector<std::uint8_t> data_vect{};
app_layer.create_command_get_last_transaction_ocmf(data_vect);
std::vector<std::uint8_t> slip_msg_get_last_ocmf = slip.package_single(config.powermeter_device_id, data_vect);
serial_device.tx(slip_msg_get_last_ocmf);
get_transaction_values_msg_status = MessageStatus::SENT;
Timeout timeout(TIMEOUT_2s);
while (get_transaction_values_msg_status != MessageStatus::RECEIVED) {
receive_response();
if (timeout.reached()) {
stop_transact_result = ast_app_layer::CommandResult::TIMEOUT;
break;
}
}
if (stop_transact_result != ast_app_layer::CommandResult::OK) {
std::stringstream ss;
ss << "Error: command failed with code " << int(stop_transact_result) << ". ("
<< command_result_to_string(stop_transact_result) << ")";
return ss.str();
}
return last_ocmf_str;
}
} // namespace main
} // namespace module