• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

fliuzzi02 / nmealib / 23093480989

14 Mar 2026 06:10PM UTC coverage: 94.299% (+1.7%) from 92.6%
23093480989

push

github

web-flow
PlatformIO Integration (#72)

* [UPDATE] Replace GPL with MIT License in LICENSE file

* [REFINE] Remove hasEqualContent method from NMEA 0183 message classes and update related tests

* [UPDATE] Enhance README with PlatformIO integration details and supported protocols

* Refactor NMEA message creation to improve error handling

- Updated MTW, MWV, RMC, VHW, VTG, VWR, ZDA, and NMEA2000 message creation methods to use NMEALIB_RETURN_ERROR for exception handling.
- Enhanced parsing logic for optional fields using detail::parseOptionalDouble and similar functions to streamline error checking.
- Improved readability and maintainability by removing try-catch blocks in favor of direct error handling.
- Added checks for base message validity in Nmea0183Factory and Nmea2000Factory to ensure robust message creation.

* [FIX] Data type for cross platform use

* [UPDATE] Implement version update script for automated release process

* [UPDATE] Update nmealib version in installation instructions for PlatformIO integration

* [REFINE] Remove unnecessary tolerance calculation in fromValue method

* [UPDATE] Adjust default parallel build level and enhance compiler warning settings

* [ADD] Integrate PlatformIO configuration and example usage for NMEA library

* [UPDATE] Refactor static analysis problems

268 of 305 new or added lines in 21 files covered. (87.87%)

1 existing line in 1 file now uncovered.

1836 of 1947 relevant lines covered (94.3%)

25.77 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

94.5
/src/nmea2000.cpp
1
#include "nmealib/nmea2000.h"
2

3
#include "nmealib/detail/errorSupport.h"
4
#include "nmealib/detail/parse.h"
5

6
#include <cctype>
7
#include <iomanip>
8
#include <sstream>
9
#include <vector>
10
#include <algorithm>
11
#include <cstring>
12

13
namespace nmealib {
14
namespace nmea2000 {
15

16
/**
17
 * @brief Normalizes raw CAN frame string to the canonical "CANID:data" format.
18
 * 
19
 * Supports multiple input formats:
20
 * - "CANID:data" (already canonical, returned as-is)
21
 * - "CANID#data" (alternative separator)
22
 * - "0xCANID, 0xBB 0xCC 0xDD ..." (comma-separated with 0x prefix)
23
 * - "0xCANID 0xBB 0xCC 0xDD ..." (space-separated with 0x prefix)
24
 * - "CANID BB CC DD ..." (space-separated without prefix)
25
 * 
26
 * @param raw The raw CAN frame string in any supported format.
27
 * @return std::string The normalized string in "CANID:data" format.
28
 */
29
static std::string normalizeRawFormat(const std::string& raw) {
85✔
30
    // If already in CANID:data format, use as-is
31
    if (raw.find(':') != std::string::npos) {
85✔
32
        return raw;
33
    }
34

35
    // Check for alternative separator (e.g., "CANID#data")
36
    if (raw.find('#') != std::string::npos) {
7✔
37
        std::string normalized = raw;
38
        std::replace(normalized.begin(), normalized.end(), '#', ':');
39
        return normalized;
×
40
    }
41
    
42
    // Check for comma-separated format (e.g., "0x1CFF63CC, 0x3B 0x9F ...")
43
    size_t commaPos = raw.find(',');
7✔
44
    if (commaPos != std::string::npos) {
7✔
45
        std::string canIdPart = raw.substr(0, commaPos);
2✔
46
        std::string dataPart = raw.substr(commaPos + 1);
2✔
47
        
48
        // Clean up CAN ID: remove whitespace and 0x prefix
49
        canIdPart.erase(std::remove_if(canIdPart.begin(), canIdPart.end(), 
2✔
50
                                       [](unsigned char c) { return std::isspace(c); }), 
8✔
51
                       canIdPart.end());
52
        if (canIdPart.size() >= 2 && (canIdPart.substr(0, 2) == "0x" || canIdPart.substr(0, 2) == "0X")) {
4✔
53
            canIdPart = canIdPart.substr(2);
4✔
54
        }
55
        
56
        // Process data bytes: extract hex values, remove 0x prefix
57
        std::string dataHex;
58
        std::istringstream iss(dataPart);
2✔
59
        std::string token;
60
        while (iss >> token) {
18✔
61
            if (token.size() >= 2 && (token.substr(0, 2) == "0x" || token.substr(0, 2) == "0X")) {
32✔
62
                token = token.substr(2);
32✔
63
            }
64
            dataHex += token;
65
        }
66
        
67
        return canIdPart + ":" + dataHex;
4✔
68
    }
2✔
69
    
70
    // Check for space-separated format (e.g., "0x1CFF63CC 0x3B 0x9F ..." or "1CFF63CC 3B 9F ...")
71
    if (raw.find(' ') != std::string::npos) {
5✔
72
        std::istringstream iss(raw);
4✔
73
        std::string token;
74
        std::string canId;
75
        std::string dataHex;
76
        bool firstToken = true;
77
        
78
        while (iss >> token) {
40✔
79
            // Remove 0x prefix if present
80
            if (token.size() >= 2 && (token.substr(0, 2) == "0x" || token.substr(0, 2) == "0X")) {
90✔
81
                token = token.substr(2);
36✔
82
            }
83
            
84
            if (firstToken) {
36✔
85
                canId = token;
86
                firstToken = false;
87
            } else {
88
                dataHex += token;
89
            }
90
        }
91
        
92
        return canId + ":" + dataHex;
8✔
93
    }
4✔
94
    
95
    // If no special formatting detected, return as-is and let create() handle validation
96
    return raw;
97
}
98

99
Message2000::Message2000(std::string raw,
80✔
100
                         TimePoint ts,
101
                         uint32_t pgn,
102
                         std::vector<uint8_t> canFrame) noexcept
80✔
103
    : Message(std::move(raw), Type::NMEA2000, ts),
104
      pgn_(pgn),
80✔
105
      canFrame_(std::move(canFrame)) {
80✔
106
}
80✔
107

108
std::unique_ptr<Message2000> Message2000::create(std::string raw, TimePoint ts) {
85✔
109
    std::string context = "Message2000::create";
85✔
110
    
111
    // Normalize raw format to canonical "CANID:data" format
112
    std::string normalizedRaw = normalizeRawFormat(raw);
85✔
113
    
114
    // Parse raw string in format "CANID:data"
115
    size_t colonPos = normalizedRaw.find(':');
85✔
116
    if (colonPos == std::string::npos) {
85✔
117
        NMEALIB_RETURN_ERROR(InvalidCanFrameException(context, "This formatting is not supported"));
2✔
118
    }
119

120
    // Extract and parse CAN ID
121
    std::string canIdStr = normalizedRaw.substr(0, colonPos);
84✔
122
    uint32_t canId = 0;
84✔
123
    if (!detail::parseUnsigned(canIdStr, canId, 16)) {
84✔
124
        NMEALIB_RETURN_ERROR(InvalidCanFrameException(context, "Invalid CAN ID format: " + canIdStr));
2✔
125
    }
126

127
    // Extract and parse frame data
128
    std::string dataStr = normalizedRaw.substr(colonPos + 1);
83✔
129
    std::vector<uint8_t> frameData;
83✔
130
    
131
    if (!dataStr.empty()) {
83✔
132
        if (dataStr.length() % 2 != 0) {
82✔
133
            NMEALIB_RETURN_ERROR(InvalidCanFrameException(context,
4✔
134
                                                          "Frame data must have even number of hex characters"));
135
        }
136

137
        for (size_t i = 0; i < dataStr.length(); i += 2) {
1,135✔
138
            std::string byteStr = dataStr.substr(i, 2);
1,055✔
139
            unsigned int byte = 0;
1,055✔
140
            if (!detail::parseUnsigned(byteStr, byte, 16) || byte > 0xFFU) {
1,055✔
NEW
141
                NMEALIB_RETURN_ERROR(InvalidCanFrameException(context, "Invalid frame data hex format"));
×
142
            }
143
            frameData.push_back(static_cast<uint8_t>(byte));
1,055✔
144
        }
145
    }
146

147
    // Validate frame length (supports single-frame and fast-packet up to 223 bytes)
148
    if (frameData.size() > 223) {
81✔
149
        NMEALIB_RETURN_ERROR(FrameTooLongException(context, "Frame length: " + std::to_string(frameData.size())));
3✔
150
    }
151

152
    // Extract and validate PGN from CAN ID
153
    uint32_t pgn = extractPgnFromCanId(canId);
80✔
154
    if (!isValidPgn(pgn)) {
155
        NMEALIB_RETURN_ERROR(InvalidPgnException(context, "PGN out of valid range: 0x" +
156
                                                 std::to_string(pgn)));
157
    }
158

159
    return std::unique_ptr<Message2000>(
160
        new Message2000(std::move(raw), ts, pgn, frameData)
160✔
161
    );
80✔
162
}
83✔
163

164
uint32_t Message2000::extractPgnFromCanId(uint32_t canId) noexcept {
×
165
    // In NMEA2000, the CAN ID structure is 29-bit extended format:
166
    // Priority (3 bits) | Reserved (1 bit) | PGN (18 bits) | Source Address (8 bits)
167
    // The PGN is in bits 8-25 (counting from bit 0)
168
    return (canId >> 8) & 0x3FFFF;  // Mask 18 bits for PGN
80✔
169
}
170

171
bool Message2000::isValidPgn(uint32_t pgn) noexcept {
×
172
    // Accept any PGN that fits within 18 bits (0x000000-0x3FFFF)
173
    // Both single-frame and fast-packet messages use this range
174
    return pgn <= 0x3FFFF;
×
175
}
176

177
std::unique_ptr<nmealib::Message> Message2000::clone() const {
1✔
178
    return std::unique_ptr<Message2000>(new Message2000(*this));
1✔
179
}
180

181
uint32_t Message2000::getPgn() const noexcept {
67✔
182
    return pgn_;
67✔
183
}
184

185
const std::vector<uint8_t>& Message2000::getCanFrame() const noexcept {
201✔
186
    return canFrame_;
201✔
187
}
188

189
uint8_t Message2000::getCanFrameLength() const noexcept {
39✔
190
    return static_cast<uint8_t>(canFrame_.size());
53✔
191
}
192

193
std::string Message2000::getStringContent(bool verbose) const noexcept {
2✔
194
    std::ostringstream oss;
2✔
195

196
    if (verbose) {
2✔
197
        oss << this->toString(true);
1✔
198
        oss << "\n";
1✔
199
    } else {
200
        oss << this->toString(false);
1✔
201
        oss << "Data=";
1✔
202
        for (size_t i = 0; i < canFrame_.size(); ++i) {
9✔
203
            if (i > 0) oss << " ";
8✔
204
            oss << std::hex << std::setfill('0') << std::setw(2) << static_cast<int>(canFrame_[i]);
8✔
205
        }
206
        oss << std::dec;
207
    }
208
    return oss.str();
2✔
209
}
2✔
210

211
std::string Message2000::toString(bool verbose) const noexcept {
21✔
212
    std::ostringstream oss;
21✔
213

214
    if (verbose) {
21✔
215
        oss << "--------------------------------\n";
14✔
216
        oss << "Protocol: " << typeToString(type_) << "\n";
42✔
217
        oss << "PGN: " << pgn_ << "(0x" << std::hex << pgn_ << std::dec << ")\n";
42✔
218
        oss << "Frame Length: " << static_cast<int>(getCanFrameLength()) << " bytes\n";
14✔
219
        oss << "Frame Data: ";
14✔
220
        for (size_t i = 0; i < canFrame_.size(); ++i) {
126✔
221
            if (i > 0) oss << " ";
112✔
222
            oss << std::hex << std::setfill('0') << std::setw(2) << static_cast<int>(canFrame_[i]);
112✔
223
        }
224
        oss << std::dec;
225
    } else {
226
        oss << "[OK] " << typeToString(type_)  << " PGN" << pgn_ << ": ";
21✔
227
        oss << std::dec;
228
    }
229

230
    return oss.str();
21✔
231
}
21✔
232

233
std::string Message2000::serialize() const {
1✔
234
    std::ostringstream oss;
1✔
235
    oss << std::hex << std::setfill('0');
236

237
    // Output PGN
238
    oss << std::setw(5) << pgn_ << ":";
1✔
239

240
    // Output frame data as hex bytes
241
    for (size_t i = 0; i < canFrame_.size(); ++i) {
9✔
242
        oss << std::setw(2) << static_cast<int>(canFrame_[i]);
8✔
243
    }
244

245
    return oss.str();
1✔
246
}
1✔
247

248
bool Message2000::operator==(const Message2000& other) const noexcept {
7✔
249
    return Message::operator==(other) && pgn_ == other.pgn_ && canFrame_ == other.canFrame_;
7✔
250
}
251

252
bool Message2000::validate() const {
9✔
253
    // Validate PGN is valid (fits in 18 bits)
254
    if (!isValidPgn(pgn_)) {
9✔
255
        return false;
256
    }
257

258
    // Validate frame length (CAN single/fast-packet is 0-223 bytes)
259
    if (canFrame_.size() > 223) {
9✔
260
        return false;
×
261
    }
262

263
    return true;
264
}
265

266
} // namespace nmea2000
267
} // namespace nmealib
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc