AgIsoStack++
A control-function-focused implementation of the major ISOBUS and J1939 protocols
Loading...
Searching...
No Matches
nmea2000_fast_packet_protocol.cpp
Go to the documentation of this file.
1//================================================================================================
13//================================================================================================
14
16
20#include "isobus/utility/system_timing.hpp"
21
22#include <algorithm>
23
24namespace isobus
25{
27 std::unique_ptr<CANMessageData> data,
28 std::uint32_t parameterGroupNumber,
29 std::uint16_t totalMessageSize,
30 std::uint8_t sequenceNumber,
32 std::shared_ptr<ControlFunction> source,
33 std::shared_ptr<ControlFunction> destination,
34 TransmitCompleteCallback sessionCompleteCallback,
35 void *parentPointer) :
36 TransportProtocolSessionBase(direction, std::move(data), parameterGroupNumber, totalMessageSize, source, destination, sessionCompleteCallback, parentPointer),
37 sequenceNumber(sequenceNumber),
38 priority(priority)
39 {
40 }
41
43 {
44 // We know that this session can only be used to transfer 223 bytes of data, so we can safely cast to a uint8_t
45 return static_cast<std::uint8_t>(TransportProtocolSessionBase::get_message_length());
46 }
47
49 {
50 return (nullptr == get_destination());
51 }
52
54 {
55 return numberOfBytesTransferred;
56 }
57
59 {
60 // We know that this session can only be used to transfer 223 bytes of data, so we can safely cast to a uint8_t
61 std::uint8_t numberOfFrames = calculate_number_of_frames(static_cast<std::uint8_t>(get_total_bytes_transferred()));
62 if (numberOfFrames > 0)
63 {
64 return numberOfFrames;
65 }
66 else
67 {
68 return 0;
69 }
70 }
71
73 {
74 return get_total_number_of_packets() - get_last_packet_number();
75 }
76
81
83 {
84 numberOfBytesTransferred += bytes;
85 update_timestamp();
86 }
87
88 std::uint8_t FastPacketProtocol::calculate_number_of_frames(std::uint8_t messageLength)
89 {
90 std::uint8_t numberOfFrames = 0;
91 // Account for the 6 bytes of data in the first frame
92 if (messageLength > 6)
93 {
94 messageLength -= 6;
95 numberOfFrames++;
96 }
97 numberOfFrames += (messageLength / PROTOCOL_BYTES_PER_FRAME);
98 if (0 != (messageLength % PROTOCOL_BYTES_PER_FRAME))
99 {
100 numberOfFrames++;
101 }
102 return numberOfFrames;
103 }
104
109
110 void FastPacketProtocol::register_multipacket_message_callback(std::uint32_t parameterGroupNumber, CANLibCallback callback, void *parent, std::shared_ptr<InternalControlFunction> internalControlFunction)
111 {
112 parameterGroupNumberCallbacks.emplace_back(parameterGroupNumber, callback, parent, internalControlFunction);
113 }
114
115 void FastPacketProtocol::remove_multipacket_message_callback(std::uint32_t parameterGroupNumber, CANLibCallback callback, void *parent, std::shared_ptr<InternalControlFunction> internalControlFunction)
116 {
117 ParameterGroupNumberCallbackData tempObject(parameterGroupNumber, callback, parent, internalControlFunction);
118 auto callbackLocation = std::find(parameterGroupNumberCallbacks.begin(), parameterGroupNumberCallbacks.end(), tempObject);
119 if (parameterGroupNumberCallbacks.end() != callbackLocation)
120 {
121 parameterGroupNumberCallbacks.erase(callbackLocation);
122 }
123 }
124
129
130 bool FastPacketProtocol::send_multipacket_message(std::uint32_t parameterGroupNumber,
131 const std::uint8_t *messageData,
132 std::uint8_t messageLength,
133 std::shared_ptr<InternalControlFunction> source,
134 std::shared_ptr<ControlFunction> destination,
136 TransmitCompleteCallback txCompleteCallback,
137 void *parentPointer,
138 DataChunkCallback frameChunkCallback)
139 {
140 std::unique_ptr<CANMessageData> data;
141 if (nullptr != frameChunkCallback)
142 {
143 data.reset(new CANMessageDataCallback(messageLength, frameChunkCallback, parentPointer));
144 }
145 else if (nullptr != messageData)
146 {
147 // Make a copy of the data as it could go out of scope
148 data.reset(new CANMessageDataVector(messageData, messageLength));
149 }
150
151 // Return false early if we can't send the message
152 if ((nullptr == data) || (data->size() <= CAN_DATA_LENGTH) || (data->size() > MAX_PROTOCOL_MESSAGE_LENGTH))
153 {
154 LOG_ERROR("[FP]: Unable to send multipacket message, data is invalid or has invalid length.");
155 return false;
156 }
157 else if ((nullptr == source) || (!source->get_address_valid()) || has_session(parameterGroupNumber, source, destination))
158 {
159 LOG_ERROR("[FP]: Unable to send multipacket message, source is invalid or already in a session for the PGN.");
160 return false;
161 }
162 else if ((parameterGroupNumber < FP_MIN_PARAMETER_GROUP_NUMBER) || (parameterGroupNumber > FP_MAX_PARAMETER_GROUP_NUMBER))
163 {
164 LOG_ERROR("[FP]: Unable to send multipacket message, PGN is unsupported by this protocol.");
165 return false;
166 }
167 else if ((nullptr != destination) && (!destination->get_address_valid()))
168 {
169 LOG_ERROR("[FP]: Unable to send multipacket message, destination is invalid.");
170 return false;
171 }
172
173 std::uint8_t sequenceNumber = get_new_sequence_number(source->get_NAME(), parameterGroupNumber);
174 auto session = std::make_shared<FastPacketProtocolSession>(FastPacketProtocolSession::Direction::Transmit,
175 std::move(data),
176 parameterGroupNumber,
177 messageLength,
178 sequenceNumber,
179 priority,
180 source,
181 destination,
182 txCompleteCallback,
183 parentPointer);
184
185 LOCK_GUARD(Mutex, sessionMutex);
186 activeSessions.push_back(session);
187 return true;
188 }
189
191 {
192 LOCK_GUARD(Mutex, sessionMutex);
193 // We use a fancy for loop here to allow us to remove sessions from the list while iterating
194 for (std::size_t i = activeSessions.size(); i > 0; i--)
195 {
196 auto session = activeSessions.at(i - 1);
197 if (!session->get_source()->get_address_valid())
198 {
199 LOG_WARNING("[FP]: Closing active session as the source control function is no longer valid");
200 close_session(session, false);
201 }
202 else if (!session->is_broadcast() && !session->get_destination()->get_address_valid())
203 {
204 LOG_WARNING("[FP]: Closing active session as the destination control function is no longer valid");
205 close_session(session, false);
206 }
207 update_session(session);
208 }
209 }
210
211 void FastPacketProtocol::add_session_history(const std::shared_ptr<FastPacketProtocolSession> &session)
212 {
213 if (nullptr != session)
214 {
215 bool formerSessionMatched = false;
216
217 for (auto &formerSessions : sessionHistory)
218 {
219 if ((formerSessions.isoName == session->get_source()->get_NAME()) &&
220 (formerSessions.parameterGroupNumber == session->get_parameter_group_number()))
221 {
222 formerSessions.sequenceNumber = session->sequenceNumber;
223 formerSessionMatched = true;
224 break;
225 }
226 }
227
228 if (!formerSessionMatched)
229 {
230 FastPacketHistory history{
231 session->get_source()->get_NAME(),
232 session->get_parameter_group_number(),
233 session->sequenceNumber
234 };
235 sessionHistory.push_back(history);
236 }
237 }
238 }
239
240 void FastPacketProtocol::close_session(std::shared_ptr<FastPacketProtocolSession> session, bool successful)
241 {
242 if (nullptr != session)
243 {
244 session->complete(successful);
245 add_session_history(session);
246
247 auto sessionLocation = std::find(activeSessions.begin(), activeSessions.end(), session);
248 if (activeSessions.end() != sessionLocation)
249 {
250 activeSessions.erase(sessionLocation);
251 }
252 }
253 }
254
255 std::uint8_t FastPacketProtocol::get_new_sequence_number(NAME name, std::uint32_t parameterGroupNumber) const
256 {
257 std::uint8_t sequenceNumber = 0;
258 for (const auto &formerSessions : sessionHistory)
259 {
260 if ((formerSessions.isoName == name) && (formerSessions.parameterGroupNumber == parameterGroupNumber))
261 {
262 sequenceNumber = formerSessions.sequenceNumber + 1;
263 break;
264 }
265 }
266 return sequenceNumber;
267 }
268
270 {
271 if ((CAN_DATA_LENGTH != message.get_data_length()) ||
272 (message.get_source_control_function() == nullptr) ||
275 {
276 // Not a valid message for this protocol
277 return;
278 }
279
281 {
282 // No listeners, no need to process the message
283 return;
284 }
285
286 if ((!message.is_destination_our_device()) && (!allowAnyControlFunction) && (!message.is_broadcast()))
287 {
288 // Destined for someone else, no need to process the message
289 return;
290 }
291
292 // Check if we have any callbacks for this PGN
293 bool pgnNeedsParsing = false;
294 for (const auto &callback : parameterGroupNumberCallbacks)
295 {
296 if ((callback.get_parameter_group_number() == message.get_identifier().get_parameter_group_number()) &&
297 ((nullptr == callback.get_internal_control_function()) ||
298 (callback.get_internal_control_function() == message.get_destination_control_function())))
299 {
300 pgnNeedsParsing = true;
301 break;
302 }
303 }
304
305 if (!pgnNeedsParsing)
306 {
307 // No callbacks for this PGN, no need to process the message
308 return;
309 }
310
311 std::shared_ptr<FastPacketProtocolSession> session = get_session(message.get_identifier().get_parameter_group_number(),
314 std::uint8_t actualFrameCount = (message.get_uint8_at(0) & FRAME_COUNTER_BIT_MASK);
315
316 if (nullptr != session)
317 {
318 // We have a matching active session
319 if (0 == actualFrameCount)
320 {
321 // This is the beginning of a new message, but we already have a session
322 LOG_ERROR("[FP]: Existing session matched new frame counter, aborting the matching session.");
323 close_session(session, false);
324 }
325 else
326 {
327 // Correct sequence number, copy the data
328 // Convert data type to a vector to allow for manipulation
329 auto &data = static_cast<CANMessageDataVector &>(session->get_data());
330 for (std::uint8_t i = 0; i < PROTOCOL_BYTES_PER_FRAME; i++)
331 {
332 if (session->numberOfBytesTransferred < session->get_message_length())
333 {
334 data.set_byte(session->numberOfBytesTransferred, message.get_uint8_at(1 + i));
335 session->add_number_of_bytes_transferred(1);
336 }
337 else
338 {
339 // Reached the end of the message, no need to copy any more data
340 break;
341 }
342 }
343
344 if (session->numberOfBytesTransferred >= session->get_message_length())
345 {
346 // Complete
347 CANMessage completedMessage(CANMessage::Type::Receive,
348 message.get_identifier(),
349 std::move(data),
352 message.get_can_port_index());
353
354 // Find the appropriate callback and let them know
355 for (const auto &callback : parameterGroupNumberCallbacks)
356 {
357 if ((callback.get_parameter_group_number() == message.get_identifier().get_parameter_group_number()) &&
358 ((nullptr == callback.get_internal_control_function()) ||
359 (callback.get_internal_control_function() == message.get_destination_control_function())))
360 {
361 callback.get_callback()(completedMessage, callback.get_parent());
362 }
363 }
364 close_session(session, true);
365 }
366 }
367 }
368 else
369 {
370 // No matching session. See if we need to start a new session
371 if (0 != actualFrameCount)
372 {
373 // This is the middle of some message that we have no context for.
374 // Ignore the message for now until we receive it with a fresh packet counter.
375 LOG_WARNING("[FP]: Ignoring FP message with PGN %u, no context available. The message may be processed when packet count returns to zero.",
377 }
378 else
379 {
380 // This is the beginning of a new message
381 std::uint8_t messageLength = message.get_uint8_at(1);
382 if (messageLength > MAX_PROTOCOL_MESSAGE_LENGTH)
383 {
384 LOG_WARNING("[FP]: Ignoring possible new FP session with advertised length > 233.");
385 return;
386 }
387 else if (messageLength <= CAN_DATA_LENGTH)
388 {
389 LOG_WARNING("[FP]: Ignoring possible new FP session with advertised length <= 8.");
390 return;
391 }
392
393 // Create a new session
394 session = std::make_shared<FastPacketProtocolSession>(FastPacketProtocolSession::Direction::Receive,
395 std::unique_ptr<CANMessageData>(new CANMessageDataVector(messageLength)),
397 messageLength,
399 message.get_identifier().get_priority(),
402 nullptr, // No callback
403 nullptr);
404
405 // Save the 6 bytes of payload in this first message
406 // Convert data type to a vector to allow for manipulation
407 auto &data = static_cast<CANMessageDataVector &>(session->get_data());
408 for (std::uint8_t i = 0; i < (PROTOCOL_BYTES_PER_FRAME - 1); i++)
409 {
410 data.set_byte(session->numberOfBytesTransferred, message.get_uint8_at(2 + i));
411 session->add_number_of_bytes_transferred(1);
412 }
413
414 LOCK_GUARD(Mutex, sessionMutex);
415 activeSessions.push_back(session);
416 }
417 }
418 }
419
420 void FastPacketProtocol::update_session(const std::shared_ptr<FastPacketProtocolSession> &session)
421 {
422 if (nullptr == session)
423 {
424 return;
425 }
426
427 if (session->get_direction() == FastPacketProtocolSession::Direction::Receive)
428 {
429 // We are receiving a message, only need to check for timeouts
430 if (session->get_time_since_last_update() > FP_TIMEOUT_MS)
431 {
432 LOG_ERROR("[FP]: Rx session timed out.");
433 close_session(session, false);
434 }
435 }
436 else
437 {
438 std::array<std::uint8_t, CAN_DATA_LENGTH> buffer;
439 // We are transmitting a message, let's try and send remaining packets
440 for (std::uint8_t i = 0; i < session->get_number_of_remaining_packets(); i++)
441 {
442 buffer[0] = session->get_last_packet_number();
443 buffer[0] |= (session->sequenceNumber << SEQUENCE_NUMBER_BIT_OFFSET);
444
445 std::uint8_t startIndex = 1;
446 std::uint8_t bytesThisFrame = PROTOCOL_BYTES_PER_FRAME;
447 if (0 == session->get_total_bytes_transferred())
448 {
449 // This is the first frame, so we need to send the message length
450 buffer[1] = session->get_message_length();
451 startIndex++;
452 bytesThisFrame--;
453 }
454
455 for (std::uint8_t j = 0; j < bytesThisFrame; j++)
456 {
457 std::uint8_t index = static_cast<std::uint8_t>(session->get_total_bytes_transferred()) + j;
458 if (index < session->get_message_length())
459 {
460 buffer[startIndex + j] = session->get_data().get_byte(index);
461 }
462 else
463 {
464 buffer[startIndex + j] = 0xFF;
465 }
466 }
467
468 if (sendCANFrameCallback(session->get_parameter_group_number(),
469 CANDataSpan(buffer.data(), buffer.size()),
470 std::static_pointer_cast<InternalControlFunction>(session->get_source()),
471 session->get_destination(),
472 session->priority))
473 {
474 session->add_number_of_bytes_transferred(bytesThisFrame);
475 }
476 else
477 {
478 if (session->get_time_since_last_update() > FP_TIMEOUT_MS)
479 {
480 LOG_ERROR("[FP]: Tx session timed out.");
481 close_session(session, false);
482 }
483 break;
484 }
485 }
486
487 if (session->get_number_of_remaining_packets() == 0)
488 {
489 close_session(session, true);
490 }
491 }
492 }
493
494 bool FastPacketProtocol::has_session(std::uint32_t parameterGroupNumber, std::shared_ptr<ControlFunction> source, std::shared_ptr<ControlFunction> destination)
495 {
496 LOCK_GUARD(Mutex, sessionMutex);
497 return std::any_of(activeSessions.begin(), activeSessions.end(), [&](const std::shared_ptr<FastPacketProtocolSession> &session) {
498 return session->matches(source, destination) && (session->get_parameter_group_number() == parameterGroupNumber);
499 });
500 }
501
502 std::shared_ptr<FastPacketProtocol::FastPacketProtocolSession> FastPacketProtocol::get_session(std::uint32_t parameterGroupNumber,
503 std::shared_ptr<ControlFunction> source,
504 std::shared_ptr<ControlFunction> destination)
505 {
506 LOCK_GUARD(Mutex, sessionMutex);
507 auto result = std::find_if(activeSessions.begin(), activeSessions.end(), [&](const std::shared_ptr<FastPacketProtocolSession> &session) {
508 return session->matches(source, destination) && (session->get_parameter_group_number() == parameterGroupNumber);
509 });
510 return (activeSessions.end() != result) ? (*result) : nullptr;
511 }
512
513} // namespace isobus
General constants used throughout this library.
The main class that manages the ISOBUS stack including: callbacks, Name to Address management,...
A class that acts as a logging sink. The intent is that someone could make their own derived class of...
CANPriority
Defines all the CAN frame priorities that can be encoded in a frame ID.
CANPriority get_priority() const
Returns the priority of the frame encoded in the identifier.
std::uint32_t get_parameter_group_number() const
Returns the PGN encoded in the identifier.
A class that represents data of a CAN message by using a callback function.
A class that represents data of a CAN message by holding a vector of bytes.
void set_byte(std::size_t index, std::uint8_t value)
Set the byte at the given index.
A class that represents a generic CAN message of arbitrary length.
@ Receive
Message is being received.
std::uint8_t get_can_port_index() const
Returns the CAN channel index associated with the message.
std::uint32_t get_data_length() const
Returns the length of the data in the CAN message.
std::shared_ptr< ControlFunction > get_source_control_function() const
Gets the source control function that the message is from.
std::uint8_t get_uint8_at(const std::uint32_t index) const
Get a 8-bit unsigned byte from the buffer at a specific index. A 8-bit unsigned byte can hold a value...
CANIdentifier get_identifier() const
Returns the identifier of the message.
std::shared_ptr< ControlFunction > get_destination_control_function() const
Gets the destination control function that the message is to.
bool is_broadcast() const
Returns whether the message is sent as a broadcast message / to all devices on the bus.
bool is_destination_our_device() const
Returns whether the message is destined for our device on the bus.
std::uint8_t get_message_length() const
Get the total number of bytes that will be sent or received in this session.
std::uint8_t get_total_number_of_packets() const
Get the total number of packets that will be sent or received in this session.
bool is_broadcast() const
Get whether or not this session is a broadcast session (BAM)
void add_number_of_bytes_transferred(std::uint8_t bytes)
Add number of bytes to the total number of bytes that have been sent or received in this session.
FastPacketProtocolSession(TransportProtocolSessionBase::Direction direction, std::unique_ptr< CANMessageData > data, std::uint32_t parameterGroupNumber, std::uint16_t totalMessageSize, std::uint8_t sequenceNumber, CANIdentifier::CANPriority priority, std::shared_ptr< ControlFunction > source, std::shared_ptr< ControlFunction > destination, TransmitCompleteCallback sessionCompleteCallback, void *parentPointer)
The constructor for a session, for advanced use only. In most cases, you should use the CANNetworkMan...
std::uint8_t get_number_of_remaining_packets() const
Get the number of packets that remain to be sent or received in this session.
std::uint32_t get_total_bytes_transferred() const override
Get the number of bytes that have been sent or received in this session.
std::uint8_t get_last_packet_number() const
Get the last packet number that was sent or received in this session.
static constexpr std::uint32_t FP_MIN_PARAMETER_GROUP_NUMBER
Start of PGNs that can be received via Fast Packet.
void add_session_history(const std::shared_ptr< FastPacketProtocolSession > &session)
Adds a session's info to the history so that we can continue the sequence number later.
bool allowAnyControlFunction
Denotes if messages for non-internal control functions should be parsed by this protocol.
static constexpr std::uint8_t FRAME_COUNTER_BIT_MASK
Bit mask for masking out the frame counter.
std::vector< std::shared_ptr< FastPacketProtocolSession > > activeSessions
A list of all active TP sessions.
static constexpr std::uint8_t SEQUENCE_NUMBER_BIT_MASK
Bit mask for masking out the sequence number bits.
static std::uint8_t calculate_number_of_frames(std::uint8_t messageLength)
Calculates the number of frames needed for a message.
void close_session(std::shared_ptr< FastPacketProtocolSession > session, bool successful)
Gracefully closes a session to prepare for a new session.
std::uint8_t get_new_sequence_number(NAME name, std::uint32_t parameterGroupNumber) const
Get the sequence number to use for a new session based on the history of past sessions.
void remove_multipacket_message_callback(std::uint32_t parameterGroupNumber, CANLibCallback callback, void *parent, std::shared_ptr< InternalControlFunction > internalControlFunction=nullptr)
const CANMessageFrameCallback sendCANFrameCallback
A callback for sending a CAN frame.
Mutex sessionMutex
A mutex to lock the sessions list in case someone starts a Tx while the stack is processing sessions.
static constexpr std::uint32_t FP_TIMEOUT_MS
Protocol timeout in milliseconds.
static constexpr std::uint8_t SEQUENCE_NUMBER_BIT_OFFSET
The bit offset into the first byte of data to get the seq number.
std::vector< ParameterGroupNumberCallbackData > parameterGroupNumberCallbacks
A list of all parameter group number callbacks that will be parsed as fast packet messages.
FastPacketProtocol(const CANMessageFrameCallback &sendCANFrameCallback)
The constructor for the FastPacketProtocol, for advanced use only. In most cases, you should use the ...
void allow_any_control_function(bool allow)
Set whether or not to allow messages for non-internal control functions to be parsed by this protocol...
std::vector< FastPacketHistory > sessionHistory
Used to keep track of sequence numbers for future sessions.
void update()
Updates all sessions managed by this protocol manager instance.
std::shared_ptr< FastPacketProtocolSession > get_session(std::uint32_t parameterGroupNumber, std::shared_ptr< ControlFunction > source, std::shared_ptr< ControlFunction > destination)
Gets a FP session from the passed in source and destination and PGN combination.
static constexpr std::uint8_t PROTOCOL_BYTES_PER_FRAME
The number of payload bytes per frame for all but the first message, which has 6.
static constexpr std::uint8_t MAX_PROTOCOL_MESSAGE_LENGTH
Max message length based on there being 5 bits of sequence data.
void process_message(const CANMessage &message)
A generic way for a protocol to process a received message.
void register_multipacket_message_callback(std::uint32_t parameterGroupNumber, CANLibCallback callback, void *parent, std::shared_ptr< InternalControlFunction > internalControlFunction=nullptr)
Add a callback to be called when a message is received by the Fast Packet protocol.
void update_session(const std::shared_ptr< FastPacketProtocolSession > &session)
Update a single session.
static constexpr std::uint32_t FP_MAX_PARAMETER_GROUP_NUMBER
End of PGNs that can be received via Fast Packet.
bool has_session(std::uint32_t parameterGroupNumber, std::shared_ptr< ControlFunction > source, std::shared_ptr< ControlFunction > destination)
Checks if a session by the passed in source and destination and PGN combination exists.
bool send_multipacket_message(std::uint32_t parameterGroupNumber, const std::uint8_t *data, std::uint8_t messageLength, std::shared_ptr< InternalControlFunction > source, std::shared_ptr< ControlFunction > destination, CANIdentifier::CANPriority priority=CANIdentifier::CANPriority::PriorityDefault6, TransmitCompleteCallback txCompleteCallback=nullptr, void *parentPointer=nullptr, DataChunkCallback frameChunkCallback=nullptr)
Used to send CAN messages using fast packet.
A class that represents an ISO11783 control function NAME from an address claim.
Definition can_NAME.hpp:24
A storage class to hold data about callbacks for a specific PGN.
An object to keep track of session information internally.
std::uint32_t get_message_length() const
Get the total number of bytes that will be sent or received in this session.
Direction
Enumerates the possible session directions.
@ Transmit
We are transmitting a message.
This namespace encompasses all of the ISO11783 stack's functionality to reduce global namespace pollu...
DataSpan< const std::uint8_t > CANDataSpan
A read-only span of data for a CAN message.
std::function< bool(std::uint32_t parameterGroupNumber, CANDataSpan data, std::shared_ptr< InternalControlFunction > sourceControlFunction, std::shared_ptr< ControlFunction > destinationControlFunction, CANIdentifier::CANPriority priority)> CANMessageFrameCallback
A callback for communicating CAN message frames.
void(*)(std::uint32_t parameterGroupNumber, std::uint32_t dataLength, std::shared_ptr< InternalControlFunction > sourceControlFunction, std::shared_ptr< ControlFunction > destinationControlFunction, bool successful, void *parentPointer) TransmitCompleteCallback
A callback for when a transmit is completed by the stack.
constexpr std::uint8_t CAN_DATA_LENGTH
The length of a classical CAN frame.
void(*)(const CANMessage &message, void *parentPointer) CANLibCallback
A callback for control functions to get CAN messages.
bool(*)(std::uint32_t callbackIndex, std::uint32_t bytesOffset, std::uint32_t numberOfBytesNeeded, std::uint8_t *chunkBuffer, void *parentPointer) DataChunkCallback
A callback to get chunks of data for transfer by a protocol.
A protocol that handles the NMEA 2000 (IEC 61162-3) fast packet protocol.
A structure for keeping track of past sessions so we can resume with the right session number.