Precommit (#1)

* first commit

* cleanup
This commit is contained in:
tompzf
2025-11-04 13:28:06 +01:00
committed by GitHub
parent dba45dc636
commit 6ed4b1534e
898 changed files with 256340 additions and 0 deletions

View 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

View 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

View 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

View 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