- 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
558 lines
24 KiB
C++
558 lines
24 KiB
C++
// SPDX-License-Identifier: Apache-2.0
|
|
// Copyright 2020 - 2026 Pionix GmbH and Contributors to EVerest
|
|
|
|
#ifndef CARLO_GAVAZZI_EM580_HELPER_HPP
|
|
#define CARLO_GAVAZZI_EM580_HELPER_HPP
|
|
|
|
#include <array>
|
|
#include <cctype>
|
|
#include <chrono>
|
|
#include <cstdint>
|
|
#include <iomanip>
|
|
#include <optional>
|
|
#include <sstream>
|
|
#include <stdexcept>
|
|
#include <string>
|
|
#include <thread>
|
|
#include <vector>
|
|
|
|
#include <everest/logging.hpp>
|
|
|
|
#include "transport.hpp"
|
|
#include <generated/interfaces/powermeter/Implementation.hpp>
|
|
|
|
namespace em580 {
|
|
namespace registers {
|
|
|
|
constexpr std::int32_t MODBUS_BASE_ADDRESS = 300001;
|
|
|
|
// the following identification register is only accessible/visible when a direct single access is used
|
|
constexpr std::int32_t MODBUS_IDENTIFICATION_CODE_ADDRESS = 300012; // 000Bh: Carlo Gavazzi Controls identification code
|
|
|
|
constexpr std::int32_t MODBUS_SIGNATURE_TYPE_ADDRESS = 309472; // 24FFh: Signature type (UINT16)
|
|
constexpr std::int32_t MODBUS_PUBLIC_KEY_ADDRESS = 309473; // 2500h: Public key (UINT16[130])
|
|
// DER formatted public key (Table 4.20/4.21), mandatory to read whole block
|
|
// from 2600h.
|
|
constexpr std::int32_t MODBUS_PUBLIC_KEY_DER_ADDRESS = 309729; // 2600h: Public key DER (read-only)
|
|
constexpr std::uint16_t MODBUS_PUBLIC_KEY_DER_WORD_COUNT_256 = 46; // 2600h..262Dh (92 bytes, DER length 0x5A + 2)
|
|
constexpr std::uint16_t MODBUS_PUBLIC_KEY_DER_WORD_COUNT_384 = 62; // 2600h..263Dh (124 bytes, DER length 0x7A + 2)
|
|
|
|
constexpr std::int32_t MODBUS_SIGNED_MAP_ADDRESS = 302049;
|
|
constexpr std::uint16_t MODBUS_SIGNED_MAP_WORD_COUNT_256 = 93; // 61 words signed Data + 32 words signature
|
|
constexpr std::uint16_t MODBUS_SIGNED_MAP_WORD_COUNT_384 = 109; // 61 words signed Data + 48 words signature
|
|
|
|
constexpr std::int32_t MODBUS_REAL_TIME_VALUES_ADDRESS = 300001;
|
|
// We only need instantaneous values up to 300052 (frequency) for the live polling loop.
|
|
// Energy totals are read from 301281+ (INT64, Wh) and signed values from 302049+.
|
|
constexpr std::uint16_t MODBUS_REAL_TIME_VALUES_COUNT = 52; // Registers 300001-300052 (52 words)
|
|
|
|
constexpr std::int32_t MODBUS_REAL_TIME_ENERGY_ADDRESS_EM300_SERIES = 301025;
|
|
constexpr std::uint16_t MODBUS_REAL_TIME_ENERGY_COUNT_EM300_SERIES = 12; // Table 2.5-1: 301025-301036 (12 words)
|
|
|
|
constexpr std::int32_t MODBUS_REAL_TIME_ENERGY_ADDRESS = 301281;
|
|
constexpr std::uint16_t MODBUS_REAL_TIME_ENERGY_COUNT = 32; // Registers 301281-301312 (32 words)
|
|
|
|
constexpr std::int32_t MODBUS_TEMPERATURE_ADDRESS = 300776; // Internal Temperature
|
|
|
|
constexpr std::int32_t MODBUS_FIRMWARE_MEASURE_MODULE_ADDRESS = 300771; // Measure module firmware version/revision
|
|
constexpr std::int32_t MODBUS_FIRMWARE_COMMUNICATION_MODULE_ADDRESS =
|
|
300772; // Communication module firmware version/revision
|
|
constexpr std::int32_t MODBUS_SERIAL_NUMBER_START_ADDRESS = 320481; // Serial number (7 registers: 320481-320487)
|
|
constexpr std::uint16_t MODBUS_SERIAL_NUMBER_REGISTER_COUNT = 7; // 7 UINT16 registers = 14 bytes
|
|
constexpr std::int32_t MODBUS_PRODUCTION_YEAR_ADDRESS = 320488; // Production year (1 UINT16 register)
|
|
// same register for older series, but with following note in datasheet:
|
|
// This register is available only in EM330 and EM340 manufactured from
|
|
// October 1st 2018 (from serial number YR2018 274xxxS and following)
|
|
constexpr std::int32_t MODBUS_PRODUCTION_YEAR_ADDRESS_EM300_SERIES = 320497;
|
|
|
|
// Device state register (Table 4.30, Section 4.3.6)
|
|
constexpr std::int32_t MODBUS_DEVICE_STATE_ADDRESS = 320499; // 5012h: Device state (UINT16 bitfield)
|
|
|
|
// Time synchronization registers
|
|
constexpr std::int32_t MODBUS_UTC_TIMESTAMP_ADDRESS = 328723; // UTC Timestamp for synchronization (INT64, 4 words)
|
|
constexpr std::int32_t MODBUS_TIMEZONE_OFFSET_ADDRESS = 328722; // Local time delta in minutes (INT16, 1 word)
|
|
|
|
// OCMF Transaction registers (Table 4.34)
|
|
constexpr std::int32_t MODBUS_OCMF_IDENTIFICATION_STATUS_ADDRESS = 328673; // 7000h: OCMF Ident. Status (UINT16)
|
|
constexpr std::int32_t MODBUS_OCMF_IDENTIFICATION_LEVEL_ADDRESS = 328674; // 7001h: OCMF Ident. Level (UINT16)
|
|
constexpr std::int32_t MODBUS_OCMF_IDENTIFICATION_FLAGS_START_ADDRESS =
|
|
328675; // 7002h: OCMF Ident. Flags 1-4 (4 UINT16)
|
|
constexpr std::uint16_t MODBUS_OCMF_IDENTIFICATION_FLAGS_COUNT = 4; // 4 flags
|
|
constexpr std::int32_t MODBUS_OCMF_IDENTIFICATION_TYPE_ADDRESS = 328679; // 7006h: OCMF Ident. Type (UINT16)
|
|
constexpr std::int32_t MODBUS_OCMF_IDENTIFICATION_DATA_START_ADDRESS =
|
|
328680; // 7007h: OCMF Ident. Data (CHAR[40] = 20 words)
|
|
constexpr std::uint16_t MODBUS_OCMF_IDENTIFICATION_DATA_WORD_COUNT = 20; // 40 bytes = 20 words
|
|
constexpr std::int32_t MODBUS_OCMF_CHARGING_POINT_ID_TYPE_ADDRESS =
|
|
328700; // 701Bh: OCMF Charging point identifier type (UINT16)
|
|
constexpr std::int32_t MODBUS_OCMF_CHARGING_POINT_ID_START_ADDRESS = 328701; // 701Ch: OCMF CPI (CHAR[40] = 20 words)
|
|
constexpr std::uint16_t MODBUS_OCMF_CHARGING_POINT_ID_WORD_COUNT = 20; // 40 bytes = 20 words
|
|
constexpr std::int32_t MODBUS_OCMF_SESSION_MODALITY_ADDRESS = 328727; // 7036h: OCMF Session Modality (UINT16)
|
|
constexpr std::uint16_t MODBUS_OCMF_SESSION_MODALITY_CHARGING_VEHICLE = 0; // Charging vehicle
|
|
constexpr std::uint16_t MODBUS_OCMF_SESSION_MODALITY_VEHICLE_TO_GRID = 1; // Vehicle to grid
|
|
constexpr std::uint16_t MODBUS_OCMF_SESSION_MODALITY_BIDIRECTIONAL = 2; // Bidirectional
|
|
|
|
// Tariff text register (Table 4.32)
|
|
// 326881 (6900h): Tariff text (CHAR[252] = 126 words)
|
|
constexpr std::int32_t MODBUS_OCMF_TARIFF_TEXT_ADDRESS = 326881; // 6900h: Tariff text (CHAR[252] = 126 words)
|
|
constexpr std::uint16_t MODBUS_OCMF_TARIFF_TEXT_WORD_COUNT = 126; // 252 bytes = 126 words (CHAR[252])
|
|
constexpr std::int32_t MODBUS_OCMF_TRANSACTION_ID_GENERATION_ADDRESS =
|
|
328417; // 6F00h: OCMF Transaction ID Generation (UINT16)
|
|
|
|
// Tariff update register (Table 4.33)
|
|
constexpr std::int32_t MODBUS_OCMF_TARIFF_UPDATE_ADDRESS = 327085; // 69CCh: Tariff update (UINT16)
|
|
|
|
// OCMF Command register (Table 4.35)
|
|
// The register is UINT16 containing the ASCII code (e.g. 'B', 'E', 'A').
|
|
constexpr std::int32_t MODBUS_OCMF_COMMAND_ADDRESS = 328737; // 7040h: OCMF Command Data (UINT16)
|
|
constexpr std::uint16_t MODBUS_OCMF_COMMAND_START = 0x42; // Start transaction ('B')
|
|
constexpr std::uint16_t MODBUS_OCMF_COMMAND_END = 0x45; // End transaction ('E')
|
|
constexpr std::uint16_t MODBUS_OCMF_COMMAND_ABORT = 0x41; // Abort transaction ('A')
|
|
|
|
// OCMF State / status registers (Table 4.39 and related)
|
|
constexpr std::int32_t MODBUS_OCMF_STATE_ADDRESS = 328929; // 7100h: OCMF State (UINT16)
|
|
constexpr std::int32_t MODBUS_OCMF_TRANSACTION_ID_ADDRESS = 328931; // 7102h: OCMF Transaction ID (UINT32)
|
|
constexpr std::uint16_t MODBUS_OCMF_STATE_NOT_READY = 0; // Not ready
|
|
constexpr std::uint16_t MODBUS_OCMF_STATE_RUNNING = 1; // Running
|
|
constexpr std::uint16_t MODBUS_OCMF_STATE_READY = 2; // Ready
|
|
constexpr std::uint16_t MODBUS_OCMF_STATE_CORRUPTED = 3; // Corrupted
|
|
constexpr std::int32_t MODBUS_OCMF_STATE_SIZE_ADDRESS = 328930; // 7101h: OCMF Size (UINT16)
|
|
constexpr std::int32_t MODBUS_OCMF_STATE_FILE_ADDRESS = 328945; // 7110h: OCMF File (max theoretically 2031 words)
|
|
constexpr std::uint16_t MODBUS_OCMF_STATE_FILE_WORD_COUNT = 2031; // 2031 words = 4062 bytes
|
|
constexpr std::int32_t MODBUS_OCMF_CHARGING_STATUS_ADDRESS = 328742; // 7045h: Charging status (UINT16)
|
|
constexpr std::int32_t MODBUS_OCMF_LAST_TRANSACTION_ID_ADDRESS = 328762; // 7059h: Last transaction id (CHAR[])
|
|
constexpr std::uint16_t MODBUS_OCMF_LAST_TRANSACTION_ID_WORD_COUNT = 7; // 14 bytes = 7 words
|
|
constexpr std::int32_t MODBUS_OCMF_TIME_SYNC_STATUS_ADDRESS = 328769; // 7060h: Time synchronization status (UINT16)
|
|
|
|
} // namespace registers
|
|
} // namespace em580
|
|
|
|
namespace modbus_utils {
|
|
|
|
inline void check_bounds_or_throw(const transport::DataVector& data, transport::DataVector::size_type offset,
|
|
transport::DataVector::size_type needed_bytes, const char* what) {
|
|
if (offset > data.size() || needed_bytes > (data.size() - offset)) {
|
|
throw std::out_of_range(std::string(what) + ": offset/length out of range (offset=" + std::to_string(offset) +
|
|
", needed=" + std::to_string(needed_bytes) + ", size=" + std::to_string(data.size()) +
|
|
")");
|
|
}
|
|
}
|
|
|
|
// Strong type wrappers to prevent parameter swapping
|
|
struct ByteOffset {
|
|
explicit ByteOffset(transport::DataVector::size_type val) : value(val) {
|
|
}
|
|
explicit operator transport::DataVector::size_type() const {
|
|
return value;
|
|
}
|
|
|
|
private:
|
|
transport::DataVector::size_type value;
|
|
};
|
|
|
|
struct ByteLength {
|
|
explicit ByteLength(transport::DataVector::size_type val) : value(val) {
|
|
}
|
|
explicit operator transport::DataVector::size_type() const {
|
|
return value;
|
|
}
|
|
|
|
private:
|
|
transport::DataVector::size_type value;
|
|
};
|
|
|
|
inline std::uint32_t to_uint32(const transport::DataVector& data, ByteOffset offset) {
|
|
const auto off = static_cast<transport::DataVector::size_type>(offset);
|
|
check_bounds_or_throw(data, off, 4, "to_uint32");
|
|
return static_cast<std::uint32_t>(data[off] << 24 | data[off + 1] << 16 | data[off + 2] << 8 | data[off + 3]);
|
|
}
|
|
|
|
inline std::int32_t to_int32(const transport::DataVector& data, ByteOffset offset) {
|
|
const auto off = static_cast<transport::DataVector::size_type>(offset);
|
|
check_bounds_or_throw(data, off, 4, "to_int32");
|
|
return static_cast<std::int32_t>(data[off + 2] << 24 | data[off + 3] << 16 | data[off] << 8 | data[off + 1]);
|
|
}
|
|
|
|
inline std::int64_t to_int64(const transport::DataVector& data, ByteOffset offset) {
|
|
const auto off = static_cast<transport::DataVector::size_type>(offset);
|
|
check_bounds_or_throw(data, off, 8, "to_int64");
|
|
// EM580 Modbus spec:
|
|
// - Byte order inside a word is MSB -> LSB.
|
|
// - Word order for INT64/UINT64 is LSW -> MSW.
|
|
const std::uint64_t w0 = (static_cast<std::uint64_t>(data[off]) << 8) | static_cast<std::uint64_t>(data[off + 1]);
|
|
const std::uint64_t w1 =
|
|
(static_cast<std::uint64_t>(data[off + 2]) << 8) | static_cast<std::uint64_t>(data[off + 3]);
|
|
const std::uint64_t w2 =
|
|
(static_cast<std::uint64_t>(data[off + 4]) << 8) | static_cast<std::uint64_t>(data[off + 5]);
|
|
const std::uint64_t w3 =
|
|
(static_cast<std::uint64_t>(data[off + 6]) << 8) | static_cast<std::uint64_t>(data[off + 7]);
|
|
const std::uint64_t u = (w0) | (w1 << 16) | (w2 << 32) | (w3 << 48);
|
|
return static_cast<std::int64_t>(u);
|
|
}
|
|
|
|
inline std::uint16_t to_uint16(const transport::DataVector& data, ByteOffset offset) {
|
|
const auto off = static_cast<transport::DataVector::size_type>(offset);
|
|
check_bounds_or_throw(data, off, 2, "to_uint16");
|
|
return static_cast<std::uint16_t>(data[off] << 8 | data[off + 1]);
|
|
}
|
|
|
|
inline std::int16_t to_int16(const transport::DataVector& data, ByteOffset offset) {
|
|
std::uint16_t raw = to_uint16(data, offset);
|
|
return static_cast<std::int16_t>(raw);
|
|
}
|
|
|
|
inline std::string to_hex_string(const transport::DataVector& data, ByteOffset offset, ByteLength length) {
|
|
const auto off = static_cast<transport::DataVector::size_type>(offset);
|
|
const auto len = static_cast<transport::DataVector::size_type>(length);
|
|
check_bounds_or_throw(data, off, len, "to_hex_string");
|
|
std::stringstream ss;
|
|
for (std::size_t index = 0; index < len; ++index) {
|
|
ss << std::uppercase << std::hex << std::setfill('0') << std::setw(2) << static_cast<int>(data[off + index]);
|
|
}
|
|
return ss.str();
|
|
}
|
|
|
|
inline std::size_t max_payload_bytes_for_words(std::size_t max_words) {
|
|
const std::size_t capacity_bytes = max_words * 2;
|
|
return capacity_bytes > 0 ? capacity_bytes - 1 : 0; // reserve NUL
|
|
}
|
|
|
|
inline void log_truncation_warning_if_needed(const char* field_name, const std::string& value, std::size_t max_words) {
|
|
const std::size_t capacity_bytes = max_words * 2;
|
|
const std::size_t max_payload_bytes = max_payload_bytes_for_words(max_words);
|
|
if (value.size() > max_payload_bytes) {
|
|
EVLOG_warning << field_name << " too long (" << value.size() << " bytes). Max is " << max_payload_bytes
|
|
<< " bytes (" << max_words << " words / " << capacity_bytes << " bytes incl. NUL). "
|
|
<< "It will be truncated.";
|
|
}
|
|
}
|
|
|
|
/// Converts a string to a big-endian Modbus CHAR array (vector of UINT16 words)
|
|
/// that is **0-terminated** and contains only the **used** part (i.e. no full
|
|
/// fixed-length padding).
|
|
///
|
|
/// - Max capacity is `max_words * 2` bytes.
|
|
/// - Ensures a terminating `\\0` byte is present within the returned data.
|
|
/// - If `str` is too long, it is truncated to fit `max_words * 2 - 1` bytes (+
|
|
/// 1 byte terminator).
|
|
inline std::vector<std::uint16_t> string_to_modbus_char_array(const std::string& str, std::size_t max_words) {
|
|
const std::size_t max_bytes = max_words * 2;
|
|
if (max_bytes == 0) {
|
|
return {};
|
|
}
|
|
|
|
const std::size_t used_len = std::min(str.size(), max_bytes - 1); // leave space for terminator
|
|
const std::size_t bytes_to_write = used_len + 1; // include terminator byte
|
|
const std::size_t words_to_write = (bytes_to_write + 1) / 2; // ceil(bytes/2)
|
|
|
|
std::vector<std::uint16_t> data(words_to_write, 0);
|
|
for (std::size_t i = 0; i < used_len; ++i) {
|
|
const std::size_t word_idx = i / 2;
|
|
if ((i % 2) == 0) {
|
|
data[word_idx] = static_cast<std::uint8_t>(str[i]) << 8;
|
|
} else {
|
|
data[word_idx] |= static_cast<std::uint8_t>(str[i]);
|
|
}
|
|
}
|
|
return data;
|
|
}
|
|
|
|
} // namespace modbus_utils
|
|
|
|
namespace ocmf {
|
|
|
|
/// Confirm OCMF file read by writing NOT_READY (0) into the OCMF state
|
|
/// register.
|
|
inline void confirm_file_read(transport::AbstractModbusTransport& modbus_transport) {
|
|
std::vector<std::uint16_t> ocmf_confirmation_data = {em580::registers::MODBUS_OCMF_STATE_NOT_READY};
|
|
modbus_transport.write_multiple_registers(em580::registers::MODBUS_OCMF_STATE_ADDRESS, ocmf_confirmation_data);
|
|
}
|
|
|
|
/// Wait until OCMF state becomes READY (2).
|
|
/// @return true if READY, false on CORRUPTED or timeout.
|
|
inline bool wait_for_ready(transport::AbstractModbusTransport& modbus_transport,
|
|
std::chrono::milliseconds poll_interval = std::chrono::milliseconds{100},
|
|
int max_retries = 10) {
|
|
std::uint16_t state = em580::registers::MODBUS_OCMF_STATE_NOT_READY;
|
|
transport::DataVector state_data;
|
|
int retries = 0;
|
|
|
|
while (state != em580::registers::MODBUS_OCMF_STATE_READY) {
|
|
state_data = modbus_transport.fetch(em580::registers::MODBUS_OCMF_STATE_ADDRESS, 1);
|
|
state = modbus_utils::to_uint16(state_data, modbus_utils::ByteOffset{0});
|
|
|
|
if (state == em580::registers::MODBUS_OCMF_STATE_CORRUPTED) {
|
|
return false;
|
|
}
|
|
if (state != em580::registers::MODBUS_OCMF_STATE_READY) {
|
|
EVLOG_info << "OCMF state: " << state;
|
|
std::this_thread::sleep_for(poll_interval);
|
|
retries++;
|
|
if (retries > max_retries) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
inline bool is_uuid36(const std::string& s) {
|
|
if (s.size() != 36) {
|
|
return false;
|
|
}
|
|
for (std::size_t i = 0; i < s.size(); ++i) {
|
|
const char c = s[i];
|
|
if (i == 8 || i == 13 || i == 18 || i == 23) {
|
|
if (c != '-') {
|
|
return false;
|
|
}
|
|
continue;
|
|
}
|
|
if (!std::isxdigit(static_cast<unsigned char>(c))) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
inline std::optional<std::string> extract_transaction_id_from_ocmf_record(const std::string& ocmf_record) {
|
|
const std::string key = "\"TT\"";
|
|
std::size_t key_pos = ocmf_record.find(key);
|
|
if (key_pos == std::string::npos) {
|
|
return std::nullopt;
|
|
}
|
|
|
|
std::size_t colon_pos = ocmf_record.find(':', key_pos + key.size());
|
|
if (colon_pos == std::string::npos) {
|
|
return std::nullopt;
|
|
}
|
|
|
|
std::size_t value_start = ocmf_record.find('"', colon_pos + 1);
|
|
if (value_start == std::string::npos) {
|
|
return std::nullopt;
|
|
}
|
|
++value_start;
|
|
|
|
std::string tt_value;
|
|
tt_value.reserve(128);
|
|
bool escaped = false;
|
|
for (std::size_t i = value_start; i < ocmf_record.size(); ++i) {
|
|
const char c = ocmf_record[i];
|
|
if (escaped) {
|
|
tt_value.push_back(c);
|
|
escaped = false;
|
|
continue;
|
|
}
|
|
if (c == '\\') {
|
|
escaped = true;
|
|
continue;
|
|
}
|
|
if (c == '"') {
|
|
break;
|
|
}
|
|
tt_value.push_back(c);
|
|
}
|
|
|
|
const std::string marker = "<=>";
|
|
const std::size_t marker_pos = tt_value.rfind(marker);
|
|
if (marker_pos == std::string::npos) {
|
|
return std::nullopt;
|
|
}
|
|
|
|
std::string tail = tt_value.substr(marker_pos + marker.size());
|
|
while (!tail.empty() && std::isspace(static_cast<unsigned char>(tail.front()))) {
|
|
tail.erase(tail.begin());
|
|
}
|
|
while (!tail.empty() && std::isspace(static_cast<unsigned char>(tail.back()))) {
|
|
tail.pop_back();
|
|
}
|
|
|
|
std::optional<std::string> last_uuid;
|
|
if (tail.size() >= 36) {
|
|
for (std::size_t i = 0; i + 36 <= tail.size(); ++i) {
|
|
const std::string candidate = tail.substr(i, 36);
|
|
if (is_uuid36(candidate)) {
|
|
last_uuid = candidate;
|
|
}
|
|
}
|
|
}
|
|
return last_uuid;
|
|
}
|
|
|
|
/// Extract transaction id (UUID) from a tariff text string.
|
|
///
|
|
/// Driver convention: tariff text is written as "<user text><=><transaction_id>".
|
|
/// Returns the last UUID found after the "<=>" marker.
|
|
inline std::optional<std::string> extract_transaction_id_from_tariff_text(const std::string& tariff_text,
|
|
std::string_view marker) {
|
|
const std::size_t marker_pos = tariff_text.rfind(marker);
|
|
if (marker_pos == std::string::npos) {
|
|
return std::nullopt;
|
|
}
|
|
|
|
std::string tail = tariff_text.substr(marker_pos + marker.size());
|
|
while (!tail.empty() && std::isspace(static_cast<unsigned char>(tail.front()))) {
|
|
tail.erase(tail.begin());
|
|
}
|
|
while (!tail.empty() && std::isspace(static_cast<unsigned char>(tail.back()))) {
|
|
tail.pop_back();
|
|
}
|
|
|
|
// The transaction id is appended at the end, so search from the back.
|
|
if (tail.size() < 36) {
|
|
return std::nullopt;
|
|
}
|
|
for (std::size_t i = tail.size() - 36 + 1; i-- > 0;) {
|
|
const std::string candidate = tail.substr(i, 36);
|
|
if (is_uuid36(candidate)) {
|
|
return candidate;
|
|
}
|
|
}
|
|
return std::nullopt;
|
|
}
|
|
|
|
inline std::uint16_t flag_to_value(types::powermeter::OCMFIdentificationFlags flag) {
|
|
switch (flag) {
|
|
case types::powermeter::OCMFIdentificationFlags::RFID_NONE:
|
|
return 0;
|
|
case types::powermeter::OCMFIdentificationFlags::RFID_PLAIN:
|
|
return 1;
|
|
case types::powermeter::OCMFIdentificationFlags::RFID_RELATED:
|
|
return 2;
|
|
case types::powermeter::OCMFIdentificationFlags::RFID_PSK:
|
|
return 3;
|
|
case types::powermeter::OCMFIdentificationFlags::OCPP_NONE:
|
|
return 0;
|
|
case types::powermeter::OCMFIdentificationFlags::OCPP_RS:
|
|
return 1;
|
|
case types::powermeter::OCMFIdentificationFlags::OCPP_AUTH:
|
|
return 2;
|
|
case types::powermeter::OCMFIdentificationFlags::OCPP_RS_TLS:
|
|
return 3;
|
|
case types::powermeter::OCMFIdentificationFlags::OCPP_AUTH_TLS:
|
|
return 4;
|
|
case types::powermeter::OCMFIdentificationFlags::OCPP_CACHE:
|
|
return 5;
|
|
case types::powermeter::OCMFIdentificationFlags::OCPP_WHITELIST:
|
|
return 6;
|
|
case types::powermeter::OCMFIdentificationFlags::OCPP_CERTIFIED:
|
|
return 7;
|
|
case types::powermeter::OCMFIdentificationFlags::ISO15118_NONE:
|
|
return 0;
|
|
case types::powermeter::OCMFIdentificationFlags::ISO15118_PNC:
|
|
return 1;
|
|
case types::powermeter::OCMFIdentificationFlags::PLMN_NONE:
|
|
return 0;
|
|
case types::powermeter::OCMFIdentificationFlags::PLMN_RING:
|
|
return 1;
|
|
case types::powermeter::OCMFIdentificationFlags::PLMN_SMS:
|
|
return 2;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
inline std::uint16_t level_to_value(types::powermeter::OCMFIdentificationLevel level) {
|
|
switch (level) {
|
|
case types::powermeter::OCMFIdentificationLevel::NONE:
|
|
return 0;
|
|
case types::powermeter::OCMFIdentificationLevel::HEARSAY:
|
|
return 1;
|
|
case types::powermeter::OCMFIdentificationLevel::TRUSTED:
|
|
return 2;
|
|
case types::powermeter::OCMFIdentificationLevel::VERIFIED:
|
|
return 3;
|
|
case types::powermeter::OCMFIdentificationLevel::CERTIFIED:
|
|
return 4;
|
|
case types::powermeter::OCMFIdentificationLevel::SECURE:
|
|
return 5;
|
|
case types::powermeter::OCMFIdentificationLevel::MISMATCH:
|
|
return 6;
|
|
case types::powermeter::OCMFIdentificationLevel::INVALID:
|
|
return 7;
|
|
case types::powermeter::OCMFIdentificationLevel::OUTDATED:
|
|
return 8;
|
|
case types::powermeter::OCMFIdentificationLevel::UNKNOWN:
|
|
return 9;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
inline std::uint16_t type_to_value(types::powermeter::OCMFIdentificationType type) {
|
|
switch (type) {
|
|
case types::powermeter::OCMFIdentificationType::NONE:
|
|
return 0;
|
|
case types::powermeter::OCMFIdentificationType::DENIED:
|
|
return 1;
|
|
case types::powermeter::OCMFIdentificationType::UNDEFINED:
|
|
return 2;
|
|
case types::powermeter::OCMFIdentificationType::ISO14443:
|
|
return 10;
|
|
case types::powermeter::OCMFIdentificationType::ISO15693:
|
|
return 11;
|
|
case types::powermeter::OCMFIdentificationType::EMAID:
|
|
return 20;
|
|
case types::powermeter::OCMFIdentificationType::EVCCID:
|
|
return 21;
|
|
case types::powermeter::OCMFIdentificationType::EVCOID:
|
|
return 30;
|
|
case types::powermeter::OCMFIdentificationType::ISO7812:
|
|
return 40;
|
|
case types::powermeter::OCMFIdentificationType::CARD_TXN_NR:
|
|
return 50;
|
|
case types::powermeter::OCMFIdentificationType::CENTRAL:
|
|
return 60;
|
|
case types::powermeter::OCMFIdentificationType::CENTRAL_1:
|
|
return 61;
|
|
case types::powermeter::OCMFIdentificationType::CENTRAL_2:
|
|
return 62;
|
|
case types::powermeter::OCMFIdentificationType::LOCAL:
|
|
return 70;
|
|
case types::powermeter::OCMFIdentificationType::LOCAL_1:
|
|
return 71;
|
|
case types::powermeter::OCMFIdentificationType::LOCAL_2:
|
|
return 72;
|
|
case types::powermeter::OCMFIdentificationType::PHONE_NUMBER:
|
|
return 80;
|
|
case types::powermeter::OCMFIdentificationType::KEY_CODE:
|
|
return 90;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
} // namespace ocmf
|
|
|
|
namespace device_state_utils {
|
|
|
|
inline std::vector<std::string> decode_device_state_errors(std::uint16_t device_state) {
|
|
struct BitError {
|
|
const char* message;
|
|
std::uint16_t bit;
|
|
};
|
|
|
|
static constexpr std::array<BitError, 12> errors = {{
|
|
{"V1N over maximum range", 0U},
|
|
{"V2N over maximum range", 1U},
|
|
{"V3N over maximum range", 2U},
|
|
{"V12 over maximum range", 3U},
|
|
{"V23 over maximum range", 4U},
|
|
{"V31 over maximum range", 5U},
|
|
{"I1 over maximum range", 6U},
|
|
{"I2 over maximum range", 7U},
|
|
{"I3 over maximum range", 8U},
|
|
{"Frequency outside validity range", 9U},
|
|
{"EVCS module internal fault", 12U},
|
|
{"Measure module internal fault", 13U},
|
|
}};
|
|
|
|
std::vector<std::string> out;
|
|
for (const auto& err : errors) {
|
|
if ((device_state & static_cast<std::uint16_t>(1U << err.bit)) != 0U) {
|
|
out.emplace_back(err.message);
|
|
}
|
|
}
|
|
return out;
|
|
}
|
|
|
|
} // namespace device_state_utils
|
|
|
|
#endif // CARLO_GAVAZZI_EM580_HELPER_HPP
|