29static std::string normalizeRawFormat(
const std::string& raw) {
30 if (raw.find(
':') != std::string::npos) {
34 if (raw.find(
'#') != std::string::npos) {
35 std::string normalized = raw;
36 std::replace(normalized.begin(), normalized.end(),
'#',
':');
40 size_t commaPos = raw.find(
',');
41 if (commaPos != std::string::npos) {
42 std::string canIdPart = raw.substr(0, commaPos);
43 std::string dataPart = raw.substr(commaPos + 1);
45 canIdPart.erase(std::remove_if(canIdPart.begin(), canIdPart.end(),
46 [](
unsigned char c) { return std::isspace(c); }),
48 if (canIdPart.size() >= 2 &&
49 (canIdPart.substr(0, 2) ==
"0x" || canIdPart.substr(0, 2) ==
"0X")) {
50 canIdPart = canIdPart.substr(2);
54 std::istringstream iss(dataPart);
56 while (iss >> token) {
57 if (token.size() >= 2 &&
58 (token.substr(0, 2) ==
"0x" || token.substr(0, 2) ==
"0X")) {
59 token = token.substr(2);
64 return canIdPart +
":" + dataHex;
67 if (raw.find(
' ') != std::string::npos) {
68 std::istringstream iss(raw);
72 bool firstToken =
true;
74 while (iss >> token) {
75 if (token.size() >= 2 &&
76 (token.substr(0, 2) ==
"0x" || token.substr(0, 2) ==
"0X")) {
77 token = token.substr(2);
87 return canId +
":" + dataHex;
99 std::vector<uint8_t> canId,
100 std::vector<uint8_t> canFrame) noexcept
101 : Message(std::move(raw), Type::NMEA2000, ts),
102 canId_(std::move(canId)),
103 canFrame_(std::move(canFrame)) {}
110 const std::string context =
"Message2000::create";
112 std::string normalizedRaw = normalizeRawFormat(raw);
115 size_t colonPos = normalizedRaw.find(
':');
116 if (colonPos == std::string::npos) {
121 std::string canIdStr = normalizedRaw.substr(0, colonPos);
122 std::transform(canIdStr.begin(), canIdStr.end(), canIdStr.begin(), ::toupper);
123 std::vector<uint8_t> canId;
125 if (canIdStr.empty()) {
129 unsigned long canIdValue = 0;
148 canId.push_back(
static_cast<uint8_t
>((canIdValue >> 24) & 0xFF));
149 canId.push_back(
static_cast<uint8_t
>((canIdValue >> 16) & 0xFF));
150 canId.push_back(
static_cast<uint8_t
>((canIdValue >> 8) & 0xFF));
151 canId.push_back(
static_cast<uint8_t
>( canIdValue & 0xFF));
154 std::string dataStr = normalizedRaw.substr(colonPos + 1);
155 std::vector<uint8_t> frameData;
157 if (!dataStr.empty()) {
158 if (dataStr.length() % 2 != 0) {
160 context,
"Frame data must have even number of hex characters"));
163 for (
size_t i = 0; i < dataStr.length(); i += 2) {
164 std::string byteStr = dataStr.substr(i, 2);
165 unsigned int byte = 0;
170 frameData.push_back(
static_cast<uint8_t
>(
byte));
175 if (frameData.size() > 223) {
177 context,
"Frame length: " + std::to_string(frameData.size())));
181 uint32_t pgn = extractPgnFromCanId(canId);
182 if (!isValidPgn(pgn)) {
184 context,
"PGN out of valid range: 0x" + std::to_string(pgn)));
187 return std::unique_ptr<Message2000>(
188 new Message2000(std::move(raw), ts, std::move(canId), std::move(frameData)));
195uint32_t Message2000::extractPgnFromCanId(
const std::vector<uint8_t>& canId)
noexcept {
213 const uint8_t rdp = canId[0] & 0x03;
214 const uint8_t pf = canId[1];
215 const uint8_t ps = canId[2];
219 return (
static_cast<uint32_t
>(rdp) << 16) |
220 (
static_cast<uint32_t
>(pf) << 8);
223 return (
static_cast<uint32_t
>(rdp) << 16) |
224 (
static_cast<uint32_t
>(pf) << 8) |
225 static_cast<uint32_t
>(ps);
233bool Message2000::isValidPgn(uint32_t pgn)
noexcept {
235 return pgn <= 0x3FFFFU;
243 return std::unique_ptr<Message2000>(
new Message2000(*
this));
297 return extractPgnFromCanId(
getCanId());
321 std::ostringstream oss;
324 oss << toString(
true) <<
"\n";
326 oss << toString(
false) <<
"Data=";
327 for (
size_t i = 0; i < getCanFrame().size(); ++i) {
328 if (i > 0) oss <<
" ";
329 oss << std::hex << std::setfill(
'0') << std::setw(2)
330 <<
static_cast<int>(getCanFrame()[i]);
338 std::ostringstream oss;
341 const uint32_t pgn = getPgn();
342 const uint8_t pf = getPDUFormat();
344 oss <<
"--------------------------------\n";
345 oss <<
"Protocol: " << typeToString(type_) <<
"\n";
346 oss <<
"Priority: " <<
static_cast<int>(getPriority()) <<
"\n";
347 oss <<
"Data Page: " << (getDataPage() ?
"1" :
"0") <<
"\n";
348 oss <<
"PDU Format: 0x" << std::hex << std::setw(2) << std::setfill(
'0')
349 <<
static_cast<int>(pf) << std::dec
350 << (pf < 0xF0 ?
" (PDU1 - addressed)" :
" (PDU2 - broadcast)") <<
"\n";
351 oss <<
"Destination: ";
353 oss << static_cast<int>(getDestinationAddress());
355 oss <<
"255 (global)";
358 oss <<
"Source Addr: " <<
static_cast<int>(getSourceAddress()) <<
"\n";
359 oss <<
"PGN: " << pgn
360 <<
" (0x" << std::hex << pgn << std::dec <<
")\n";
361 oss <<
"Frame Len: " <<
static_cast<int>(getCanFrameLength()) <<
" bytes\n";
362 oss <<
"Frame Data: ";
363 for (
size_t i = 0; i < getCanFrame().size(); ++i) {
364 if (i > 0) oss <<
" ";
365 oss << std::hex << std::setfill(
'0') << std::setw(2)
366 <<
static_cast<int>(getCanFrame()[i]);
370 oss <<
"[OK] " << typeToString(type_) <<
" PGN" << getPgn() <<
": ";
381 std::ostringstream oss;
382 oss << std::hex << std::uppercase << std::setfill(
'0');
384 for (
const auto byte :
getCanId()) {
385 oss << std::setw(2) << static_cast<int>(
byte);
391 oss << std::setw(2) << static_cast<int>(
byte);
402 return getType() == other.getType() &&
403 getCanId() == other.getCanId() &&
404 getCanFrame() == other.getCanFrame();
408 if (!isValidPgn(
getPgn())) {
std::chrono::system_clock::time_point TimePoint
Exception thrown when the CAN frame is invalid.
Exception thrown when an NMEA 2000 PGN is outside the valid range.
Represents a generic NMEA 2000 message encapsulating a CAN frame.
uint8_t getSourceAddress() const noexcept
Returns the source address of the transmitting device.
Message2000(const Message2000 &)=default
std::vector< uint8_t > canId_
29-bit CAN Id stored as 4 bytes (big-endian)
const std::vector< uint8_t > & getCanFrame() const noexcept
Returns the raw CAN frame payload (0-223 bytes).
bool getPriority2() const noexcept
Bit 3 of canId_[0].
std::string serialize() const override
Serializes the message to "CANID:data" hex format.
std::vector< uint8_t > canFrame_
CAN frame payload (0-223 bytes)
uint32_t getPgn() const noexcept
Returns the PGN (Parameter Group Number) extracted from the CAN Id.
bool getPriority3() const noexcept
Returns individual priority bits (P3 is the MSB).
uint8_t getPDUSpecific() const noexcept
Returns the PDU Specific byte (PS).
bool validate() const override
Returns true if the PGN fits in 18 bits and the frame is 0-223 bytes.
bool getHeaderReserved() const noexcept
Returns the Reserved bit (R1).
bool getDataPage() const noexcept
Returns the Data Page bit (DP).
std::string toString(bool verbose) const noexcept
Returns the base string common to all PGNs (used by getStringContent).
std::unique_ptr< nmealib::Message > clone() const override
Creates a polymorphic deep copy of this Message2000.
uint8_t getPDUFormat() const noexcept
Returns the PDU Format byte (PF).
static std::unique_ptr< Message2000 > create(std::string raw, TimePoint ts=std::chrono::system_clock::now())
Protected factory — parses "CANID:data" (and variant formats) into a Message2000.
virtual std::string getStringContent(bool verbose) const noexcept
Returns a human-readable string representation of the message.
const std::vector< uint8_t > & getCanId() const noexcept
Returns the 29-bit CAN identifier as 4 bytes (big-endian).
uint8_t getCanFrameLength() const noexcept
Returns the number of bytes in the CAN frame payload.
virtual bool operator==(const Message2000 &other) const noexcept
bool getPriority1() const noexcept
Bit 2 of canId_[0].
uint8_t getDestinationAddress() const noexcept
Returns the destination address.
uint8_t getPriority() const noexcept
Returns the 3-bit message priority (0 = highest, 7 = lowest).
#define NMEALIB_RETURN_ERROR(exceptionExpr)
bool parseUnsigned(const std::string &text, T &value, int base=10) noexcept