mirror of
https://github.com/eclipse-openvehicle-api/openvehicle-api.git
synced 2026-07-02 05:35:11 +00:00
375
global/ascformat/ascreader.cpp
Normal file
375
global/ascformat/ascreader.cpp
Normal file
@@ -0,0 +1,375 @@
|
||||
#include "ascreader.h"
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
|
||||
#ifdef _WIN32
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
#include <WinSock2.h>
|
||||
#include <Windows.h>
|
||||
#include <processthreadsapi.h>
|
||||
#elif defined __unix__
|
||||
#include <pthread.h>
|
||||
#endif
|
||||
|
||||
namespace asc
|
||||
{
|
||||
CAscReader::CAscReader() : m_itCurrent(m_lstMessages.begin())
|
||||
{}
|
||||
|
||||
CAscReader::~CAscReader()
|
||||
{
|
||||
StopPlayback();
|
||||
}
|
||||
|
||||
bool CAscReader::Read(const std::filesystem::path& rpathFile)
|
||||
{
|
||||
// Clear current messages
|
||||
m_lstMessages.clear();
|
||||
JumpBegin();
|
||||
|
||||
// Open and read the file
|
||||
std::ifstream fstream(rpathFile);
|
||||
if (!fstream.is_open()) return false;
|
||||
|
||||
// Read the file line by line
|
||||
std::string ssLine;
|
||||
enum class EState {header, body, footer} eState = EState::header;
|
||||
while (std::getline(fstream, ssLine))
|
||||
{
|
||||
switch (eState)
|
||||
{
|
||||
case EState::header:
|
||||
if (ssLine.compare(0, 18, "Begin Triggerblock") == 0)
|
||||
eState = EState::body;
|
||||
break;
|
||||
case EState::body:
|
||||
if (ssLine.compare(0, 16, "End TriggerBlock") == 0)
|
||||
eState = EState::footer;
|
||||
else
|
||||
ProcessSample(ssLine);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
fstream.close();
|
||||
|
||||
// Set the current iterator
|
||||
JumpBegin();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::pair<SCanMessage, bool> CAscReader::Get() const
|
||||
{
|
||||
if (m_itCurrent == m_lstMessages.end())
|
||||
return std::make_pair(SCanMessage(), false);
|
||||
else
|
||||
return std::make_pair(*m_itCurrent, true);
|
||||
}
|
||||
|
||||
uint32_t CAscReader::GetLoopCount() const
|
||||
{
|
||||
return m_uiLoopCount;
|
||||
}
|
||||
|
||||
void CAscReader::JumpBegin()
|
||||
{
|
||||
m_itCurrent = m_lstMessages.begin();
|
||||
}
|
||||
|
||||
void CAscReader::JumpEnd()
|
||||
{
|
||||
m_itCurrent = m_lstMessages.end();
|
||||
}
|
||||
|
||||
CAscReader& CAscReader::operator++()
|
||||
{
|
||||
if (m_itCurrent != m_lstMessages.end())
|
||||
++m_itCurrent;
|
||||
return *this;
|
||||
}
|
||||
|
||||
CAscReader& CAscReader::operator++(int)
|
||||
{
|
||||
if (m_itCurrent != m_lstMessages.end())
|
||||
++m_itCurrent;
|
||||
return *this;
|
||||
}
|
||||
|
||||
CAscReader& CAscReader::operator--()
|
||||
{
|
||||
if (m_itCurrent != m_lstMessages.begin())
|
||||
--m_itCurrent;
|
||||
return *this;
|
||||
}
|
||||
|
||||
CAscReader& CAscReader::operator--(int)
|
||||
{
|
||||
if (m_itCurrent != m_lstMessages.begin())
|
||||
--m_itCurrent;
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool CAscReader::IsBOF() const
|
||||
{
|
||||
return m_itCurrent == m_lstMessages.begin();
|
||||
}
|
||||
|
||||
bool CAscReader::IsEOF() const
|
||||
{
|
||||
return m_itCurrent == m_lstMessages.end();
|
||||
}
|
||||
|
||||
void CAscReader::StartPlayback(std::function<void(const SCanMessage&)> fnCallback, bool bRepeat /*= true*/)
|
||||
{
|
||||
StopPlayback();
|
||||
|
||||
if (!fnCallback) return;
|
||||
if (m_lstMessages.empty()) return;
|
||||
|
||||
m_bPlaybackThread = false;
|
||||
m_bPlayback = true;
|
||||
m_threadPlayback = std::thread(&CAscReader::PlaybackThreadFunc, this, fnCallback, std::ref(m_uiLoopCount), bRepeat);
|
||||
while (!m_bPlaybackThread) std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
}
|
||||
|
||||
void CAscReader::StopPlayback()
|
||||
{
|
||||
m_bPlayback = false;
|
||||
if (m_threadPlayback.joinable()) m_threadPlayback.join();
|
||||
}
|
||||
|
||||
void CAscReader::ResetPlayback()
|
||||
{
|
||||
StopPlayback();
|
||||
JumpBegin();
|
||||
}
|
||||
|
||||
bool CAscReader::PlaybackRunning() const
|
||||
{
|
||||
return m_bPlayback;
|
||||
}
|
||||
|
||||
void CAscReader::ProcessSample(const std::string& rssSample)
|
||||
{
|
||||
std::istringstream sstream(rssSample);
|
||||
|
||||
SCanMessage sMsg{};
|
||||
|
||||
// Expecting a time
|
||||
if (!(sstream >> sMsg.dTimestamp))
|
||||
return; // All usable mesaurements must start with a timestamp
|
||||
|
||||
// Skip whitespace
|
||||
while (std::isspace(sstream.peek())) sstream.get();
|
||||
|
||||
// If the next timestamp is followed by the "CANFD" keyword, the sample is a CAN-FD sample
|
||||
if (std::isalpha(sstream.peek()))
|
||||
{
|
||||
std::string ssKeyword;
|
||||
if (!(sstream >> ssKeyword))
|
||||
return; // Unexpected
|
||||
if (ssKeyword != "CANFD")
|
||||
return; // Not a CAN-FD sample
|
||||
sMsg.bCanFd = true;
|
||||
|
||||
// Expecting a channel
|
||||
if (!(sstream >> sMsg.uiChannel))
|
||||
return; // Expected a channel (otherwise the measurement might contain meta data)
|
||||
|
||||
// Determine the direction
|
||||
std::string ssDirection;
|
||||
if (!(sstream >> ssDirection))
|
||||
return; // Expected a direction
|
||||
if (ssDirection == "Rx")
|
||||
sMsg.eDirection = SCanMessage::EDirection::rx;
|
||||
else if (ssDirection == "Tx")
|
||||
sMsg.eDirection = SCanMessage::EDirection::tx;
|
||||
else
|
||||
return; // Invalid direction
|
||||
|
||||
// Expecting a message ID in hex format
|
||||
if (!(sstream >> std::hex >> sMsg.uiId))
|
||||
return; // Expected an ID (otherwise the measurement might contain meta data)
|
||||
|
||||
// Skip whitespace
|
||||
while (std::isspace(sstream.peek())) sstream.get();
|
||||
|
||||
// Optional 'x' for an extended ID
|
||||
if (std::tolower(sstream.peek()) == 'x')
|
||||
{
|
||||
sMsg.bExtended = true;
|
||||
sstream.get(); // Skip the character, since already processed
|
||||
}
|
||||
|
||||
// Get bit rate switch (BRS)
|
||||
size_t nBRS = 0;
|
||||
if (!(sstream >> nBRS) || nBRS > 1)
|
||||
return; // Unexpected or invalid BRS
|
||||
|
||||
// Get error state indicator (ESI)
|
||||
size_t nESI = 0;
|
||||
if (!(sstream >> nESI) || nESI > 1)
|
||||
return; // Unexpected or invalid ESI
|
||||
|
||||
// Get data length code (DLC)
|
||||
size_t nDLC;
|
||||
if (!(sstream >> nDLC) || nDLC > 15)
|
||||
return; // Unexpected or invalid DLC
|
||||
|
||||
// Get the data length
|
||||
if (!(sstream >> std::dec >> sMsg.uiLength) || sMsg.uiLength > 64)
|
||||
return; // Length expected or invalid length
|
||||
|
||||
// Check for proper DLC vs. length
|
||||
const size_t rgnDLCLength[16] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 12, 16, 20, 24, 32, 48, 64 };
|
||||
if (rgnDLCLength[nDLC] != sMsg.uiLength) return; // Invalid length
|
||||
}
|
||||
else // standard CAN
|
||||
{
|
||||
// Expecting a channel
|
||||
if (!(sstream >> sMsg.uiChannel))
|
||||
return; // Expected a channel (otherwise the measurement might contain meta data)
|
||||
|
||||
// Expecting a message ID in hex format
|
||||
if (!(sstream >> std::hex >> sMsg.uiId))
|
||||
return; // Expected an ID (otherwise the measurement might contain meta data)
|
||||
|
||||
// Skip whitespace
|
||||
while (std::isspace(sstream.peek())) sstream.get();
|
||||
|
||||
// Optional 'x' for an extended ID
|
||||
if (std::tolower(sstream.peek()) == 'x')
|
||||
{
|
||||
sMsg.bExtended = true;
|
||||
sstream.get(); // Skip the character, since already processed
|
||||
}
|
||||
|
||||
// Determine the direction
|
||||
std::string ssDirection;
|
||||
if (!(sstream >> ssDirection))
|
||||
return; // Expected a direction
|
||||
if (ssDirection == "Rx")
|
||||
sMsg.eDirection = SCanMessage::EDirection::rx;
|
||||
else if (ssDirection == "Tx")
|
||||
sMsg.eDirection = SCanMessage::EDirection::tx;
|
||||
else
|
||||
return; // Invalid direction
|
||||
|
||||
// Determine whether the measurement contains data from a data frame (remote frames are not processed).
|
||||
char cLocation = '\0';
|
||||
if (!(sstream >> cLocation) || std::tolower(cLocation) != 'd')
|
||||
return; // Not supported location
|
||||
|
||||
// Get the data length
|
||||
if (!(sstream >> std::dec >> sMsg.uiLength) || sMsg.uiLength > 8)
|
||||
return; // Length expected or invalid length
|
||||
}
|
||||
|
||||
// Read the data
|
||||
for (uint32_t uiIndex = 0; uiIndex < sMsg.uiLength; uiIndex++)
|
||||
{
|
||||
uint32_t uiVal = 0;
|
||||
if (!(sstream >> std::hex >> uiVal))
|
||||
return; // Expected data byte
|
||||
sMsg.rguiData[uiIndex] = static_cast<uint8_t>(uiVal);
|
||||
}
|
||||
|
||||
// Add the message to the vector
|
||||
m_lstMessages.push_back(std::move(sMsg));
|
||||
|
||||
// Ignore the rest of the line (additional information could have been logged).
|
||||
}
|
||||
|
||||
void CAscReader::PlaybackThreadFunc(std::function<void(const SCanMessage&)> fnCallback, std::atomic<uint32_t>& loopCount, bool bRepeat)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL);
|
||||
#elif defined __unix__
|
||||
pthread_attr_t thAttr;
|
||||
pthread_attr_init(&thAttr);
|
||||
int iPolicy = 0;
|
||||
pthread_attr_getschedpolicy(&thAttr, &iPolicy);
|
||||
int iMaxPrioForPolicy = sched_get_priority_max(iPolicy);
|
||||
pthread_setschedprio(pthread_self(), iMaxPrioForPolicy);
|
||||
pthread_attr_destroy(&thAttr);
|
||||
#endif
|
||||
|
||||
// Indicate that playback thread has started.
|
||||
m_bPlaybackThread = true;
|
||||
// when data set is repeated, we need the offset compared to the first run
|
||||
double dLoopOffset = 0.00000;
|
||||
|
||||
// Define the offset
|
||||
double dOffset = 0.00000;
|
||||
if (!IsBOF())
|
||||
{
|
||||
operator--();
|
||||
auto prSample = Get();
|
||||
if (!prSample.second)
|
||||
{
|
||||
m_bPlayback = false;
|
||||
std::cout << "ASC PLAYBACK: Unexpected situation. Not BOF, but also no previous sample." << std::endl;
|
||||
return; // Should not occur
|
||||
}
|
||||
dOffset = prSample.first.dTimestamp;
|
||||
operator++();
|
||||
}
|
||||
|
||||
loopCount++;
|
||||
auto timepointStartOfFirstLoop = std::chrono::high_resolution_clock::now();
|
||||
auto timepointStart = std::chrono::high_resolution_clock::now();
|
||||
while (m_bPlayback)
|
||||
{
|
||||
if (IsEOF())
|
||||
{
|
||||
if (!bRepeat) break; // Done
|
||||
|
||||
// Data set will be repeated, callculate offset compared to the first run
|
||||
auto timepointStartOfNextLoop = std::chrono::high_resolution_clock::now();
|
||||
dLoopOffset = std::chrono::duration<double>(timepointStartOfNextLoop - timepointStartOfFirstLoop).count();
|
||||
|
||||
// Restart
|
||||
loopCount++;
|
||||
dOffset = 0.00000;
|
||||
JumpBegin();
|
||||
}
|
||||
|
||||
// Get a sample
|
||||
auto prSample = Get();
|
||||
if (!prSample.second)
|
||||
break; // Should not occur
|
||||
|
||||
// Need to sleep?
|
||||
while (m_bPlayback)
|
||||
{
|
||||
// Determine the current time relative to start time.
|
||||
auto timepoint = std::chrono::high_resolution_clock::now();
|
||||
double dTime = std::chrono::duration<double>(timepoint - timepointStart).count() + dOffset;
|
||||
|
||||
// If the sample is overdue... do not sleep
|
||||
if ((prSample.first.dTimestamp + dLoopOffset) < dTime)
|
||||
break;
|
||||
|
||||
// If the sample will be overdue in 10 ms, yield the current thread only (this will not put the thread in idle mode).
|
||||
//if (prSample.first.dTimestamp < dTime + 0.010)
|
||||
// std::this_thread::yield();
|
||||
//else // Sleep shortly... this might cause a sleep of more than 1ms dependable on the load of the system
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(0));
|
||||
}
|
||||
|
||||
// Send the sample
|
||||
fnCallback(prSample.first);
|
||||
|
||||
// Next sample
|
||||
operator++();
|
||||
}
|
||||
|
||||
m_bPlayback = false;
|
||||
}
|
||||
} // namespace asc
|
||||
162
global/ascformat/ascreader.h
Normal file
162
global/ascformat/ascreader.h
Normal file
@@ -0,0 +1,162 @@
|
||||
#ifndef ASC_FILE_READER_H
|
||||
#define ASC_FILE_READER_H
|
||||
|
||||
#include <list>
|
||||
#include <functional>
|
||||
#include <filesystem>
|
||||
#include <thread>
|
||||
|
||||
namespace asc
|
||||
{
|
||||
/**
|
||||
* @brief CAN message structure
|
||||
*/
|
||||
struct SCanMessage
|
||||
{
|
||||
double dTimestamp; ///< Timestamp in seconds
|
||||
uint32_t uiChannel; ///< CAN channel (first channel starts with 0).
|
||||
uint32_t uiId; ///< CAN ID
|
||||
bool bExtended; ///< Set when the CAN ID is extended (29 bits) or not when standard
|
||||
///< (11 bits).
|
||||
bool bCanFd; ///< Set when the message contains CAN-FD data (more than 8 bytes).
|
||||
/// Direction enumeration
|
||||
enum class EDirection { rx, tx } eDirection; ///< Direction RX or TX.
|
||||
uint32_t uiLength; ///< Length of the CAN data.
|
||||
uint8_t rguiData[64]; ///< Array with the CAN data.
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief This class allows reading the Vector ASC file format.
|
||||
* @attention This class assumes no concurrency between reading a file, navigation and playback. No thread synchronization is
|
||||
* implemented.
|
||||
*/
|
||||
class CAscReader
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Constructor
|
||||
*/
|
||||
CAscReader();
|
||||
|
||||
/**
|
||||
* @brief Destructor
|
||||
*/
|
||||
~CAscReader();
|
||||
|
||||
/**
|
||||
* @brief Read a file (this will replace the current samples with the samples of the file).
|
||||
* @param[in] rpathFile Reference to the file path.
|
||||
* @return Returns 'true' on success or 'false' when not.
|
||||
*/
|
||||
bool Read(const std::filesystem::path& rpathFile);
|
||||
|
||||
/**
|
||||
* @brief Get the current sample. If the current position is EOF, no sample is returned.
|
||||
* @return A pair consisting of the current sample and a boolean indicating that the sample is valid.
|
||||
*/
|
||||
std::pair<SCanMessage, bool> Get() const;
|
||||
|
||||
/**
|
||||
* @brief Get the number of loops the data set was sent
|
||||
* @return Number of loops the data set was sent including the current loop
|
||||
*/
|
||||
uint32_t GetLoopCount() const;
|
||||
|
||||
/**
|
||||
* @brief Jump to the first sample.
|
||||
*/
|
||||
void JumpBegin();
|
||||
|
||||
/**
|
||||
* @brief Jump beyond the last sample.
|
||||
*/
|
||||
void JumpEnd();
|
||||
|
||||
/**
|
||||
* @{
|
||||
* @brief Increase the current position to the next sample.
|
||||
* @return Reference to this class.
|
||||
*/
|
||||
CAscReader& operator++();
|
||||
CAscReader& operator++(int);
|
||||
/**
|
||||
* @}
|
||||
*/
|
||||
|
||||
/**
|
||||
* @{
|
||||
* @brief Decrease the current position to the previous sample.
|
||||
* @return Reference to this class.
|
||||
*/
|
||||
CAscReader& operator--();
|
||||
CAscReader& operator--(int);
|
||||
/**
|
||||
* @}
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief Does the current position point to the first sample?
|
||||
* @return Returns whether the current position points to the first sample.
|
||||
*/
|
||||
bool IsBOF() const;
|
||||
|
||||
/**
|
||||
* @brief Does the current position point beyond the last sample?
|
||||
* @return Returns whether the current position points beyond the last sample.
|
||||
*/
|
||||
bool IsEOF() const;
|
||||
|
||||
/**
|
||||
* @brief Start playback using the timestamp of the samples to come as close to the original recording time.
|
||||
* @param[in] fnCallback The function being called with the current sample.
|
||||
* @param[in] bRepeat When set, the playback continuous at the beginning when reaching the end of the data set.
|
||||
*/
|
||||
void StartPlayback(std::function<void(const SCanMessage&)> fnCallback, bool bRepeat = true);
|
||||
|
||||
/**
|
||||
* @brief Stop the current playback.
|
||||
* @remarks This does not change the current position (pointing to the next sample following the one just sent.
|
||||
*/
|
||||
void StopPlayback();
|
||||
|
||||
/**
|
||||
* @brief Reset the playback to the beginning of the data set. This is equal to calling StopPlayback and JumpBegin.
|
||||
*/
|
||||
void ResetPlayback();
|
||||
|
||||
/**
|
||||
* @brief Returns whether playback is running at the moment.
|
||||
* @return Returns whether playback is running.
|
||||
*/
|
||||
bool PlaybackRunning() const;
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief Process a measurement value sample (one line within the ASC trigger block section).
|
||||
* @details The ASC measurement uses the following format for one CAN measurement value:
|
||||
*
|
||||
* \verbatim
|
||||
* For CAN: Timestamp Busnumber CanId ["x"] "Rx|Tx" "d" Number-of-bytes Byte1, Byte2, ..., Byte8 [...]
|
||||
* For CAN-FD: Timestamp CANFD <channel> <dir> <id> <brs> <esi> <dlc> <data_len> <data> [ ... ]
|
||||
* \endverbatim
|
||||
*
|
||||
* @param[in] rssSample Reference to the measurement value sample.
|
||||
*/
|
||||
void ProcessSample(const std::string& rssSample);
|
||||
|
||||
/**
|
||||
* @brief Playback thread function. If the current position is BOF, the playback starts from time = 0.0000, otherwise the
|
||||
* playback starts from the current position.
|
||||
*/
|
||||
void PlaybackThreadFunc(std::function<void(const SCanMessage&)> fnCallback, std::atomic<uint32_t>& loopCount, bool bRepeat);
|
||||
|
||||
std::list<SCanMessage> m_lstMessages; ///< Vector with messages
|
||||
std::list<SCanMessage>::iterator m_itCurrent; ///< Current iterator position
|
||||
std::thread m_threadPlayback; ///< Playback thread.
|
||||
bool m_bPlaybackThread = false; ///< Set when running playback thread
|
||||
bool m_bPlayback = false; ///< Set when running playback
|
||||
std::atomic<uint32_t> m_uiLoopCount{ 0 }; ///< Counter how often the data set was sent
|
||||
};
|
||||
}
|
||||
|
||||
#endif // !defined ASC_FILE_READER_H
|
||||
96
global/ascformat/ascwriter.cpp
Normal file
96
global/ascformat/ascwriter.cpp
Normal file
@@ -0,0 +1,96 @@
|
||||
#include "ascwriter.h"
|
||||
#include <fstream>
|
||||
|
||||
namespace asc
|
||||
{
|
||||
CAscWriter::CAscWriter()
|
||||
{
|
||||
StartTimer();
|
||||
}
|
||||
|
||||
void CAscWriter::AddSample(const SCanMessage& rsSample)
|
||||
{
|
||||
SCanMessage sSampleCopy(rsSample);
|
||||
if (sSampleCopy.dTimestamp == 0.0000000)
|
||||
sSampleCopy.dTimestamp =
|
||||
std::chrono::duration<double>(std::chrono::high_resolution_clock::now() - m_timepointStart).count();
|
||||
else
|
||||
{
|
||||
if (!m_lstMessages.empty() && m_lstMessages.back().dTimestamp > sSampleCopy.dTimestamp) return;
|
||||
}
|
||||
m_lstMessages.push_back(std::move(sSampleCopy));
|
||||
}
|
||||
|
||||
bool CAscWriter::HasSamples() const
|
||||
{
|
||||
return !m_lstMessages.empty();
|
||||
}
|
||||
|
||||
void CAscWriter::StartTimer()
|
||||
{
|
||||
if (m_lstMessages.empty())
|
||||
{
|
||||
m_timepointStart = std::chrono::high_resolution_clock::now();
|
||||
m_timepointSystem = std::chrono::system_clock::now();
|
||||
}
|
||||
}
|
||||
|
||||
void CAscWriter::Clear()
|
||||
{
|
||||
m_lstMessages.clear();
|
||||
m_timepointStart = std::chrono::high_resolution_clock::now();
|
||||
}
|
||||
|
||||
bool CAscWriter::Write(const std::filesystem::path& rpathFile)
|
||||
{
|
||||
// Open and write the file
|
||||
std::ofstream fstream(rpathFile, std::ios::out | std::ios::trunc);
|
||||
if (!fstream.is_open()) return false;
|
||||
|
||||
// Fill in header
|
||||
time_t tmSystemTime = std::chrono::system_clock::to_time_t(m_timepointSystem);
|
||||
fstream << "date " << std::put_time(std::localtime(&tmSystemTime), "%c") << std::endl;
|
||||
fstream << "base hex timestamps absolute" << std::endl;
|
||||
|
||||
// Stream trigger block
|
||||
fstream << "Begin Triggerblock " << std::put_time(std::localtime(&tmSystemTime), "%c") << std::endl;
|
||||
fstream << " 0.000000 Start of measurement" << std::endl;
|
||||
for (const SCanMessage& rsSample : m_lstMessages)
|
||||
{
|
||||
std::stringstream sstreamTimestamp;
|
||||
sstreamTimestamp << std::fixed << std::setprecision(6) << rsSample.dTimestamp;
|
||||
fstream << std::string(11ull - sstreamTimestamp.str().length(), ' ') << sstreamTimestamp.str();
|
||||
if (rsSample.bCanFd)
|
||||
{
|
||||
fstream << " " << "CANFD" << " " << rsSample.uiChannel << " " <<
|
||||
(rsSample.eDirection == SCanMessage::EDirection::rx ? "Rx" : "Tx") <<
|
||||
std::hex << std::uppercase << rsSample.uiId << (rsSample.bExtended ? "x" : "") << " 1 0 ";
|
||||
const size_t rgnDLCLength[16] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 12, 16, 20, 24, 32, 48, 64 };
|
||||
size_t nDLC = 0;
|
||||
for (size_t n = 0; n < 16; n++)
|
||||
if (rgnDLCLength[n] == rsSample.uiLength) nDLC = n;
|
||||
fstream << nDLC << " " << rsSample.uiLength;
|
||||
}
|
||||
else // Normal CAN
|
||||
{
|
||||
fstream << " " << rsSample.uiChannel;
|
||||
std::stringstream sstreamID;
|
||||
sstreamID << std::hex << std::uppercase << rsSample.uiId;
|
||||
fstream << " " << sstreamID.str() << (rsSample.bExtended ? "x" : "");
|
||||
fstream << std::string(16ull - sstreamID.str().length(), ' ') <<
|
||||
(rsSample.eDirection == SCanMessage::EDirection::rx ? "Rx" : "Tx");
|
||||
fstream << " " << "d";
|
||||
fstream << " " << rsSample.uiLength;
|
||||
}
|
||||
for (uint32_t uiIndex = 0; uiIndex < rsSample.uiLength; uiIndex++)
|
||||
fstream << " " << std::hex << std::setw(2) << std::setfill('0') << std::uppercase << static_cast<size_t>(rsSample.rguiData[uiIndex]);
|
||||
fstream << std::endl;
|
||||
}
|
||||
fstream << "End TriggerBlock" << std::endl;
|
||||
|
||||
// Done
|
||||
fstream.close();
|
||||
|
||||
return true;
|
||||
}
|
||||
} // namespace asc
|
||||
63
global/ascformat/ascwriter.h
Normal file
63
global/ascformat/ascwriter.h
Normal file
@@ -0,0 +1,63 @@
|
||||
#ifndef ASC_FILE_WRITER_H
|
||||
#define ASC_FILE_WRITER_H
|
||||
|
||||
#include "ascreader.h"
|
||||
#include <list>
|
||||
#include <chrono>
|
||||
|
||||
namespace asc
|
||||
{
|
||||
/**
|
||||
* @brief This class allows writing the Vector ASC file format.
|
||||
* @attention This class assumes no concurrency between writing the file and adding samples. No thread synchronization is
|
||||
* implemented.
|
||||
*/
|
||||
class CAscWriter
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Constructor
|
||||
*/
|
||||
CAscWriter();
|
||||
|
||||
/**
|
||||
* @brief Start the timer for automatic timestamp creation.
|
||||
* @remarks Resets the start time only if there are no samples are available.
|
||||
*/
|
||||
void StartTimer();
|
||||
|
||||
/**
|
||||
* @brief Add a sample.
|
||||
* @attention The sample will not be added if the timestamp is smaller than the last sample.
|
||||
* @param[in] rsSample Reference to the CAN sample structure. If the timestamp is empty, the timestamp is created automatically
|
||||
* using the integrated timer.
|
||||
*/
|
||||
void AddSample(const SCanMessage& rsSample);
|
||||
|
||||
/**
|
||||
* @brief Returns whether at least one sample is stored.
|
||||
* @return Returns 'true' when samples are available; 'false' when not.
|
||||
*/
|
||||
bool HasSamples() const;
|
||||
|
||||
/**
|
||||
* @brief Clear the content.
|
||||
*/
|
||||
void Clear();
|
||||
|
||||
/**
|
||||
* @brief Write to a file (the file gets overwritten if existing).
|
||||
* @param[in] rpathFile Reference to the file path.
|
||||
* @return Returns 'true' on success or 'false' when not.
|
||||
*/
|
||||
bool Write(const std::filesystem::path& rpathFile);
|
||||
|
||||
private:
|
||||
std::list<SCanMessage> m_lstMessages; ///< Queue with messages
|
||||
std::chrono::high_resolution_clock::time_point m_timepointStart; ///< Starting timepoint for automatic timestamp
|
||||
///< generation.
|
||||
std::chrono::system_clock::time_point m_timepointSystem; ///< Starting timepoint for time/date generation.
|
||||
};
|
||||
}
|
||||
|
||||
#endif // !defined ASC_FILE_WRITER_H
|
||||
Reference in New Issue
Block a user