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
This commit is contained in:
@@ -0,0 +1,16 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include "AST_DC650.hpp"
|
||||
|
||||
namespace module {
|
||||
|
||||
void AST_DC650::init() {
|
||||
invoke_init(*p_main);
|
||||
}
|
||||
|
||||
void AST_DC650::ready() {
|
||||
invoke_ready(*p_main);
|
||||
}
|
||||
|
||||
} // namespace module
|
||||
@@ -0,0 +1,60 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
#ifndef AST_DC650_HPP
|
||||
#define AST_DC650_HPP
|
||||
|
||||
//
|
||||
// AUTO GENERATED - MARKED REGIONS WILL BE KEPT
|
||||
// template version 2
|
||||
//
|
||||
|
||||
#include "ld-ev.hpp"
|
||||
|
||||
// headers for provided interface implementations
|
||||
#include <generated/interfaces/powermeter/Implementation.hpp>
|
||||
|
||||
// ev@4bf81b14-a215-475c-a1d3-0a484ae48918:v1
|
||||
// insert your custom include headers here
|
||||
// ev@4bf81b14-a215-475c-a1d3-0a484ae48918:v1
|
||||
|
||||
namespace module {
|
||||
|
||||
struct Conf {};
|
||||
|
||||
class AST_DC650 : public Everest::ModuleBase {
|
||||
public:
|
||||
AST_DC650() = delete;
|
||||
AST_DC650(const ModuleInfo& info, Everest::MqttProvider& mqtt_provider, std::unique_ptr<powermeterImplBase> p_main,
|
||||
Conf& config) :
|
||||
ModuleBase(info), mqtt(mqtt_provider), p_main(std::move(p_main)), config(config){};
|
||||
|
||||
Everest::MqttProvider& mqtt;
|
||||
const std::unique_ptr<powermeterImplBase> p_main;
|
||||
const Conf& config;
|
||||
|
||||
// ev@1fce4c5e-0ab8-41bb-90f7-14277703d2ac:v1
|
||||
// insert your public definitions here
|
||||
// ev@1fce4c5e-0ab8-41bb-90f7-14277703d2ac:v1
|
||||
|
||||
protected:
|
||||
// ev@4714b2ab-a24f-4b95-ab81-36439e1478de:v1
|
||||
// insert your protected definitions here
|
||||
// ev@4714b2ab-a24f-4b95-ab81-36439e1478de:v1
|
||||
|
||||
private:
|
||||
friend class LdEverest;
|
||||
void init();
|
||||
void ready();
|
||||
|
||||
// ev@211cfdbe-f69a-4cd6-a4ec-f8aaa3d1b6c8:v1
|
||||
// insert your private definitions here
|
||||
// ev@211cfdbe-f69a-4cd6-a4ec-f8aaa3d1b6c8:v1
|
||||
};
|
||||
|
||||
// ev@087e516b-124c-48df-94fb-109508c7cda9:v1
|
||||
// insert other definitions here
|
||||
// ev@087e516b-124c-48df-94fb-109508c7cda9:v1
|
||||
|
||||
} // namespace module
|
||||
|
||||
#endif // AST_DC650_HPP
|
||||
@@ -0,0 +1,34 @@
|
||||
#
|
||||
# AUTO GENERATED - MARKED REGIONS WILL BE KEPT
|
||||
# template version 3
|
||||
#
|
||||
|
||||
# module setup:
|
||||
# - ${MODULE_NAME}: module name
|
||||
ev_setup_cpp_module()
|
||||
|
||||
# ev@bcc62523-e22b-41d7-ba2f-825b493a3c97:v1
|
||||
# insert your custom targets and additional config variables here
|
||||
target_sources(${MODULE_NAME}
|
||||
PRIVATE
|
||||
slip_protocol.cpp
|
||||
serial_device.cpp
|
||||
ast_app_layer.cpp
|
||||
diagnostics.cpp
|
||||
)
|
||||
|
||||
target_compile_features(${MODULE_NAME} PUBLIC cxx_std_17)
|
||||
# ev@bcc62523-e22b-41d7-ba2f-825b493a3c97:v1
|
||||
|
||||
target_sources(${MODULE_NAME}
|
||||
PRIVATE
|
||||
"main/powermeterImpl.cpp"
|
||||
)
|
||||
|
||||
# ev@c55432ab-152c-45a9-9d2e-7281d50c69c3:v1
|
||||
# insert other things like install cmds etc here
|
||||
target_link_libraries(${MODULE_NAME} PRIVATE
|
||||
everest::framework
|
||||
everest::crc
|
||||
)
|
||||
# ev@c55432ab-152c-45a9-9d2e-7281d50c69c3:v1
|
||||
@@ -0,0 +1,28 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#ifndef TIMEOUT_HPP
|
||||
#define TIMEOUT_HPP
|
||||
|
||||
#include <chrono>
|
||||
using namespace std::chrono;
|
||||
|
||||
/*
|
||||
Simple helper class for a timeout
|
||||
*/
|
||||
|
||||
class Timeout {
|
||||
public:
|
||||
explicit Timeout(milliseconds _t) : t(_t), start(steady_clock::now()) {
|
||||
}
|
||||
|
||||
bool reached() {
|
||||
return (steady_clock::now() - start) > t;
|
||||
}
|
||||
|
||||
private:
|
||||
milliseconds t;
|
||||
time_point<steady_clock> start;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,375 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include "ast_app_layer.hpp"
|
||||
|
||||
#include <cstring>
|
||||
#include <endian.h>
|
||||
#include <errno.h>
|
||||
#include <everest/logging.hpp>
|
||||
#include <fcntl.h>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <sys/select.h>
|
||||
#include <sys/time.h>
|
||||
|
||||
#include <fmt/core.h>
|
||||
|
||||
namespace ast_app_layer {
|
||||
|
||||
uint32_t timepoint_to_uint32(date::utc_clock::time_point timepoint) {
|
||||
return static_cast<uint32_t>(
|
||||
std::chrono::duration_cast<std::chrono::seconds>(timepoint.time_since_epoch()).count());
|
||||
}
|
||||
|
||||
void insert_u16_as_u8s(std::vector<std::uint8_t>& vec, std::uint16_t u16) {
|
||||
std::uint8_t upper = static_cast<std::uint8_t>((u16 >> 8) & 0x00FF);
|
||||
std::uint8_t lower = static_cast<std::uint8_t>(u16 & 0x00FF);
|
||||
vec.push_back(lower);
|
||||
vec.push_back(upper);
|
||||
}
|
||||
|
||||
void insert_u32_as_u8s(std::vector<std::uint8_t>& vec, std::uint32_t u32) {
|
||||
vec.push_back(static_cast<std::uint8_t>(u32 & 0x000000FF));
|
||||
vec.push_back(static_cast<std::uint8_t>((u32 >> 8) & 0x000000FF));
|
||||
vec.push_back(static_cast<std::uint8_t>((u32 >> 16) & 0x000000FF));
|
||||
vec.push_back(static_cast<std::uint8_t>((u32 >> 24) & 0x000000FF));
|
||||
}
|
||||
|
||||
std::vector<std::uint8_t> AstAppLayer::create_command(ast_app_layer::Command cmd) {
|
||||
std::vector<std::uint8_t> command_data{};
|
||||
|
||||
insert_u16_as_u8s(command_data, static_cast<std::uint16_t>(cmd.type));
|
||||
insert_u16_as_u8s(command_data, static_cast<std::uint16_t>(cmd.length));
|
||||
command_data.push_back((uint8_t)ast_app_layer::CommandStatus::OK);
|
||||
|
||||
for (uint16_t i = 0; i < cmd.data.size(); i++) {
|
||||
command_data.push_back(cmd.data[i]);
|
||||
}
|
||||
return command_data;
|
||||
}
|
||||
|
||||
std::vector<std::uint8_t> AstAppLayer::create_simple_command(ast_app_layer::CommandType cmd_type) {
|
||||
ast_app_layer::Command cmd{};
|
||||
|
||||
cmd.type = cmd_type;
|
||||
cmd.length = 0x0005;
|
||||
cmd.status = ast_app_layer::CommandStatus::OK;
|
||||
|
||||
return create_command(cmd);
|
||||
}
|
||||
|
||||
int8_t AstAppLayer::get_utc_offset_in_quarter_hours(
|
||||
const std::chrono::time_point<std::chrono::system_clock>& timepoint_system_clock) {
|
||||
std::stringstream offset;
|
||||
std::int8_t offset_quarterhours = 0;
|
||||
|
||||
auto tm = std::chrono::system_clock::to_time_t(timepoint_system_clock);
|
||||
offset << std::put_time(std::localtime(&tm), "%z");
|
||||
int offset_int = std::stoi(offset.str());
|
||||
|
||||
int offset_h = offset_int / 100;
|
||||
int offset_remaining = offset_int % 100; // in case of timezones that are not full-hour offsets of UTC
|
||||
if (offset_remaining != 0) {
|
||||
std::int8_t offset_remaining_extra_hour = offset_remaining / 60;
|
||||
if (offset_remaining_extra_hour != 0) {
|
||||
offset_quarterhours += offset_remaining_extra_hour * 4; // can be positive or negative
|
||||
offset_remaining -= offset_remaining_extra_hour * 4;
|
||||
}
|
||||
std::int8_t offset_remaining_quarterhours = offset_remaining / 15;
|
||||
offset_quarterhours += offset_remaining_quarterhours;
|
||||
}
|
||||
offset_quarterhours += offset_h * 4;
|
||||
|
||||
return offset_quarterhours;
|
||||
}
|
||||
|
||||
void AstAppLayer::create_command_start_transaction(ast_app_layer::UserIdStatus user_id_status,
|
||||
ast_app_layer::UserIdType user_id_type,
|
||||
const std::string& user_id_data,
|
||||
std::int8_t gmt_offset_quarter_hours,
|
||||
std::vector<std::uint8_t>& command_data) {
|
||||
ast_app_layer::Command cmd{};
|
||||
|
||||
cmd.type = ast_app_layer::CommandType::START_TRANSACTION;
|
||||
cmd.length = 0x0034;
|
||||
cmd.status = ast_app_layer::CommandStatus::OK;
|
||||
|
||||
insert_u32_as_u8s(cmd.data, (timepoint_to_uint32(date::utc_clock::now())));
|
||||
cmd.data.push_back(
|
||||
static_cast<std::uint8_t>(gmt_offset_quarter_hours)); // GMT offset in quarters of an hour, e.g. 0x08 = +2h
|
||||
cmd.data.push_back(static_cast<std::uint8_t>(user_id_status));
|
||||
cmd.data.push_back(static_cast<std::uint8_t>(user_id_type));
|
||||
|
||||
std::uint8_t byte_count = 0;
|
||||
for (std::uint8_t databyte : user_id_data) { // push up to 40 characters of user id name into command
|
||||
cmd.data.push_back(databyte);
|
||||
byte_count++;
|
||||
if (byte_count >= 40)
|
||||
break;
|
||||
}
|
||||
while (byte_count < 40) { // fill remaining user id name characters with zeros
|
||||
cmd.data.push_back(0x00);
|
||||
byte_count++;
|
||||
}
|
||||
|
||||
command_data = create_command(cmd);
|
||||
}
|
||||
|
||||
void AstAppLayer::create_command_stop_transaction(std::vector<std::uint8_t>& command_data) {
|
||||
command_data = create_simple_command(ast_app_layer::CommandType::STOP_TRANSACTION);
|
||||
}
|
||||
|
||||
void AstAppLayer::create_command_get_time(std::vector<std::uint8_t>& command_data) {
|
||||
command_data = create_simple_command(ast_app_layer::CommandType::TIME);
|
||||
}
|
||||
|
||||
void AstAppLayer::create_command_set_time(date::utc_clock::time_point timepoint,
|
||||
std::int8_t gmt_offset_quarters_of_an_hour,
|
||||
std::vector<std::uint8_t>& command_data) {
|
||||
ast_app_layer::Command cmd{};
|
||||
|
||||
cmd.type = ast_app_layer::CommandType::TIME;
|
||||
cmd.length = 0x000A;
|
||||
cmd.status = ast_app_layer::CommandStatus::OK;
|
||||
|
||||
insert_u32_as_u8s(cmd.data, timepoint_to_uint32(timepoint));
|
||||
cmd.data.push_back(gmt_offset_quarters_of_an_hour);
|
||||
|
||||
command_data = create_command(cmd);
|
||||
}
|
||||
|
||||
void AstAppLayer::create_command_get_voltage(std::vector<std::uint8_t>& command_data) {
|
||||
command_data = create_simple_command(ast_app_layer::CommandType::GET_VOLTAGE_L1);
|
||||
}
|
||||
|
||||
void AstAppLayer::create_command_get_current(std::vector<std::uint8_t>& command_data) {
|
||||
command_data = create_simple_command(ast_app_layer::CommandType::GET_CURRENT_L1);
|
||||
}
|
||||
|
||||
void AstAppLayer::create_command_get_import_power(std::vector<std::uint8_t>& command_data) {
|
||||
command_data = create_simple_command(ast_app_layer::CommandType::GET_IMPORT_DEV_POWER);
|
||||
}
|
||||
|
||||
void AstAppLayer::create_command_get_export_power(std::vector<std::uint8_t>& command_data) {
|
||||
command_data = create_simple_command(ast_app_layer::CommandType::GET_EXPORT_DEV_POWER);
|
||||
}
|
||||
|
||||
void AstAppLayer::create_command_get_total_dev_import_energy(std::vector<std::uint8_t>& command_data) {
|
||||
command_data = create_simple_command(ast_app_layer::CommandType::GET_TOTAL_IMPORT_DEV_ENERGY);
|
||||
}
|
||||
|
||||
void AstAppLayer::create_command_get_total_dev_export_energy(std::vector<std::uint8_t>& command_data) {
|
||||
command_data = create_simple_command(ast_app_layer::CommandType::GET_TOTAL_EXPORT_DEV_ENERGY);
|
||||
}
|
||||
|
||||
void AstAppLayer::create_command_get_total_power(std::vector<std::uint8_t>& command_data) {
|
||||
command_data = create_simple_command(ast_app_layer::CommandType::GET_TOTAL_DEV_POWER);
|
||||
}
|
||||
|
||||
void AstAppLayer::create_command_get_total_start_import_energy(std::vector<std::uint8_t>& command_data) {
|
||||
command_data = create_simple_command(ast_app_layer::CommandType::GET_TOTAL_START_IMPORT_DEV_ENERGY);
|
||||
}
|
||||
|
||||
void AstAppLayer::create_command_get_total_stop_import_energy(std::vector<std::uint8_t>& command_data) {
|
||||
command_data = create_simple_command(ast_app_layer::CommandType::GET_TOTAL_STOP_IMPORT_DEV_ENERGY);
|
||||
}
|
||||
|
||||
void AstAppLayer::create_command_get_total_start_export_energy(std::vector<std::uint8_t>& command_data) {
|
||||
command_data = create_simple_command(ast_app_layer::CommandType::GET_TOTAL_START_EXPORT_DEV_ENERGY);
|
||||
}
|
||||
|
||||
void AstAppLayer::create_command_get_total_stop_export_energy(std::vector<std::uint8_t>& command_data) {
|
||||
command_data = create_simple_command(ast_app_layer::CommandType::GET_TOTAL_STOP_EXPORT_DEV_ENERGY);
|
||||
}
|
||||
|
||||
void AstAppLayer::create_command_get_total_transaction_duration(std::vector<std::uint8_t>& command_data) {
|
||||
command_data = create_simple_command(ast_app_layer::CommandType::GET_TRANSACT_TOTAL_DURATION);
|
||||
}
|
||||
|
||||
void AstAppLayer::create_command_get_pubkey_str16(std::vector<std::uint8_t>& command_data) {
|
||||
command_data = create_simple_command(ast_app_layer::CommandType::GET_PUBKEY_STR16);
|
||||
}
|
||||
|
||||
void AstAppLayer::create_command_get_pubkey_asn1(std::vector<std::uint8_t>& command_data) {
|
||||
command_data = create_simple_command(ast_app_layer::CommandType::GET_PUBKEY_ASN1);
|
||||
}
|
||||
|
||||
void AstAppLayer::create_command_get_meter_pubkey(std::vector<std::uint8_t>& command_data) {
|
||||
command_data = create_simple_command(ast_app_layer::CommandType::REQUEST_METER_PUBKEY);
|
||||
}
|
||||
|
||||
void AstAppLayer::create_command_get_ocmf_stats(std::vector<std::uint8_t>& command_data) {
|
||||
command_data = create_simple_command(ast_app_layer::CommandType::OCMF_STATS);
|
||||
}
|
||||
|
||||
/* OCMF ID: 1..235000
|
||||
OCMF data from specified transaction will be at minimum import energy of transaction
|
||||
*/
|
||||
void AstAppLayer::create_command_get_transaction_ocmf(uint32_t ocmf_id, std::vector<std::uint8_t>& command_data) {
|
||||
ast_app_layer::Command cmd{};
|
||||
|
||||
cmd.type = ast_app_layer::CommandType::GET_OCMF;
|
||||
cmd.length = 0x0009;
|
||||
cmd.status = ast_app_layer::CommandStatus::OK;
|
||||
|
||||
insert_u32_as_u8s(cmd.data, ocmf_id);
|
||||
|
||||
command_data = create_command(cmd);
|
||||
}
|
||||
|
||||
void AstAppLayer::create_command_get_last_transaction_ocmf(std::vector<std::uint8_t>& command_data) {
|
||||
command_data = create_simple_command(ast_app_layer::CommandType::GET_LAST_OCMF);
|
||||
}
|
||||
|
||||
void AstAppLayer::create_command_get_ocmf_info(std::vector<std::uint8_t>& command_data) {
|
||||
command_data = create_simple_command(ast_app_layer::CommandType::OCMF_INFO);
|
||||
}
|
||||
|
||||
void AstAppLayer::create_command_get_ocmf_config(std::vector<std::uint8_t>& command_data) {
|
||||
command_data = create_simple_command(ast_app_layer::CommandType::OCMF_CONFIG);
|
||||
}
|
||||
|
||||
void AstAppLayer::create_command_get_charge_point_id(std::vector<std::uint8_t>& command_data) {
|
||||
command_data = create_simple_command(ast_app_layer::CommandType::CHARGE_POINT_ID);
|
||||
}
|
||||
|
||||
/* only works in "assembly mode" */
|
||||
void AstAppLayer::create_command_set_charge_point_id(ast_app_layer::UserIdType id_type, std::string id_data,
|
||||
std::vector<std::uint8_t>& command_data) {
|
||||
ast_app_layer::Command cmd{};
|
||||
|
||||
cmd.type = ast_app_layer::CommandType::CHARGE_POINT_ID;
|
||||
cmd.length = 0x0013;
|
||||
cmd.status = ast_app_layer::CommandStatus::OK;
|
||||
|
||||
cmd.data.push_back((uint8_t)id_type);
|
||||
|
||||
std::uint8_t byte_count = 0;
|
||||
for (std::uint8_t databyte : id_data) { // push up to 13 characters of id data into command
|
||||
cmd.data.push_back(databyte);
|
||||
byte_count++;
|
||||
if (byte_count >= 13)
|
||||
break;
|
||||
}
|
||||
while (byte_count < 13) { // fill remaining user id name characters with zeros
|
||||
cmd.data.push_back(0x00);
|
||||
byte_count++;
|
||||
}
|
||||
|
||||
command_data = create_command(cmd);
|
||||
}
|
||||
|
||||
void AstAppLayer::create_command_get_errors(ast_app_layer::ErrorCategory category, ast_app_layer::ErrorSource src,
|
||||
std::vector<std::uint8_t>& command_data) {
|
||||
ast_app_layer::Command cmd{};
|
||||
|
||||
cmd.type = ast_app_layer::CommandType::GET_ERRORS;
|
||||
cmd.length = 0x0007;
|
||||
cmd.status = ast_app_layer::CommandStatus::OK;
|
||||
|
||||
cmd.data.push_back((uint8_t)category);
|
||||
cmd.data.push_back((uint8_t)src);
|
||||
|
||||
command_data = create_command(cmd);
|
||||
}
|
||||
|
||||
void AstAppLayer::create_command_get_log_stats(std::vector<std::uint8_t>& command_data) {
|
||||
command_data = create_simple_command(ast_app_layer::CommandType::GET_LOG_STATS);
|
||||
}
|
||||
|
||||
/* log entry ids: 1..2500 */
|
||||
void AstAppLayer::create_command_get_log_entry(uint32_t log_entry_id, std::vector<std::uint8_t>& command_data) {
|
||||
ast_app_layer::Command cmd{};
|
||||
|
||||
cmd.type = ast_app_layer::CommandType::GET_LOG_ENTRY;
|
||||
cmd.length = 0x0009;
|
||||
cmd.status = ast_app_layer::CommandStatus::OK;
|
||||
|
||||
insert_u32_as_u8s(cmd.data, log_entry_id);
|
||||
|
||||
command_data = create_command(cmd);
|
||||
}
|
||||
|
||||
void AstAppLayer::create_command_get_last_log_entry(std::vector<std::uint8_t>& command_data) {
|
||||
command_data = create_simple_command(ast_app_layer::CommandType::GET_LAST_LOG_ENTRY);
|
||||
}
|
||||
|
||||
/* log entry ids: 1..2500
|
||||
thus: if 20 log entries and log_entry_id == 2, then log entry 18 will be returned
|
||||
*/
|
||||
void AstAppLayer::create_command_get_log_entry_reverse(uint32_t log_entry_id, std::vector<std::uint8_t>& command_data) {
|
||||
ast_app_layer::Command cmd{};
|
||||
|
||||
cmd.type = ast_app_layer::CommandType::GET_LOG_ENTRY_REVERSE;
|
||||
cmd.length = 0x0009;
|
||||
cmd.status = ast_app_layer::CommandStatus::OK;
|
||||
|
||||
insert_u32_as_u8s(cmd.data, log_entry_id);
|
||||
|
||||
command_data = create_command(cmd);
|
||||
}
|
||||
|
||||
void AstAppLayer::create_command_get_application_board_mode(std::vector<std::uint8_t>& command_data) {
|
||||
command_data = create_simple_command(ast_app_layer::CommandType::AB_MODE_SET);
|
||||
}
|
||||
|
||||
void AstAppLayer::create_command_set_application_board_mode(ast_app_layer::ApplicationBoardMode mode,
|
||||
std::vector<std::uint8_t>& command_data) {
|
||||
ast_app_layer::Command cmd{};
|
||||
|
||||
cmd.type = ast_app_layer::CommandType::AB_MODE_SET;
|
||||
cmd.length = 0x0006;
|
||||
cmd.status = ast_app_layer::CommandStatus::OK;
|
||||
|
||||
cmd.data.push_back((uint8_t)mode);
|
||||
|
||||
command_data = create_command(cmd);
|
||||
}
|
||||
|
||||
// diagnostics
|
||||
|
||||
void AstAppLayer::create_command_get_hardware_version(std::vector<std::uint8_t>& command_data) {
|
||||
command_data = create_simple_command(ast_app_layer::CommandType::AB_HW_VERSION);
|
||||
}
|
||||
|
||||
void AstAppLayer::create_command_get_application_board_server_id(std::vector<std::uint8_t>& command_data) {
|
||||
command_data = create_simple_command(ast_app_layer::CommandType::AB_SERVER_ID);
|
||||
}
|
||||
|
||||
void AstAppLayer::create_command_get_application_board_serial_number(std::vector<std::uint8_t>& command_data) {
|
||||
command_data = create_simple_command(ast_app_layer::CommandType::AB_SERIAL_NR);
|
||||
}
|
||||
|
||||
void AstAppLayer::create_command_get_application_board_software_version(std::vector<std::uint8_t>& command_data) {
|
||||
command_data = create_simple_command(ast_app_layer::CommandType::AB_SW_VERSION);
|
||||
}
|
||||
|
||||
void AstAppLayer::create_command_get_application_board_fw_checksum(std::vector<std::uint8_t>& command_data) {
|
||||
command_data = create_simple_command(ast_app_layer::CommandType::AB_FW_CHECKSUM);
|
||||
}
|
||||
|
||||
void AstAppLayer::create_command_get_application_board_fw_hash(std::vector<std::uint8_t>& command_data) {
|
||||
command_data = create_simple_command(ast_app_layer::CommandType::AB_FW_HASH);
|
||||
}
|
||||
|
||||
void AstAppLayer::create_command_get_application_board_status(std::vector<std::uint8_t>& command_data) {
|
||||
command_data = create_simple_command(ast_app_layer::CommandType::AB_STATUS);
|
||||
}
|
||||
|
||||
void AstAppLayer::create_command_get_metering_board_software_version(std::vector<std::uint8_t>& command_data) {
|
||||
command_data = create_simple_command(ast_app_layer::CommandType::MB_SW_VERSION);
|
||||
}
|
||||
|
||||
void AstAppLayer::create_command_get_metering_board_fw_checksum(std::vector<std::uint8_t>& command_data) {
|
||||
command_data = create_simple_command(ast_app_layer::CommandType::MB_FW_CHECKSUM);
|
||||
}
|
||||
|
||||
/* doubles as OCMF "meter model name" */
|
||||
void AstAppLayer::create_command_get_device_type(std::vector<std::uint8_t>& command_data) {
|
||||
command_data = create_simple_command(ast_app_layer::CommandType::AB_DEVICE_TYPE);
|
||||
}
|
||||
|
||||
} // namespace ast_app_layer
|
||||
@@ -0,0 +1,361 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
/*
|
||||
This is an implementation for the AST powermeter application layer
|
||||
*/
|
||||
#ifndef AST_APP_LAYER
|
||||
#define AST_APP_LAYER
|
||||
|
||||
#include "ld-ev.hpp"
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <everest/logging.hpp>
|
||||
#include <generated/types/powermeter.hpp>
|
||||
|
||||
namespace ast_app_layer {
|
||||
|
||||
enum class CommandType : std::uint16_t {
|
||||
RESET_DC_METER = 0x4101, // reset the DC meter
|
||||
AB_MODE_SET = 0x4102, // get or set the mode of application board
|
||||
AB_SERVER_ID = 0x4110, // get or set the server ID of application board
|
||||
AB_SERIAL_NR = 0x4111, // get or set the serial number
|
||||
AB_HW_VERSION = 0x4112, // get or set the hardware version
|
||||
AB_DEVICE_TYPE = 0x4113, // get or set the device type
|
||||
AB_SW_VERSION = 0x4114, // get the software version of application board
|
||||
AB_FW_CHECKSUM = 0x4115, // get the checksum (crc16) of application board
|
||||
MB_SW_VERSION = 0x4116, // get the software version of metering board
|
||||
MB_FW_CHECKSUM = 0x4117, // get the crc16 from software integrity check of metering board
|
||||
AB_FW_HASH = 0x4119, // get the firmware hash(crc16) of application board
|
||||
MEASUREMENT_MODE = 0x4120, // get the measurement mode
|
||||
GET_NORMAL_VOLTAGE = 0x4121, // get the normal voltage
|
||||
GET_NORMAL_CURRENT = 0x4122, // get the normal current
|
||||
GET_MAX_CURRENT = 0x4123, // get the maximum current
|
||||
LINE_LOSS_IMPEDANCE = 0x4130, // get or set the line loss impedance
|
||||
LINE_LOSS_MEAS_MODE = 0x4131, // get the line loss energy measurement mode
|
||||
TIME = 0x4135, // get or set the device time
|
||||
TEMPERATURE = 0x4136, // get the temperature
|
||||
AB_STATUS = 0x4137, // get the statusword of application board
|
||||
METER_BUS_ADDR = 0x4212, // get or set the meter bus address
|
||||
START_TRANSACTION = 0x1501, // start transaction
|
||||
STOP_TRANSACTION = 0x1502, // stop transaction
|
||||
OCMF_STATS = 0x1510, // get the statistics of OCMF
|
||||
GET_OCMF = 0x1520, // get the OCMF
|
||||
GET_LAST_OCMF = 0x1521, // get last OCMF
|
||||
GET_OCMF_REVERSE = 0x1522, // get the OCMF reverse
|
||||
GET_PUBKEY_ASN1 = 0x1530, // get public key in ASN1 format
|
||||
CHARGE_POINT_ID = 0x1531, // get or set the charge point identification
|
||||
GET_PUBKEY_BIN = 0x1532, // get public key as binary string
|
||||
GET_TRANSACT_IMPORT_LINE_LOSS_ENERGY = 0x1540, // get total transaction import line loss energy
|
||||
GET_TRANSACT_EXPORT_LINE_LOSS_ENERGY = 0x1541, // get total transaction export line loss energy
|
||||
GET_TRANSACT_TOTAL_IMPORT_DEV_ENERGY = 0x1550, // get total transaction import device energy
|
||||
GET_TRANSACT_TOTAL_EXPORT_DEV_ENERGY = 0x1551, // get total transaction export device energy
|
||||
GET_TRANSACT_TOTAL_IMPORT_MAINS_ENERGY = 0x1560, // get total transaction import mains energy
|
||||
GET_TRANSACT_TOTAL_EXPORT_MAINS_ENERGY = 0x1561, // get total transaction export mains energy
|
||||
GET_TRANSACT_TOTAL_DURATION = 0x156A, // get total transaction duration.
|
||||
GET_TOTAL_START_IMPORT_LINE_LOSS_ENERGY = 0x1570, // get total start import line loss energy
|
||||
GET_TOTAL_START_EXPORT_LINE_LOSS_ENERGY = 0x1571, // get total start export line loss energy
|
||||
GET_TOTAL_START_IMPORT_DEV_ENERGY = 0x1580, // get total start import device energy
|
||||
GET_TOTAL_START_EXPORT_DEV_ENERGY = 0x1581, // get total start export device energy
|
||||
GET_TOTAL_START_IMPORT_MAINS_ENERGY = 0x1590, // get total start import mains energy
|
||||
GET_TOTAL_START_EXPORT_MAINS_ENERGY = 0x1591, // get total start export mains energy
|
||||
GET_TOTAL_STOP_IMPORT_LINE_LOSS_ENERGY = 0x15A0, // get total stop import line loss energy
|
||||
GET_TOTAL_STOP_EXPORT_LINE_LOSS_ENERGY = 0x15A1, // get total stop export line loss energy
|
||||
GET_TOTAL_STOP_IMPORT_DEV_ENERGY = 0x15B0, // get total stop import device energy
|
||||
GET_TOTAL_STOP_EXPORT_DEV_ENERGY = 0x15B1, // get total stop export device energy
|
||||
GET_TOTAL_STOP_IMPORT_MAINS_ENERGY = 0x15C0, // get total stop import mains energy
|
||||
GET_TOTAL_STOP_EXPORT_MAINS_ENERGY = 0x15C1, // get total stop export mains energy
|
||||
GET_LOG_STATS = 0x1710, // get the statistic of log
|
||||
GET_LOG_ENTRY = 0x1720, // get the log entry
|
||||
GET_LAST_LOG_ENTRY = 0x1721, // get last log entry
|
||||
GET_LOG_ENTRY_REVERSE = 0x1722, // get the log entry reverse
|
||||
REGISTER_DISPLAY_PUBKEY = 0x1801, // register display public key
|
||||
REQUEST_CHALLENGE = 0x1810, // request challenge
|
||||
SET_SIGNED_CHALLENGE = 0x1811, // set signed challenge
|
||||
REQUEST_METER_PUBKEY = 0x1830, // request meter public key
|
||||
GET_TOTAL_IMPORT_MAINS_ENERGY = 0x0110, // get total import mains energy
|
||||
GET_TOTAL_EXPORT_MAINS_ENERGY = 0x0120, // get total export mains energy
|
||||
GET_CURRENT_L1 = 0x0131, // get current (phase L1)
|
||||
GET_VOLTAGE_L1 = 0x0132, // get voltage (phase L1)
|
||||
GET_TOTAL_IMPORT_MAINS_POWER = 0x0133, // total import mains power
|
||||
GET_TOTAL_EXPORT_MAINS_POWER = 0x0134, // total export mains power
|
||||
GET_DEV_VOLTAGE_L1 = 0x0137, // get device voltage (phase L1)
|
||||
GET_IMPORT_DEV_POWER = 0x0138, // get import device power
|
||||
GET_EXPORT_DEV_POWER = 0x0139, // get export device power
|
||||
GET_IMPORT_LINE_LOSS_POWER = 0x013A, // get import line loss power
|
||||
GET_EXPORT_LINE_LOSS_POWER = 0x013B, // get export line loss power
|
||||
GET_TOTAL_DEV_POWER = 0x013C, // get total device power
|
||||
GET_TOTAL_IMPORT_LINE_LOSS_ENERGY = 0x0160, // get the total import line loss energy
|
||||
GET_TOTAL_EXPORT_LINE_LOSS_ENERGY = 0x0163, // get the total export line loss energy
|
||||
GET_TOTAL_IMPORT_DEV_ENERGY = 0x0170, // get the total import device energy
|
||||
GET_TOTAL_EXPORT_DEV_ENERGY = 0x0173, // get the total export device energy
|
||||
GET_SECOND_INDEX = 0x0180, // get the second index
|
||||
GET_PUBKEY_STR16 = 0x0211, // get the public key in string format (base16)
|
||||
GET_PUBKEY_STR32 = 0x0212, // get the public key in string format (base32)
|
||||
GET_PUBKEY_CSTR16 = 0x0213, // get the public key compressed in string format (base16)
|
||||
GET_PUBKEY_CSTR32 = 0x0214, // get the public key compressed in string format (base32)
|
||||
REPEAT_DATA = 0xA000, // repeat data
|
||||
OCMF_INFO = 0xA001, // get or set OCMF info
|
||||
OCMF_CONFIG = 0xA002, // get or set the table of OCMF configuration field
|
||||
GET_ERRORS = 0xA004, // get errors
|
||||
AB_DMC = 0xA006, // get or set the DMC application board
|
||||
AB_PROD_DATE = 0xA00B, // get or set the production date of application board
|
||||
SET_REQUEST_CHALLENGE = 0xA00C // enable/disable the request challenge
|
||||
};
|
||||
|
||||
enum class CommandStatus : std::uint8_t {
|
||||
OK = 0, // no error
|
||||
GENERAL_ERROR = 1, // internal process has an error
|
||||
OUT_OF_RANGE = 2, // data is out of range
|
||||
SECURITY_ACCES_DENIED = 3, // the current mode of the slave cannot start this command
|
||||
REJECTED = 4, // error of communication between application and metering board
|
||||
LOGICAL_ADDRESS_NOT_FOUND = 5, // command not implemented
|
||||
FORMAL_INVALID = 6, // length in bytes is incorrect; e.g. received more data than expected
|
||||
NOT_AVAILABLE = 7, // data not available
|
||||
IS_BUSY = 8, // slave cannot process command; already processing long operation
|
||||
PUBKEY_MISSING = 9 // public key is not stored in internal memory
|
||||
};
|
||||
|
||||
enum class CommandResult : std::uint8_t {
|
||||
OK = 0,
|
||||
GENERAL_ERROR = 1, // read ERROR register (read with GET_ERRORS[0xA004]) for more information
|
||||
OUT_OF_RANGE = 2, //
|
||||
SECURITY_ACCESS_DENIED = 3, // application board is not in production mode
|
||||
REJECTED = 4, // metering board is not responding
|
||||
FORMAT_INVALID = 6, // payload is not empty
|
||||
NOT_AVAILABLE = 7, // data could not be read
|
||||
BUSY = 8, // transaction ongoing, metering board unavailable
|
||||
PUBLIC_KEY_MISSING = 9, //
|
||||
PROTOCOL_ERROR = 250, // error on reception at host: protocol error (SLIP protocol)
|
||||
TIMEOUT = 251, // no response during at least 1100 ms
|
||||
COMMUNICATION_FAILED = 254, // error on communication between PM and host device
|
||||
PENDING = 255 // special state for transaction commands
|
||||
};
|
||||
|
||||
inline std::string command_result_to_string(CommandResult res) {
|
||||
switch (res) {
|
||||
case CommandResult::OK:
|
||||
return "OK";
|
||||
case CommandResult::GENERAL_ERROR:
|
||||
return "General Error";
|
||||
case CommandResult::OUT_OF_RANGE:
|
||||
return "Out of Range";
|
||||
case CommandResult::SECURITY_ACCESS_DENIED:
|
||||
return "Security Access Denied";
|
||||
case CommandResult::REJECTED:
|
||||
return "Rejected";
|
||||
case CommandResult::FORMAT_INVALID:
|
||||
return "Format Invalid";
|
||||
case CommandResult::NOT_AVAILABLE:
|
||||
return "Not Available";
|
||||
case CommandResult::BUSY:
|
||||
return "Busy";
|
||||
case CommandResult::PUBLIC_KEY_MISSING:
|
||||
return "Public Key Missing";
|
||||
case CommandResult::PROTOCOL_ERROR:
|
||||
return "Protocol Error";
|
||||
case CommandResult::TIMEOUT:
|
||||
return "Timeout";
|
||||
case CommandResult::COMMUNICATION_FAILED:
|
||||
return "Communication Failed";
|
||||
case CommandResult::PENDING:
|
||||
return "Pending";
|
||||
}
|
||||
throw std::out_of_range("No known string conversion for provided enum of type CommandResult");
|
||||
}
|
||||
|
||||
enum class UserIdStatus : std::uint8_t {
|
||||
USER_NOT_ASSIGNED = 0x00,
|
||||
USER_ASSIGNED = 0x01
|
||||
};
|
||||
|
||||
enum class UserIdType : std::uint8_t {
|
||||
NONE = 0, // not available
|
||||
DENIED = 1, // not retrievable (e.g. two-factor-auth)
|
||||
UNDEFINED = 2, // type unknown / other
|
||||
ISO14443 = 10, // UID of RFID card according to ISO14443 (4 or 7 bytes HEX)
|
||||
ISO15693 = 11, // UID of RFID card according to ISO15693 (8 bytes HEX)
|
||||
EMAID = 20, // Electro-Mobility-Account-ID according to ISO/IEC15118 (14 or 15 bytes string)
|
||||
EVCCID = 21, // ID of an EV according to ISO/IEC15118 (max 6 bytes)
|
||||
EVCOID = 30, // EV-Contract-ID according to DIN91286
|
||||
ISO7812 = 40, // Identification-Card-Format according to ISO/IEC7812 (credit-/banking-cards, etc.)
|
||||
CAR_TXN_NR = 50, // Card-Transaction-Number (CardTxNbr) for credit- or banking-cards
|
||||
CENTRAL = 60, // centrally generated ID (no fixed format, e.g. UUID); OCPP 2.0
|
||||
CENTRAL_1 = 61, // centrally generated ID (no fixed format, e.g. start-via-SMS); (up to) OCPP 1.6
|
||||
CENTRAL_2 = 62, // centrally generated ID (no fixed format, e.g. start-by-operator); (up to) OCPP 1.6
|
||||
LOCAL = 70, // locally generated ID (no fixed format, e.g. UUID); OCPP 2.0
|
||||
LOCAL_1 = 71, // locally generated ID (no fixed format, e.g. generated by chargepoint); (up to) OCPP 1.6
|
||||
LOCAL_2 = 72, // locally generated ID (no fixed format, other); (up to) OCPP 1.6
|
||||
PHONE_NUMBER = 80, // international phone number (leading '+' with country code)
|
||||
KEY_CODE = 90 // private user key (no fixed format)
|
||||
};
|
||||
|
||||
enum class ErrorCategory : std::uint8_t {
|
||||
LAST = 0,
|
||||
LAST_CRITICAL = 1,
|
||||
FIRST = 2,
|
||||
FIRST_CRITICAL = 3
|
||||
};
|
||||
|
||||
enum class ErrorSource : std::uint8_t {
|
||||
SYSTEM = 0,
|
||||
COMMUNICATION = 1
|
||||
};
|
||||
|
||||
enum class ApplicationBoardMode : std::uint8_t {
|
||||
APPLICATION = 0,
|
||||
ASSEMBLY = 1,
|
||||
// PRODUCTION = 2
|
||||
};
|
||||
|
||||
enum class LogType : std::uint8_t {
|
||||
NONE = 0,
|
||||
LINE_LOSS_MEASUREMENT_MODE = 1,
|
||||
IMPEDANCE_CHANGED = 2,
|
||||
OPERATION_MODE_CHANGED = 3,
|
||||
ASSEMBLY_CONFIG_CHANGED = 4,
|
||||
FATAL_ERROR_EVENT = 5,
|
||||
TIME_DELTA_TOO_BIG_EVENT = 6,
|
||||
CHARGE_POINT_ID_CHANGED = 7,
|
||||
EXTERNAL_DISPLAY_PAIRED = 8,
|
||||
EXTERNAL_DISPLAY_FAILURE = 9,
|
||||
CHARGE_DATA_OUT_OF_MEMORY = 10,
|
||||
LOG_DATA_OUT_OF_MEMORY = 11,
|
||||
FW_VERSION_CHANGED = 12,
|
||||
PULSE_LED_SOURCE_CHANGED = 13
|
||||
};
|
||||
|
||||
inline std::string log_type_to_string(LogType log) {
|
||||
switch (log) {
|
||||
case LogType::NONE:
|
||||
return "None";
|
||||
case LogType::LINE_LOSS_MEASUREMENT_MODE:
|
||||
return "Line loss measurement mode";
|
||||
case LogType::IMPEDANCE_CHANGED:
|
||||
return "Impedance changed";
|
||||
case LogType::OPERATION_MODE_CHANGED:
|
||||
return "Operation mode changed";
|
||||
case LogType::ASSEMBLY_CONFIG_CHANGED:
|
||||
return "Assembly config changed";
|
||||
case LogType::FATAL_ERROR_EVENT:
|
||||
return "Fatal error event";
|
||||
case LogType::TIME_DELTA_TOO_BIG_EVENT:
|
||||
return "Time delta too big event";
|
||||
case LogType::CHARGE_POINT_ID_CHANGED:
|
||||
return "Charge point id changed";
|
||||
case LogType::EXTERNAL_DISPLAY_PAIRED:
|
||||
return "External display paired";
|
||||
case LogType::EXTERNAL_DISPLAY_FAILURE:
|
||||
return "External display failure";
|
||||
case LogType::CHARGE_DATA_OUT_OF_MEMORY:
|
||||
return "Charge data out of memory";
|
||||
case LogType::LOG_DATA_OUT_OF_MEMORY:
|
||||
return "Log data out of memory";
|
||||
case LogType::FW_VERSION_CHANGED:
|
||||
return "Fw version changed";
|
||||
case LogType::PULSE_LED_SOURCE_CHANGED:
|
||||
return "Pulse LED source changed";
|
||||
}
|
||||
throw std::out_of_range("No known string conversion for provided enum of type LogType");
|
||||
}
|
||||
|
||||
class LogEntry {
|
||||
public:
|
||||
ast_app_layer::LogType type;
|
||||
std::uint32_t second_index{};
|
||||
std::uint32_t utc_time{};
|
||||
std::uint8_t utc_offset{};
|
||||
std::vector<uint8_t> old_value; // max. 10 elements
|
||||
std::vector<uint8_t> new_value; // max. 10 elements
|
||||
std::vector<uint8_t> server_id; // 10 elements
|
||||
std::vector<uint8_t> signature; // 64 elements
|
||||
};
|
||||
|
||||
class Command {
|
||||
public:
|
||||
ast_app_layer::CommandType type;
|
||||
std::uint16_t length;
|
||||
ast_app_layer::CommandStatus status;
|
||||
std::vector<std::uint8_t> data;
|
||||
};
|
||||
|
||||
static constexpr std::uint16_t PM_AST_MAX_RX_LENGTH = 1000;
|
||||
static constexpr std::uint16_t PM_AST_SERIAL_RX_INITIAL_TIMEOUT_MS = 1100;
|
||||
static constexpr std::uint16_t PM_AST_SERIAL_RX_WITHIN_MESSAGE_TIMEOUT_MS = 100;
|
||||
|
||||
class AstAppLayer {
|
||||
|
||||
public:
|
||||
void create_command_start_transaction(ast_app_layer::UserIdStatus user_id_status,
|
||||
ast_app_layer::UserIdType user_id_type, const std::string& user_id_data,
|
||||
std::int8_t gmt_offset_quarter_hours,
|
||||
std::vector<std::uint8_t>& command_data);
|
||||
void create_command_stop_transaction(std::vector<uint8_t>& command_data);
|
||||
|
||||
void create_command_get_time(std::vector<std::uint8_t>& command_data);
|
||||
void create_command_set_time(date::utc_clock::time_point timepoint, int8_t gmt_offset_quarters_of_an_hour,
|
||||
std::vector<std::uint8_t>& command_data);
|
||||
|
||||
void create_command_get_voltage(std::vector<std::uint8_t>& command_data);
|
||||
void create_command_get_current(std::vector<std::uint8_t>& command_data);
|
||||
void create_command_get_import_power(std::vector<std::uint8_t>& command_data);
|
||||
void create_command_get_export_power(std::vector<std::uint8_t>& command_data);
|
||||
void create_command_get_total_power(std::vector<std::uint8_t>& command_data);
|
||||
|
||||
void create_command_get_total_start_import_energy(std::vector<std::uint8_t>& command_data);
|
||||
void create_command_get_total_stop_import_energy(std::vector<std::uint8_t>& command_data);
|
||||
void create_command_get_total_start_export_energy(std::vector<std::uint8_t>& command_data);
|
||||
void create_command_get_total_stop_export_energy(std::vector<std::uint8_t>& command_data);
|
||||
void create_command_get_total_dev_import_energy(std::vector<std::uint8_t>& command_data);
|
||||
void create_command_get_total_dev_export_energy(std::vector<std::uint8_t>& command_data);
|
||||
|
||||
void create_command_get_total_transaction_duration(std::vector<std::uint8_t>& command_data);
|
||||
|
||||
void create_command_get_pubkey_str16(std::vector<std::uint8_t>& command_data);
|
||||
void create_command_get_pubkey_asn1(std::vector<std::uint8_t>& command_data);
|
||||
void create_command_get_meter_pubkey(std::vector<std::uint8_t>& command_data);
|
||||
|
||||
void create_command_get_ocmf_stats(std::vector<std::uint8_t>& command_data);
|
||||
void create_command_get_transaction_ocmf(std::uint32_t ocmf_id, std::vector<std::uint8_t>& command_data);
|
||||
void create_command_get_last_transaction_ocmf(std::vector<std::uint8_t>& command_data);
|
||||
void create_command_get_ocmf_info(std::vector<std::uint8_t>& command_data);
|
||||
void create_command_get_ocmf_config(std::vector<std::uint8_t>& command_data);
|
||||
|
||||
void create_command_get_charge_point_id(std::vector<std::uint8_t>& command_data);
|
||||
void create_command_set_charge_point_id(ast_app_layer::UserIdType id_type, std::string id_data,
|
||||
std::vector<std::uint8_t>& command_data);
|
||||
|
||||
void create_command_get_errors(ast_app_layer::ErrorCategory category, ast_app_layer::ErrorSource src,
|
||||
std::vector<std::uint8_t>& command_data);
|
||||
|
||||
void create_command_get_log_stats(std::vector<std::uint8_t>& command_data);
|
||||
void create_command_get_log_entry(uint32_t log_entry_id, std::vector<std::uint8_t>& command_data);
|
||||
void create_command_get_last_log_entry(std::vector<std::uint8_t>& command_data);
|
||||
void create_command_get_log_entry_reverse(uint32_t log_entry_id, std::vector<std::uint8_t>& command_data);
|
||||
|
||||
void create_command_get_application_board_mode(std::vector<std::uint8_t>& command_data);
|
||||
void create_command_set_application_board_mode(ast_app_layer::ApplicationBoardMode mode,
|
||||
std::vector<std::uint8_t>& command_data);
|
||||
|
||||
void create_command_get_hardware_version(std::vector<std::uint8_t>& command_data);
|
||||
void create_command_get_application_board_server_id(std::vector<std::uint8_t>& command_data);
|
||||
void create_command_get_application_board_serial_number(std::vector<std::uint8_t>& command_data);
|
||||
void create_command_get_application_board_software_version(std::vector<std::uint8_t>& command_data);
|
||||
void create_command_get_application_board_fw_checksum(std::vector<std::uint8_t>& command_data);
|
||||
void create_command_get_application_board_fw_hash(std::vector<std::uint8_t>& command_data);
|
||||
void create_command_get_application_board_status(std::vector<std::uint8_t>& command_data);
|
||||
void create_command_get_metering_board_software_version(std::vector<std::uint8_t>& command_data);
|
||||
void create_command_get_metering_board_fw_checksum(std::vector<std::uint8_t>& command_data);
|
||||
|
||||
void create_command_get_device_type(std::vector<std::uint8_t>& command_data);
|
||||
|
||||
int8_t
|
||||
get_utc_offset_in_quarter_hours(const std::chrono::time_point<std::chrono::system_clock>& timepoint_system_clock);
|
||||
|
||||
private:
|
||||
std::vector<uint8_t> create_command(ast_app_layer::Command cmd);
|
||||
std::vector<uint8_t> create_simple_command(ast_app_layer::CommandType cmd_type);
|
||||
};
|
||||
|
||||
} // namespace ast_app_layer
|
||||
#endif // AST_APP_LAYER
|
||||
@@ -0,0 +1,225 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include <diagnostics.hpp>
|
||||
|
||||
namespace module {
|
||||
|
||||
void to_json(json& j, const DeviceData& k) {
|
||||
j["UTC"] = module::conversions::u32_epoch_to_rfc3339(k.utc_time_s);
|
||||
j["GMT_offset_quarterhours"] = k.gmt_offset_quarterhours;
|
||||
j["total_start_import_energy_Wh"] = k.total_start_import_energy_Wh;
|
||||
j["total_stop_import_energy_Wh"] = k.total_stop_import_energy_Wh;
|
||||
j["total_start_export_energy_Wh"] = k.total_start_export_energy_Wh;
|
||||
j["total_stop_export_energy_Wh"] = k.total_stop_export_energy_Wh;
|
||||
j["total_transaction_duration_s"] = k.total_transaction_duration_s;
|
||||
j["OCMF_stats"] = json();
|
||||
j["OCMF_stats"]["number_transactions"] = k.ocmf_stats.number_transactions;
|
||||
j["OCMF_stats"]["timestamp_first_transaction"] = k.ocmf_stats.timestamp_first_transaction;
|
||||
j["OCMF_stats"]["timestamp_last_transaction"] = k.ocmf_stats.timestamp_last_transaction;
|
||||
j["OCMF_stats"]["max_number_of_transactions"] = k.ocmf_stats.max_number_of_transactions;
|
||||
j["last_ocmf_transaction"] = k.last_ocmf_transaction;
|
||||
j["requested_ocmf"] = k.requested_ocmf;
|
||||
j["OCMF_info"] = json();
|
||||
j["OCMF_info"]["gateway_id"] = k.ocmf_info.gateway_id;
|
||||
j["OCMF_info"]["manufacturer"] = k.ocmf_info.manufacturer;
|
||||
j["OCMF_info"]["model"] = k.ocmf_info.model;
|
||||
j["total_dev_import_energy_Wh"] = k.total_dev_import_energy_Wh;
|
||||
j["total_dev_export_energy_Wh"] = k.total_dev_export_energy_Wh;
|
||||
j["status"] = module::conversions::to_bin_string(k.ab_status);
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const DeviceData& k) {
|
||||
os << json(k).dump(4);
|
||||
return os;
|
||||
}
|
||||
|
||||
void to_json(json& j, const DeviceDiagnostics& k) {
|
||||
j["charge_point_id"] = k.charge_point_id;
|
||||
j["charge_point_id_type"] = k.charge_point_id_type;
|
||||
j["log_stats"] = json();
|
||||
j["log_stats"]["number_log_entries"] = k.log_stats.number_log_entries;
|
||||
j["log_stats"]["timestamp_first_log"] = k.log_stats.timestamp_first_log;
|
||||
j["log_stats"]["timestamp_last_log"] = k.log_stats.timestamp_last_log;
|
||||
j["log_stats"]["max_number_of_logs"] = k.log_stats.max_number_of_logs;
|
||||
j["app_board"] = json();
|
||||
j["app_board"]["type"] = k.app_board.type;
|
||||
j["app_board"]["HW_ver"] = k.app_board.hw_ver;
|
||||
j["app_board"]["server_id"] = k.app_board.server_id;
|
||||
j["app_board"]["mode"] = k.app_board.mode;
|
||||
j["app_board"]["serial_nr"] = k.app_board.serial_number;
|
||||
j["app_board"]["SW_ver"] = k.app_board.sw_ver;
|
||||
j["app_board"]["FW_CRC"] = module::conversions::hexdump(k.app_board.fw_crc);
|
||||
j["app_board"]["FW_hash"] = module::conversions::hexdump(k.app_board.fw_hash);
|
||||
j["m_board"] = json();
|
||||
j["m_board"]["HW_ver"] = k.m_board.hw_ver;
|
||||
j["m_board"]["SW_ver"] = k.m_board.sw_ver;
|
||||
j["m_board"]["FW_CRC"] = module::conversions::hexdump(k.m_board.fw_crc);
|
||||
j["pubkey"] = json();
|
||||
j["pubkey"]["asn1"] = json();
|
||||
j["pubkey"]["str16"] = json();
|
||||
j["pubkey"]["default"] = json();
|
||||
j["pubkey"]["asn1"]["key"] = k.pubkey_asn1;
|
||||
j["pubkey"]["str16"]["key"] = k.pubkey_str16;
|
||||
j["pubkey"]["str16"]["format"] = k.pubkey_str16_format;
|
||||
j["pubkey"]["default"]["key"] = k.pubkey;
|
||||
j["pubkey"]["default"]["format"] = k.pubkey_format;
|
||||
j["ocmf_config_table"] = json::array();
|
||||
if (k.ocmf_config_table.size() > 0) {
|
||||
for (uint8_t n = 0; n < k.ocmf_config_table.size(); n++) {
|
||||
j["ocmf_config_table"][n] = module::conversions::hexdump((uint8_t)k.ocmf_config_table.at(n));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const DeviceDiagnostics& k) {
|
||||
os << json(k).dump(4);
|
||||
return os;
|
||||
}
|
||||
|
||||
void to_json(json& j, const Logging& k) {
|
||||
j["log"] = json();
|
||||
j["log"]["last"] = json();
|
||||
j["log"]["last"]["type"] = "" + std::to_string((int)k.last_log.type) + ": " + log_type_to_string(k.last_log.type);
|
||||
j["log"]["last"]["second_index"] = k.last_log.second_index;
|
||||
j["log"]["last"]["utc_time"] = module::conversions::u32_epoch_to_rfc3339(k.last_log.utc_time);
|
||||
j["log"]["last"]["utc_offset_quarterhours"] = k.last_log.utc_offset;
|
||||
j["log"]["last"]["old_value"] = module::conversions::hexdump(k.last_log.old_value);
|
||||
j["log"]["last"]["new_value"] = module::conversions::hexdump(k.last_log.new_value);
|
||||
j["log"]["last"]["server_id"] = module::conversions::hexdump(k.last_log.server_id);
|
||||
j["log"]["last"]["signature"] = module::conversions::hexdump(k.last_log.signature);
|
||||
|
||||
j["errors"] = json();
|
||||
j["errors"]["system"] = json();
|
||||
j["errors"]["system"]["last"] = json::array();
|
||||
for (uint8_t n = 0; n < 5; n++) {
|
||||
j["errors"]["system"]["last"][n]["id"] = k.source[(uint8_t)ast_app_layer::ErrorSource::SYSTEM]
|
||||
.category[(uint8_t)ast_app_layer::ErrorCategory::LAST]
|
||||
.error[n]
|
||||
.id;
|
||||
j["errors"]["system"]["last"][n]["priority"] = k.source[(uint8_t)ast_app_layer::ErrorSource::SYSTEM]
|
||||
.category[(uint8_t)ast_app_layer::ErrorCategory::LAST]
|
||||
.error[n]
|
||||
.priority;
|
||||
j["errors"]["system"]["last"][n]["counter"] = k.source[(uint8_t)ast_app_layer::ErrorSource::SYSTEM]
|
||||
.category[(uint8_t)ast_app_layer::ErrorCategory::LAST]
|
||||
.error[n]
|
||||
.counter;
|
||||
}
|
||||
j["errors"]["system"]["last_critical"] = json::array();
|
||||
for (uint8_t n = 0; n < 5; n++) {
|
||||
j["errors"]["system"]["last_critical"][n]["id"] =
|
||||
k.source[(uint8_t)ast_app_layer::ErrorSource::SYSTEM]
|
||||
.category[(uint8_t)ast_app_layer::ErrorCategory::LAST_CRITICAL]
|
||||
.error[n]
|
||||
.id;
|
||||
j["errors"]["system"]["last_critical"][n]["priority"] =
|
||||
k.source[(uint8_t)ast_app_layer::ErrorSource::SYSTEM]
|
||||
.category[(uint8_t)ast_app_layer::ErrorCategory::LAST_CRITICAL]
|
||||
.error[n]
|
||||
.priority;
|
||||
j["errors"]["system"]["last_critical"][n]["counter"] =
|
||||
k.source[(uint8_t)ast_app_layer::ErrorSource::SYSTEM]
|
||||
.category[(uint8_t)ast_app_layer::ErrorCategory::LAST_CRITICAL]
|
||||
.error[n]
|
||||
.counter;
|
||||
}
|
||||
j["errors"]["communication"] = json();
|
||||
j["errors"]["communication"]["last"] = json::array();
|
||||
for (uint8_t n = 0; n < 5; n++) {
|
||||
j["errors"]["communication"]["last"][n]["id"] = k.source[(uint8_t)ast_app_layer::ErrorSource::COMMUNICATION]
|
||||
.category[(uint8_t)ast_app_layer::ErrorCategory::LAST]
|
||||
.error[n]
|
||||
.id;
|
||||
j["errors"]["communication"]["last"][n]["priority"] =
|
||||
k.source[(uint8_t)ast_app_layer::ErrorSource::COMMUNICATION]
|
||||
.category[(uint8_t)ast_app_layer::ErrorCategory::LAST]
|
||||
.error[n]
|
||||
.priority;
|
||||
j["errors"]["communication"]["last"][n]["counter"] =
|
||||
k.source[(uint8_t)ast_app_layer::ErrorSource::COMMUNICATION]
|
||||
.category[(uint8_t)ast_app_layer::ErrorCategory::LAST]
|
||||
.error[n]
|
||||
.counter;
|
||||
}
|
||||
j["errors"]["communication"]["last_critical"] = json::array();
|
||||
for (uint8_t n = 0; n < 5; n++) {
|
||||
j["errors"]["communication"]["last_critical"][n]["id"] =
|
||||
k.source[(uint8_t)ast_app_layer::ErrorSource::COMMUNICATION]
|
||||
.category[(uint8_t)ast_app_layer::ErrorCategory::LAST_CRITICAL]
|
||||
.error[n]
|
||||
.id;
|
||||
j["errors"]["communication"]["last_critical"][n]["priority"] =
|
||||
k.source[(uint8_t)ast_app_layer::ErrorSource::COMMUNICATION]
|
||||
.category[(uint8_t)ast_app_layer::ErrorCategory::LAST_CRITICAL]
|
||||
.error[n]
|
||||
.priority;
|
||||
j["errors"]["communication"]["last_critical"][n]["counter"] =
|
||||
k.source[(uint8_t)ast_app_layer::ErrorSource::COMMUNICATION]
|
||||
.category[(uint8_t)ast_app_layer::ErrorCategory::LAST_CRITICAL]
|
||||
.error[n]
|
||||
.counter;
|
||||
}
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const Logging& k) {
|
||||
os << json(k).dump(4);
|
||||
return os;
|
||||
}
|
||||
|
||||
namespace conversions {
|
||||
|
||||
std::string hexdump(const std::vector<std::uint8_t>& msg) {
|
||||
std::stringstream ss;
|
||||
for (auto index : msg) {
|
||||
ss << std::hex << std::uppercase << std::setw(2) << std::setfill('0') << (int)index << " ";
|
||||
}
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
std::string hexdump(const std::vector<std::uint8_t>& msg, std::uint8_t start, std::uint8_t number_of_chars) {
|
||||
if ((start + number_of_chars) > msg.size())
|
||||
return std::string{};
|
||||
std::stringstream ss;
|
||||
for (std::uint8_t n = start; n < (start + number_of_chars); n++) {
|
||||
ss << std::hex << std::uppercase << std::setw(2) << std::setfill('0') << (int)msg.at(n);
|
||||
if (n < (start + number_of_chars - 1))
|
||||
ss << " ";
|
||||
}
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
std::string hexdump(std::uint8_t msg) {
|
||||
std::stringstream ss;
|
||||
ss << "0x" << std::hex << std::uppercase << std::setw(2) << std::setfill('0') << (int)msg;
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
std::string hexdump(std::uint16_t msg) {
|
||||
std::stringstream ss;
|
||||
ss << "0x" << std::hex << std::uppercase << std::setw(4) << std::setfill('0') << (uint16_t)msg;
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
std::string get_string(const std::vector<std::uint8_t>& vec) {
|
||||
std::string str;
|
||||
for (std::uint16_t n = 0; n < vec.size(); n++) {
|
||||
if ((vec[n] < ' ') || (vec[n] > '~')) {
|
||||
str += " ";
|
||||
} else {
|
||||
str += vec[n];
|
||||
}
|
||||
}
|
||||
return std::move(str);
|
||||
}
|
||||
|
||||
std::string u32_epoch_to_rfc3339(std::uint32_t epoch_time) {
|
||||
auto tt = static_cast<time_t>(epoch_time);
|
||||
std::tm tm = *std::gmtime(&tt);
|
||||
std::stringstream ss;
|
||||
ss << std::put_time(&tm, "%Y-%m-%dT%H:%M:%S.000Z");
|
||||
return std::move(ss.str());
|
||||
}
|
||||
|
||||
} // namespace conversions
|
||||
} // namespace module
|
||||
@@ -0,0 +1,143 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#ifndef DIAGNOSTICS_HPP
|
||||
#define DIAGNOSTICS_HPP
|
||||
|
||||
#include "ast_app_layer.hpp"
|
||||
#include <bitset>
|
||||
#include <cstdint>
|
||||
#include <date/date.h>
|
||||
#include <date/tz.h>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
namespace module {
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
struct OcmfStats {
|
||||
std::uint32_t number_transactions{};
|
||||
std::uint32_t timestamp_first_transaction{};
|
||||
std::uint32_t timestamp_last_transaction{};
|
||||
std::uint32_t max_number_of_transactions{};
|
||||
};
|
||||
|
||||
struct OcmfInfo {
|
||||
std::string gateway_id;
|
||||
std::string manufacturer;
|
||||
std::string model;
|
||||
};
|
||||
|
||||
struct DeviceData {
|
||||
std::uint32_t utc_time_s{};
|
||||
std::uint8_t gmt_offset_quarterhours{};
|
||||
std::uint64_t total_start_import_energy_Wh{}; // meter value needs to be divided by 10 for Wh
|
||||
std::uint64_t total_stop_import_energy_Wh{}; // meter value needs to be divided by 10 for Wh
|
||||
std::uint64_t total_start_export_energy_Wh{}; // meter value needs to be divided by 10 for Wh
|
||||
std::uint64_t total_stop_export_energy_Wh{}; // meter value needs to be divided by 10 for Wh
|
||||
std::uint32_t total_transaction_duration_s{}; // must be less than 27 days in total
|
||||
OcmfStats ocmf_stats;
|
||||
std::string last_ocmf_transaction;
|
||||
std::string requested_ocmf;
|
||||
OcmfInfo ocmf_info;
|
||||
std::uint64_t total_dev_import_energy_Wh{}; // meter value needs to be divided by 10 for Wh
|
||||
std::uint64_t total_dev_export_energy_Wh{}; // meter value needs to be divided by 10 for Wh
|
||||
std::uint64_t ab_status{};
|
||||
};
|
||||
|
||||
void to_json(json& j, const DeviceData& k);
|
||||
std::ostream& operator<<(std::ostream& os, const DeviceData& k);
|
||||
|
||||
struct LogStats {
|
||||
std::uint32_t number_log_entries{};
|
||||
std::uint32_t timestamp_first_log{};
|
||||
std::uint32_t timestamp_last_log{};
|
||||
std::uint32_t max_number_of_logs{}; // ???
|
||||
};
|
||||
|
||||
struct ApplicationBoardInfo {
|
||||
std::string type;
|
||||
std::string hw_ver;
|
||||
std::string server_id;
|
||||
std::uint8_t mode{};
|
||||
std::uint32_t serial_number{};
|
||||
std::string sw_ver;
|
||||
std::uint16_t fw_crc{};
|
||||
std::uint16_t fw_hash{};
|
||||
};
|
||||
|
||||
struct MeteringBoardInfo {
|
||||
std::string hw_ver;
|
||||
std::string sw_ver;
|
||||
std::uint16_t fw_crc{};
|
||||
};
|
||||
|
||||
struct DeviceDiagnostics {
|
||||
std::string charge_point_id;
|
||||
std::uint8_t charge_point_id_type{0};
|
||||
LogStats log_stats;
|
||||
ApplicationBoardInfo app_board;
|
||||
MeteringBoardInfo m_board;
|
||||
std::string pubkey_asn1;
|
||||
std::string pubkey_str16;
|
||||
std::string pubkey;
|
||||
std::uint8_t pubkey_str16_format{}; // 0x04 for uncompressed string
|
||||
std::uint8_t pubkey_format{}; // 0x04 for uncompressed string
|
||||
std::vector<std::uint8_t> ocmf_config_table;
|
||||
};
|
||||
|
||||
void to_json(json& j, const DeviceDiagnostics& k);
|
||||
std::ostream& operator<<(std::ostream& os, const DeviceDiagnostics& k);
|
||||
|
||||
// TODO(LAD): add error data
|
||||
|
||||
struct ErrorData {
|
||||
std::uint32_t id{0};
|
||||
std::uint16_t priority{0};
|
||||
std::uint32_t counter{0};
|
||||
};
|
||||
|
||||
struct FiveErrors {
|
||||
ErrorData error[5];
|
||||
};
|
||||
|
||||
struct ErrorSet {
|
||||
FiveErrors category[4];
|
||||
};
|
||||
|
||||
struct Logging {
|
||||
ast_app_layer::LogEntry last_log;
|
||||
ErrorSet source[2];
|
||||
};
|
||||
|
||||
void to_json(json& j, const Logging& k);
|
||||
std::ostream& operator<<(std::ostream& os, const Logging& k);
|
||||
|
||||
namespace conversions {
|
||||
|
||||
template <typename T> static std::string to_bin_string(const T& num) {
|
||||
std::stringstream ss{};
|
||||
for (uint8_t n = 0; n < sizeof(T); n++) {
|
||||
ss << std::bitset<8>(num >> ((sizeof(T) - n - 1) * 8));
|
||||
if (n % 2) {
|
||||
if (n != sizeof(T) - 1) {
|
||||
ss << " - ";
|
||||
}
|
||||
} else {
|
||||
ss << " ";
|
||||
}
|
||||
}
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
std::string hexdump(const std::vector<std::uint8_t>& msg);
|
||||
std::string hexdump(const std::vector<std::uint8_t>& msg, std::uint8_t start, std::uint8_t number_of_chars);
|
||||
std::string hexdump(std::uint8_t msg);
|
||||
std::string hexdump(std::uint16_t msg);
|
||||
std::string get_string(const std::vector<std::uint8_t>& vec);
|
||||
std::string u32_epoch_to_rfc3339(std::uint32_t epoch_time);
|
||||
|
||||
} // namespace conversions
|
||||
} // namespace module
|
||||
|
||||
#endif // DIAGNOSTICS_HPP
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,123 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
#ifndef MAIN_POWERMETER_IMPL_HPP
|
||||
#define MAIN_POWERMETER_IMPL_HPP
|
||||
|
||||
//
|
||||
// AUTO GENERATED - MARKED REGIONS WILL BE KEPT
|
||||
// template version 3
|
||||
//
|
||||
|
||||
#include <generated/interfaces/powermeter/Implementation.hpp>
|
||||
|
||||
#include "../AST_DC650.hpp"
|
||||
|
||||
// ev@75ac1216-19eb-4182-a85c-820f1fc2c091:v1
|
||||
// insert your custom include headers here
|
||||
#include "ast_app_layer.hpp"
|
||||
#include "diagnostics.hpp"
|
||||
#include "serial_device.hpp"
|
||||
#include "slip_protocol.hpp"
|
||||
// ev@75ac1216-19eb-4182-a85c-820f1fc2c091:v1
|
||||
|
||||
namespace module {
|
||||
namespace main {
|
||||
|
||||
struct Conf {
|
||||
int powermeter_device_id;
|
||||
std::string serial_port;
|
||||
int baudrate;
|
||||
int parity;
|
||||
int rs485_direction_gpio;
|
||||
bool ignore_echo;
|
||||
int gmt_offset_quarter_hours;
|
||||
bool publish_device_data;
|
||||
bool publish_device_diagnostics;
|
||||
};
|
||||
|
||||
class powermeterImpl : public powermeterImplBase {
|
||||
public:
|
||||
powermeterImpl() = delete;
|
||||
powermeterImpl(Everest::ModuleAdapter* ev, const Everest::PtrContainer<AST_DC650>& mod, Conf& config) :
|
||||
powermeterImplBase(ev, "main"), mod(mod), config(config){};
|
||||
|
||||
// ev@8ea32d28-373f-4c90-ae5e-b4fcc74e2a61:v1
|
||||
// insert your public definitions here
|
||||
// ev@8ea32d28-373f-4c90-ae5e-b4fcc74e2a61:v1
|
||||
|
||||
protected:
|
||||
// command handler functions (virtual)
|
||||
virtual types::powermeter::TransactionStartResponse
|
||||
handle_start_transaction(types::powermeter::TransactionReq& value) override;
|
||||
virtual types::powermeter::TransactionStopResponse handle_stop_transaction(std::string& transaction_id) override;
|
||||
|
||||
// ev@d2d1847a-7b88-41dd-ad07-92785f06f5c4:v1
|
||||
// insert your protected definitions here
|
||||
// ev@d2d1847a-7b88-41dd-ad07-92785f06f5c4:v1
|
||||
|
||||
private:
|
||||
const Everest::PtrContainer<AST_DC650>& mod;
|
||||
const Conf& config;
|
||||
|
||||
virtual void init() override;
|
||||
virtual void ready() override;
|
||||
|
||||
// ev@3370e4dd-95f4-47a9-aaec-ea76f34a66c9:v1
|
||||
// insert your private definitions here
|
||||
|
||||
enum class MessageStatus : std::uint8_t {
|
||||
NONE = 0,
|
||||
SENT = 1,
|
||||
RECEIVED = 2
|
||||
};
|
||||
|
||||
MessageStatus start_transaction_msg_status{MessageStatus::NONE};
|
||||
ast_app_layer::CommandResult start_transact_result{};
|
||||
MessageStatus stop_transaction_msg_status{MessageStatus::NONE};
|
||||
ast_app_layer::CommandResult stop_transact_result{};
|
||||
MessageStatus get_transaction_values_msg_status{MessageStatus::NONE};
|
||||
|
||||
serial_device::SerialDevice serial_device{};
|
||||
slip_protocol::SlipProtocol slip{};
|
||||
ast_app_layer::AstAppLayer app_layer{};
|
||||
|
||||
types::powermeter::Powermeter pm_last_values;
|
||||
|
||||
DeviceData device_data_obj{};
|
||||
DeviceDiagnostics device_diagnostics_obj{};
|
||||
Logging logging_obj{};
|
||||
ast_app_layer::ErrorCategory category_requested{};
|
||||
ast_app_layer::ErrorSource source_requested{};
|
||||
std::uint8_t error_diagnostics_target{0};
|
||||
std::string last_ocmf_str;
|
||||
|
||||
void init_default_values();
|
||||
void read_powermeter_values();
|
||||
void set_device_time();
|
||||
void set_device_charge_point_id(ast_app_layer::UserIdType id_type, std::string charge_point_id);
|
||||
void read_device_data();
|
||||
void read_diagnostics_data();
|
||||
void publish_device_data_topic();
|
||||
void publish_device_diagnostics_topic();
|
||||
void publish_logging_topic();
|
||||
void get_device_public_key();
|
||||
void readRegisters();
|
||||
ast_app_layer::CommandResult process_response(const std::vector<std::uint8_t>& register_message);
|
||||
void request_device_type();
|
||||
void request_error_diagnostics(std::uint8_t addr);
|
||||
void error_diagnostics(std::uint8_t addr);
|
||||
ast_app_layer::CommandResult receive_response();
|
||||
std::string get_meter_ocmf();
|
||||
|
||||
static constexpr auto TIMEOUT_2s{std::chrono::seconds(2)};
|
||||
// ev@3370e4dd-95f4-47a9-aaec-ea76f34a66c9:v1
|
||||
};
|
||||
|
||||
// ev@3d7da0ad-02c2-493d-9920-0bbbd56b9876:v1
|
||||
// insert other definitions here
|
||||
// ev@3d7da0ad-02c2-493d-9920-0bbbd56b9876:v1
|
||||
|
||||
} // namespace main
|
||||
} // namespace module
|
||||
|
||||
#endif // MAIN_POWERMETER_IMPL_HPP
|
||||
@@ -0,0 +1,60 @@
|
||||
description: Module that collects power and energy measurements from an AST powermeter
|
||||
provides:
|
||||
main:
|
||||
description: Implementation of the driver functionality
|
||||
interface: powermeter
|
||||
config:
|
||||
powermeter_device_id:
|
||||
description: The powermeter's address on the serial bus
|
||||
type: integer
|
||||
minimum: 0
|
||||
maximum: 255
|
||||
default: 1
|
||||
serial_port:
|
||||
description: Serial port the hardware is connected to
|
||||
type: string
|
||||
default: /dev/ttyUSB0
|
||||
baudrate:
|
||||
description: Baudrate
|
||||
type: integer
|
||||
minimum: 0
|
||||
maximum: 230400
|
||||
default: 9600
|
||||
parity:
|
||||
description: 'Parity bit: 0: None, 1: Odd, 2: Even'
|
||||
type: integer
|
||||
minimum: 0
|
||||
maximum: 2
|
||||
default: 0
|
||||
rs485_direction_gpio:
|
||||
description: GPIO to use for direction switching. Set to -1 to disable.
|
||||
type: integer
|
||||
default: -1
|
||||
ignore_echo:
|
||||
description: On some hardware every message that is sent is read back, this setting filters the sent message in the reply.
|
||||
type: boolean
|
||||
default: false
|
||||
gmt_offset_quarter_hours:
|
||||
description: GMT offset in quarters of an hour, e.g. 8 = +2h
|
||||
type: integer
|
||||
minimum: -47
|
||||
maximum: 47
|
||||
default: 0
|
||||
publish_device_data:
|
||||
description: Regularly publish device data not covered by the "powermeter" interface (for debugging purposes).
|
||||
type: boolean
|
||||
default: false
|
||||
publish_device_diagnostics:
|
||||
description: Regularly publish device diagnostics (e.g. log messages, errors, SW/HW revisions; for debugging purposes).
|
||||
type: boolean
|
||||
default: false
|
||||
enable_external_mqtt: true
|
||||
metadata:
|
||||
license: https://opensource.org/licenses/Apache-2.0
|
||||
authors:
|
||||
- Lars Dieckmann
|
||||
- Andreas Heinrich
|
||||
- Cornelius Claussen
|
||||
- Florin Mihut
|
||||
- Jan Christoph Habig
|
||||
- Kai-Uwe Hermann
|
||||
@@ -0,0 +1,190 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include "serial_device.hpp"
|
||||
|
||||
#include <cstring>
|
||||
#include <endian.h>
|
||||
#include <everest/logging.hpp>
|
||||
#include <fcntl.h>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <termios.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <sys/select.h>
|
||||
#include <sys/time.h>
|
||||
|
||||
#include <fmt/core.h>
|
||||
|
||||
namespace {
|
||||
std::string hexdump(std::uint8_t* msg, int msg_len) {
|
||||
std::stringstream ss;
|
||||
for (int i = 0; i < msg_len; i++) {
|
||||
ss << std::hex << static_cast<int>(msg[i]) << " ";
|
||||
}
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
std::string hexdump(std::vector<std::uint8_t> msg) {
|
||||
std::stringstream ss;
|
||||
|
||||
for (auto index : msg) {
|
||||
ss << std::hex << std::setw(2) << std::setfill('0') << static_cast<int>(index) << " ";
|
||||
}
|
||||
return ss.str();
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace serial_device {
|
||||
|
||||
SerialDevice::~SerialDevice() {
|
||||
if (fd != 0) {
|
||||
close(fd);
|
||||
}
|
||||
}
|
||||
|
||||
bool SerialDevice::open_device(const std::string& device, int _baud, bool _ignore_echo) {
|
||||
|
||||
ignore_echo = _ignore_echo;
|
||||
|
||||
fd = open(device.c_str(), O_RDWR | O_NOCTTY | O_SYNC);
|
||||
if (fd < 0) {
|
||||
EVLOG_error << fmt::format("Serial: error {} opening {}: {}", errno, device, strerror(errno));
|
||||
return false;
|
||||
}
|
||||
|
||||
int baud;
|
||||
switch (_baud) {
|
||||
case 9600:
|
||||
baud = B9600;
|
||||
break;
|
||||
case 19200:
|
||||
baud = B19200;
|
||||
break;
|
||||
case 38400:
|
||||
baud = B38400;
|
||||
break;
|
||||
case 57600:
|
||||
baud = B57600;
|
||||
break;
|
||||
case 115200:
|
||||
baud = B115200;
|
||||
break;
|
||||
case 230400:
|
||||
baud = B230400;
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
struct termios tty;
|
||||
if (tcgetattr(fd, &tty) != 0) {
|
||||
printf("Serial: error %d from tcgetattr\n", errno);
|
||||
return false;
|
||||
}
|
||||
|
||||
cfsetospeed(&tty, baud);
|
||||
cfsetispeed(&tty, baud);
|
||||
|
||||
tty.c_cflag = (tty.c_cflag & ~CSIZE) | CS8; // 8-bit chars
|
||||
// disable IGNBRK for mismatched speed tests; otherwise receive break
|
||||
// as \000 chars
|
||||
tty.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON | IXOFF | IXANY);
|
||||
tty.c_lflag = 0; // no signaling chars, no echo,
|
||||
// no canonical processing
|
||||
tty.c_oflag = 0; // no remapping, no delays
|
||||
tty.c_cc[VMIN] = 1; // read blocks
|
||||
tty.c_cc[VTIME] = 1; // 0.1 seconds inter character read timeout after first byte was received
|
||||
|
||||
tty.c_cflag |= (CLOCAL | CREAD); // ignore modem controls,
|
||||
// enable reading
|
||||
tty.c_cflag &= ~(PARENB | PARODD); // shut off parity
|
||||
tty.c_cflag &= ~CSTOPB; // 1 Stop bit
|
||||
tty.c_cflag &= ~CRTSCTS;
|
||||
|
||||
if (tcsetattr(fd, TCSANOW, &tty) != 0) {
|
||||
EVLOG_error << "Serial: error from tcsetattr: " << errno;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
This function receives a byte array.
|
||||
*/
|
||||
int SerialDevice::rx(std::vector<std::uint8_t>& rxbuf, std::optional<int> initial_timeout_ms,
|
||||
std::optional<int> in_msg_timeout_ms) {
|
||||
std::scoped_lock lock(serial_mutex);
|
||||
int _initial_timeout = SERIAL_RX_INITIAL_TIMEOUT_MS;
|
||||
if (initial_timeout_ms.has_value()) {
|
||||
_initial_timeout = initial_timeout_ms.value();
|
||||
}
|
||||
int _in_msg_timeout = SERIAL_RX_WITHIN_MESSAGE_TIMEOUT_MS;
|
||||
if (in_msg_timeout_ms.has_value()) {
|
||||
_in_msg_timeout = in_msg_timeout_ms.value();
|
||||
}
|
||||
struct timeval timeout;
|
||||
timeout.tv_sec = 0;
|
||||
timeout.tv_usec = _initial_timeout * 1000; // intial timeout until device responds
|
||||
fd_set set;
|
||||
FD_ZERO(&set);
|
||||
FD_SET(fd, &set);
|
||||
|
||||
int bytes_read_total = 0;
|
||||
while (true) {
|
||||
int rv = select(fd + 1, &set, NULL, NULL, &timeout);
|
||||
timeout.tv_usec = _in_msg_timeout * 1000; // reduce timeout after first chunk,
|
||||
// no uneccesary waiting at the end of the message
|
||||
if (rv == -1) { // error in select function call
|
||||
perror("rx: select:");
|
||||
break;
|
||||
} else if (rv == 0) { // no more bytes to read within timeout, so transfer is complete
|
||||
EVLOG_debug << "No more bytes to read within timeout. (rv == 0)";
|
||||
break;
|
||||
} else { // received more bytes, add them to buffer
|
||||
// do we have space in the rx buffer left?
|
||||
if (bytes_read_total >= rxbuf.capacity()) {
|
||||
// no buffer space left, but more to read.
|
||||
EVLOG_info
|
||||
<< R"(No buffer space left, but more to read. (Did you mean to set "ignore_echo" to "false"?))";
|
||||
break;
|
||||
}
|
||||
|
||||
rxbuf.resize(rxbuf.capacity());
|
||||
int bytes_read = read(fd, static_cast<std::uint8_t*>(&rxbuf[0] + bytes_read_total),
|
||||
static_cast<size_t>(rxbuf.capacity() - bytes_read_total));
|
||||
|
||||
if (bytes_read > 0) {
|
||||
bytes_read_total += bytes_read;
|
||||
rxbuf.resize(bytes_read_total);
|
||||
} else if (bytes_read < 0) {
|
||||
EVLOG_error << "Error reading from device: " << strerror(errno);
|
||||
}
|
||||
}
|
||||
}
|
||||
return bytes_read_total;
|
||||
}
|
||||
|
||||
/*
|
||||
This function transmits a byte vector.
|
||||
*/
|
||||
void SerialDevice::tx(const std::vector<std::uint8_t>& request) {
|
||||
{
|
||||
std::scoped_lock lock(serial_mutex);
|
||||
// clear input and output buffer
|
||||
tcflush(fd, TCIOFLUSH);
|
||||
|
||||
// write to serial port
|
||||
write(fd, request.data(), request.size());
|
||||
tcdrain(fd);
|
||||
}
|
||||
if (ignore_echo) {
|
||||
// read back echo of what we sent and ignore it
|
||||
std::vector<std::uint8_t> req_buf{};
|
||||
req_buf.reserve(request.size() + 1);
|
||||
rx(req_buf, std::nullopt, std::nullopt);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace serial_device
|
||||
@@ -0,0 +1,44 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
/*
|
||||
This is an implementation for custom serial communications
|
||||
*/
|
||||
#ifndef SERIAL_DEVICE
|
||||
#define SERIAL_DEVICE
|
||||
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace serial_device {
|
||||
|
||||
constexpr int SERIAL_RX_INITIAL_TIMEOUT_MS = 500;
|
||||
constexpr int SERIAL_RX_WITHIN_MESSAGE_TIMEOUT_MS = 100;
|
||||
|
||||
class SerialDevice {
|
||||
|
||||
public:
|
||||
SerialDevice() = default;
|
||||
SerialDevice(const SerialDevice&) = delete;
|
||||
SerialDevice(SerialDevice&&) = delete;
|
||||
SerialDevice& operator=(const SerialDevice&) = delete;
|
||||
SerialDevice& operator=(SerialDevice&&) = delete;
|
||||
~SerialDevice();
|
||||
|
||||
bool open_device(const std::string& device, int baud, bool ignore_echo);
|
||||
void tx(const std::vector<std::uint8_t>& request);
|
||||
int rx(std::vector<std::uint8_t>& rxbuf, std::optional<int> initial_timeout_ms,
|
||||
std::optional<int> in_msg_timeout_ms);
|
||||
|
||||
private:
|
||||
// Serial interface
|
||||
int fd{0};
|
||||
bool ignore_echo{false};
|
||||
|
||||
std::mutex serial_mutex;
|
||||
};
|
||||
|
||||
} // namespace serial_device
|
||||
#endif // SERIAL_DEVICE
|
||||
@@ -0,0 +1,218 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include "slip_protocol.hpp"
|
||||
|
||||
#include <cstring>
|
||||
#include <endian.h>
|
||||
#include <everest/crc/crc.hpp>
|
||||
#include <everest/logging.hpp>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <sys/select.h>
|
||||
#include <sys/time.h>
|
||||
|
||||
#include <fmt/core.h>
|
||||
|
||||
namespace {
|
||||
inline void remove_start_and_stop_frame(std::vector<std::uint8_t>& vec) {
|
||||
vec.erase(vec.begin());
|
||||
vec.pop_back();
|
||||
}
|
||||
|
||||
// TODO: this is a duplicate of the implementation in DZG_GSH01, refactor into common library
|
||||
inline bool is_message_crc_correct(std::vector<std::uint8_t>& vec) {
|
||||
if (vec.size() < 2) {
|
||||
return false;
|
||||
}
|
||||
const auto crc_check = static_cast<std::uint16_t>((vec.at(vec.size() - 1) << 8) | vec.at(vec.size() - 2));
|
||||
// remove CRC tail from vector
|
||||
for (std::uint8_t i = 0; i < 2; i++) {
|
||||
vec.pop_back();
|
||||
}
|
||||
const std::uint16_t crc_calc = calculate_xModem_crc16(vec);
|
||||
return crc_check == crc_calc;
|
||||
}
|
||||
|
||||
inline void restore_special_characters(std::vector<std::uint8_t>& vec) {
|
||||
for (std::uint16_t j = 0; j < (vec.size() - 1);
|
||||
j++) { // can only go to vec.size() - 1 because two bytes will be checked
|
||||
if ((vec.at(j) == 0xDB) && (vec.at(j + 1) == 0xDC)) {
|
||||
vec.at(j) = 0xC0;
|
||||
vec.erase(vec.begin() + j + 1);
|
||||
} else if ((vec.at(j) == 0xDB) && (vec.at(j + 1) == 0xDD)) {
|
||||
vec.at(j) = 0xDB;
|
||||
vec.erase(vec.begin() + j + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace slip_protocol {
|
||||
|
||||
std::vector<std::uint8_t> SlipProtocol::package_single(std::uint8_t address, const std::vector<std::uint8_t>& payload) {
|
||||
std::vector<std::uint8_t> vec{};
|
||||
|
||||
// address
|
||||
vec.push_back(address);
|
||||
|
||||
// payload
|
||||
for (auto payload_byte : payload) {
|
||||
if (payload_byte == 0xC0) { // check for and replace special char 0xC0
|
||||
vec.push_back(0xDB);
|
||||
vec.push_back(0xDC);
|
||||
} else if (payload_byte == 0xDB) { // check for and replace special char 0xDB
|
||||
vec.push_back(0xDB);
|
||||
vec.push_back(0xDD);
|
||||
} else { // otherwise just use normal input
|
||||
vec.push_back(payload_byte);
|
||||
}
|
||||
}
|
||||
|
||||
// CRC16
|
||||
std::uint16_t crc = calculate_xModem_crc16(vec);
|
||||
vec.push_back(static_cast<std::uint8_t>(crc & 0x00FF)); // LSB CRC16
|
||||
vec.push_back(static_cast<std::uint8_t>((crc >> 8) & 0x00FF)); // MSB CRC16
|
||||
|
||||
// add start frame to front (can be done only after CRC has been calculated, as start frame is not part of CRC)
|
||||
vec.insert(vec.begin(), SLIP_START_END_FRAME);
|
||||
|
||||
// end frame
|
||||
vec.push_back(SLIP_START_END_FRAME);
|
||||
|
||||
return vec;
|
||||
}
|
||||
|
||||
std::vector<std::uint8_t> SlipProtocol::package_multi(const std::uint8_t address,
|
||||
const std::vector<std::vector<std::uint8_t>>& multi_payload) {
|
||||
std::vector<std::uint8_t> payload{};
|
||||
|
||||
// concatenate requests
|
||||
for (auto multi_payload_part : multi_payload) {
|
||||
payload.insert(payload.end(), multi_payload_part.begin(), multi_payload_part.end());
|
||||
}
|
||||
|
||||
// ...and return as one long request
|
||||
return package_single(address, payload);
|
||||
}
|
||||
|
||||
SlipReturnStatus SlipProtocol::unpack(std::vector<std::uint8_t>& message, std::uint8_t listen_to_address) {
|
||||
SlipReturnStatus retval = SlipReturnStatus::SLIP_ERROR_UNINITIALIZED;
|
||||
std::uint8_t message_start_end_frame_counter = 0;
|
||||
|
||||
if (message.size() < 1) {
|
||||
return SlipReturnStatus::SLIP_ERROR_SIZE_ERROR;
|
||||
}
|
||||
|
||||
// check if first element is SLIP_START_END_FRAME and if not, drop first item(s) until start frame is first
|
||||
std::uint8_t i = 0;
|
||||
while (i < message.size()) {
|
||||
if (message.at(i) == SLIP_START_END_FRAME)
|
||||
break;
|
||||
i++;
|
||||
}
|
||||
if (i > 0) {
|
||||
std::uint8_t j = 0;
|
||||
while (message.size() > 0) {
|
||||
if (j == i)
|
||||
break;
|
||||
message.erase(message.begin());
|
||||
j++;
|
||||
}
|
||||
}
|
||||
|
||||
// count number of SLIP_START_END_FRAMEs to check for multiple (or broken) messages
|
||||
for (std::uint16_t n = 0; n < message.size(); n++) {
|
||||
if (message.at(n) == SLIP_START_END_FRAME) {
|
||||
message_start_end_frame_counter++;
|
||||
}
|
||||
}
|
||||
|
||||
if (message_start_end_frame_counter != 2) { // unexpected/broken message -OR- multiple messages received
|
||||
|
||||
// split message vector into sub-vectors by delimiter (SLIP_START_END_FRAME)
|
||||
std::vector<std::vector<std::uint8_t>> sub_messages{};
|
||||
std::vector<std::uint8_t> current_sub_message{};
|
||||
for (std::uint16_t n = 0; n < message.size(); n++) {
|
||||
if (message.at(n) == SLIP_START_END_FRAME) {
|
||||
if (current_sub_message.size() > 0) {
|
||||
if (listen_to_address == 0xFF) {
|
||||
// listen_to_address is broadcast address: listen to all messages
|
||||
sub_messages.push_back(current_sub_message);
|
||||
} else {
|
||||
// dedicated client address selected, only listen to this address
|
||||
if (current_sub_message.at(0) == listen_to_address) {
|
||||
sub_messages.push_back(current_sub_message);
|
||||
}
|
||||
}
|
||||
current_sub_message.clear();
|
||||
} else {
|
||||
// intentionally do nothing on empty sub_message
|
||||
}
|
||||
} else {
|
||||
current_sub_message.push_back(message.at(n));
|
||||
}
|
||||
}
|
||||
|
||||
// from here on, we have all message parts as elements in sub_messages
|
||||
for (auto sub_message : sub_messages) {
|
||||
restore_special_characters(sub_message);
|
||||
// check all sub-messages' CRC and only process on match
|
||||
if (is_message_crc_correct(sub_message)) {
|
||||
// on correct CRC
|
||||
message_queue.push_back(sub_message);
|
||||
message_counter++;
|
||||
if (retval == SlipReturnStatus::SLIP_ERROR_UNINITIALIZED) { // only set SLIP_OK if no other error
|
||||
retval = SlipReturnStatus::SLIP_OK;
|
||||
}
|
||||
} else {
|
||||
retval = SlipReturnStatus::SLIP_ERROR_MALFORMED;
|
||||
EVLOG_error << "Malformed message received!";
|
||||
}
|
||||
}
|
||||
} else { // single message received
|
||||
|
||||
if (message.size() > 3) {
|
||||
// only process messages for correct client or if listen_to_address is broadcast address
|
||||
if ((message.at(1) == listen_to_address) || (0xFF == listen_to_address)) {
|
||||
// check if last element is SLIP_START_END_FRAME and if not, reduce message size
|
||||
while (message.at(message.size() - 1) != SLIP_START_END_FRAME) {
|
||||
if (message.size() <= 3)
|
||||
break;
|
||||
message.pop_back();
|
||||
}
|
||||
|
||||
remove_start_and_stop_frame(message);
|
||||
restore_special_characters(message);
|
||||
if (is_message_crc_correct(message)) {
|
||||
// message intact, check for special characters and restore to original contents
|
||||
message_queue.push_back(message);
|
||||
message_counter++;
|
||||
retval = SlipReturnStatus::SLIP_OK;
|
||||
} else {
|
||||
retval = SlipReturnStatus::SLIP_ERROR_CRC_MISMATCH;
|
||||
EVLOG_error << "CRC mismatch!";
|
||||
}
|
||||
} else {
|
||||
retval = SlipReturnStatus::SLIP_OK;
|
||||
}
|
||||
} else {
|
||||
retval = SlipReturnStatus::SLIP_ERROR_SIZE_ERROR;
|
||||
EVLOG_error << "Message broken: too short!";
|
||||
}
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
||||
std::vector<std::uint8_t> SlipProtocol::retrieve_single_message() {
|
||||
if (message_queue.size() > 0) {
|
||||
message_counter--;
|
||||
auto ret_vec = message_queue.back();
|
||||
message_queue.pop_back();
|
||||
return ret_vec;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
} // namespace slip_protocol
|
||||
@@ -0,0 +1,52 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
/*
|
||||
This is an implementation for the SLIP serial protocol
|
||||
*/
|
||||
#ifndef SLIP_PROTOCOL
|
||||
#define SLIP_PROTOCOL
|
||||
|
||||
#include <cstdint>
|
||||
#include <everest/crc/crc.hpp>
|
||||
#include <everest/logging.hpp>
|
||||
|
||||
namespace slip_protocol {
|
||||
|
||||
constexpr int SLIP_START_END_FRAME = 0xC0;
|
||||
constexpr int SLIP_BROADCAST_ADDR = 0xFF;
|
||||
|
||||
constexpr std::uint16_t SLIP_SIZE_ON_ERROR = 1;
|
||||
|
||||
constexpr std::int8_t SLIP_ERROR_SIZE_ERROR = -1;
|
||||
constexpr std::int8_t SLIP_ERROR_MALFORMED = -2;
|
||||
constexpr std::int8_t SLIP_ERROR_CRC_MISMATCH = -3;
|
||||
|
||||
enum class SlipReturnStatus : std::int8_t {
|
||||
SLIP_ERROR_CRC_MISMATCH = -3,
|
||||
SLIP_ERROR_MALFORMED = -2,
|
||||
SLIP_ERROR_SIZE_ERROR = -1,
|
||||
SLIP_OK = 0,
|
||||
SLIP_ERROR_UNINITIALIZED = 1
|
||||
};
|
||||
|
||||
class SlipProtocol {
|
||||
|
||||
public:
|
||||
std::vector<std::uint8_t> package_single(std::uint8_t address, const std::vector<std::uint8_t>& payload);
|
||||
std::vector<std::uint8_t> package_multi(std::uint8_t address,
|
||||
const std::vector<std::vector<std::uint8_t>>& multi_payload);
|
||||
|
||||
SlipReturnStatus unpack(std::vector<std::uint8_t>& message, std::uint8_t listen_to_address);
|
||||
auto get_message_counter() const {
|
||||
return message_counter;
|
||||
}
|
||||
std::vector<std::uint8_t> retrieve_single_message();
|
||||
|
||||
private:
|
||||
std::vector<std::vector<std::uint8_t>> message_queue;
|
||||
std::uint8_t message_counter{0};
|
||||
};
|
||||
|
||||
} // namespace slip_protocol
|
||||
#endif // SLIP_PROTOCOL
|
||||
Reference in New Issue
Block a user