mirror of
https://github.com/Serial-Studio/Serial-Studio.git
synced 2025-01-15 05:22:53 +08:00
Add option to read frames using a start delimiter (@AnnabellePundaky)
This commit is contained in:
parent
cf2af5ad66
commit
9c0f2b6c13
@ -386,10 +386,12 @@ ColumnLayout {
|
||||
currentIndex: editableValue
|
||||
font: Cpp_Misc_CommonFonts.monoFont
|
||||
onCurrentIndexChanged: {
|
||||
root.modelPointer.setData(
|
||||
view.index(row, column),
|
||||
currentIndex,
|
||||
ProjectModel.EditableValue)
|
||||
if (currentIndex !== editableValue) {
|
||||
root.modelPointer.setData(
|
||||
view.index(row, column),
|
||||
currentIndex,
|
||||
ProjectModel.EditableValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -58,7 +58,7 @@ public:
|
||||
[[nodiscard]] T read(qsizetype size);
|
||||
[[nodiscard]] T peek(qsizetype size) const;
|
||||
|
||||
[[nodiscard]] int findPatternKMP(const T &pattern);
|
||||
[[nodiscard]] int findPatternKMP(const T &pattern, const int pos = 0);
|
||||
|
||||
private:
|
||||
[[nodiscard]] std::vector<int> computeKMPTable(const T &p) const;
|
||||
@ -253,41 +253,66 @@ T IO::CircularBuffer<T, StorageType>::peek(qsizetype size) const
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Searches for a pattern in the buffer using the KMP algorithm.
|
||||
* @brief Searches for a pattern in the circular buffer using the KMP algorithm.
|
||||
*
|
||||
* Implements the Knuth-Morris-Pratt algorithm to efficiently find a pattern in
|
||||
* the buffer.
|
||||
* This function uses the Knuth-Morris-Pratt (KMP) string matching algorithm
|
||||
* to efficiently find the first occurrence of a given pattern within the
|
||||
* circular buffer, starting from a specified position. The circular nature of
|
||||
* the buffer is correctly handled to ensure accurate matching, even if the
|
||||
* pattern spans the end of the buffer and the beginning.
|
||||
*
|
||||
* @param pattern The QByteArray representing the pattern to search for.
|
||||
* @return The index of the first occurrence of the pattern, or -1 if not found.
|
||||
* @tparam T The type of elements stored in the pattern and the buffer.
|
||||
* @tparam StorageType The type of storage used for the circular buffer.
|
||||
* @param pattern The pattern to search for in the buffer.
|
||||
* @param pos The starting position (relative to the logical start of the
|
||||
* buffer) for the search. Defaults to the beginning if set to 0.
|
||||
*
|
||||
* @return The index (relative to the logical start of the buffer) of the first
|
||||
* occurrence of the pattern, or -1 if the pattern is not found.
|
||||
*
|
||||
* @warning If the `pattern` is empty or its size exceeds the current size of
|
||||
* the buffer, the function returns -1 immediately.
|
||||
*
|
||||
* @pre The buffer must be properly initialized, and the pattern should not
|
||||
* exceed the capacity of the buffer.
|
||||
*/
|
||||
template<typename T, typename StorageType>
|
||||
int IO::CircularBuffer<T, StorageType>::findPatternKMP(const T &pattern)
|
||||
int IO::CircularBuffer<T, StorageType>::findPatternKMP(const T &pattern,
|
||||
const int pos)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
|
||||
// Validate search pattern
|
||||
if (pattern.isEmpty() || m_size < pattern.size())
|
||||
return -1;
|
||||
|
||||
// Start search at `pos`
|
||||
std::vector<int> lps = computeKMPTable(pattern);
|
||||
qsizetype bufferIdx = m_head;
|
||||
qsizetype bufferIdx = (m_head + pos) % m_capacity;
|
||||
int i = pos, j = 0;
|
||||
|
||||
int i = 0, j = 0;
|
||||
while (i < m_size)
|
||||
{
|
||||
// Compare current buffer character with the pattern
|
||||
if (m_buffer[bufferIdx] == pattern[j])
|
||||
{
|
||||
i++;
|
||||
j++;
|
||||
bufferIdx = (bufferIdx + 1) % m_capacity;
|
||||
|
||||
// If the whole pattern is matched, return the logical start index
|
||||
if (j == pattern.size())
|
||||
return i - j;
|
||||
{
|
||||
int matchStart = i - j;
|
||||
return matchStart;
|
||||
}
|
||||
}
|
||||
|
||||
// Mismatch after some matches, fall back in pattern
|
||||
else if (j != 0)
|
||||
j = lps[j - 1];
|
||||
|
||||
// Mismatch at the start, move forward
|
||||
else
|
||||
{
|
||||
i++;
|
||||
@ -295,9 +320,9 @@ int IO::CircularBuffer<T, StorageType>::findPatternKMP(const T &pattern)
|
||||
}
|
||||
}
|
||||
|
||||
// Pattern not found
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Computes the KMP table for a given p.
|
||||
*
|
||||
|
@ -153,6 +153,7 @@ void IO::FrameReader::processData(const QByteArray &data)
|
||||
|
||||
// Add data to circular buffer
|
||||
m_dataBuffer.append(data);
|
||||
Q_EMIT dataReceived(data);
|
||||
|
||||
// Read frames in no-delimiter mode directly
|
||||
if (m_operationMode == SerialStudio::ProjectFile
|
||||
@ -163,9 +164,6 @@ void IO::FrameReader::processData(const QByteArray &data)
|
||||
else
|
||||
QMetaObject::invokeMethod(this, &FrameReader::readFrames,
|
||||
Qt::QueuedConnection);
|
||||
|
||||
// Notify UI of received data
|
||||
Q_EMIT dataReceived(data);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -264,6 +262,10 @@ void IO::FrameReader::readFrames()
|
||||
if (m_frameDetectionMode == SerialStudio::EndDelimiterOnly)
|
||||
readEndDelimetedFrames();
|
||||
|
||||
// Read using only a start delimeter
|
||||
else if (m_frameDetectionMode == SerialStudio::StartDelimiterOnly)
|
||||
readStartDelimitedFrames();
|
||||
|
||||
// Read using both a start & end delimiter
|
||||
else if (m_frameDetectionMode == SerialStudio::StartAndEndDelimiter)
|
||||
readStartEndDelimetedFrames();
|
||||
@ -360,6 +362,59 @@ void IO::FrameReader::readEndDelimetedFrames()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Reads frames delimited by a start sequence from the buffer.
|
||||
*
|
||||
* Extracts frames from the circular buffer that are bounded by specified
|
||||
* start delimiters. Emits `frameReady` for each valid frame.
|
||||
*/
|
||||
void IO::FrameReader::readStartDelimitedFrames()
|
||||
{
|
||||
// Cap the number of frames that we can read in a single call
|
||||
int framesRead = 0;
|
||||
constexpr int maxFrames = 100;
|
||||
|
||||
// Consume the buffer until
|
||||
while (framesRead < maxFrames)
|
||||
{
|
||||
// Initialize variables
|
||||
int startIndex = -1;
|
||||
int nextStartIndex = -1;
|
||||
|
||||
// Find the first start sequence in the buffer (project mode)
|
||||
startIndex = m_dataBuffer.findPatternKMP(m_startSequence);
|
||||
if (startIndex == -1)
|
||||
break;
|
||||
|
||||
// Find the next start sequence after the current one
|
||||
nextStartIndex = m_dataBuffer.findPatternKMP(
|
||||
m_startSequence, startIndex + m_startSequence.size());
|
||||
if (nextStartIndex == -1 || nextStartIndex == startIndex
|
||||
|| nextStartIndex < startIndex)
|
||||
break;
|
||||
|
||||
// Extract the frame from the buffer
|
||||
qsizetype frameStart = startIndex + m_startSequence.size();
|
||||
qsizetype frameLength = nextStartIndex - frameStart;
|
||||
QByteArray frame = m_dataBuffer.peek(frameStart + frameLength)
|
||||
.mid(frameStart, frameLength);
|
||||
|
||||
// Parse frame if not empty
|
||||
if (!frame.isEmpty())
|
||||
{
|
||||
Q_EMIT frameReady(frame);
|
||||
(void)m_dataBuffer.read(frameStart + frameLength);
|
||||
}
|
||||
|
||||
// Avoid infinite loops when getting a frame length of 0
|
||||
else
|
||||
(void)m_dataBuffer.read(frameStart);
|
||||
|
||||
// Increment number of frames read
|
||||
++framesRead;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Reads frames delimited by both start and end sequences from the
|
||||
* buffer.
|
||||
|
@ -78,6 +78,7 @@ private slots:
|
||||
|
||||
private:
|
||||
void readEndDelimetedFrames();
|
||||
void readStartDelimitedFrames();
|
||||
void readStartEndDelimetedFrames();
|
||||
ValidationStatus integrityChecks(const QByteArray &frame,
|
||||
const QByteArray &delimeter,
|
||||
|
@ -2090,7 +2090,8 @@ void JSON::ProjectModel::buildProjectModel()
|
||||
frameDetection->setEditable(true);
|
||||
frameDetection->setData(ComboBox, WidgetType);
|
||||
frameDetection->setData(m_frameDetectionMethods, ComboBoxData);
|
||||
frameDetection->setData(m_frameDetection, EditableValue);
|
||||
frameDetection->setData(
|
||||
m_frameDetectionMethodsValues.indexOf(m_frameDetection), EditableValue);
|
||||
frameDetection->setData(tr("Frame Detection"), ParameterName);
|
||||
frameDetection->setData(kProjectView_FrameDetection, ParameterType);
|
||||
frameDetection->setData(tr("Strategy used for identifying frame data"),
|
||||
@ -2098,7 +2099,8 @@ void JSON::ProjectModel::buildProjectModel()
|
||||
m_projectModel->appendRow(frameDetection);
|
||||
|
||||
// Add frame start sequence
|
||||
if (m_frameDetection == SerialStudio::StartAndEndDelimiter)
|
||||
if (m_frameDetection == SerialStudio::StartAndEndDelimiter
|
||||
|| m_frameDetection == SerialStudio::StartDelimiterOnly)
|
||||
{
|
||||
auto frameStart = new QStandardItem();
|
||||
frameStart->setEditable(true);
|
||||
@ -2701,9 +2703,15 @@ void JSON::ProjectModel::generateComboBoxModels()
|
||||
|
||||
// Initialize frame detection methods
|
||||
m_frameDetectionMethods.clear();
|
||||
m_frameDetectionMethodsValues.clear();
|
||||
m_frameDetectionMethods.append(tr("End Delimiter Only"));
|
||||
m_frameDetectionMethods.append(tr("Start Delimiter Only"));
|
||||
m_frameDetectionMethods.append(tr("Start + End Delimiter"));
|
||||
m_frameDetectionMethods.append(tr("No Delimiters"));
|
||||
m_frameDetectionMethodsValues.append(SerialStudio::EndDelimiterOnly);
|
||||
m_frameDetectionMethodsValues.append(SerialStudio::StartDelimiterOnly);
|
||||
m_frameDetectionMethodsValues.append(SerialStudio::StartAndEndDelimiter);
|
||||
m_frameDetectionMethodsValues.append(SerialStudio::NoDelimiters);
|
||||
|
||||
// Initialize group-level widgets
|
||||
m_groupWidgets.clear();
|
||||
@ -2997,8 +3005,7 @@ void JSON::ProjectModel::onProjectItemChanged(QStandardItem *item)
|
||||
m_frameDecoder = static_cast<SerialStudio::DecoderMethod>(value.toInt());
|
||||
break;
|
||||
case kProjectView_FrameDetection:
|
||||
m_frameDetection
|
||||
= static_cast<SerialStudio::FrameDetection>(value.toInt());
|
||||
m_frameDetection = m_frameDetectionMethodsValues.at(value.toInt());
|
||||
Q_EMIT frameDetectionChanged();
|
||||
buildProjectModel();
|
||||
break;
|
||||
|
@ -340,6 +340,8 @@ private:
|
||||
QStringList m_fftSamples;
|
||||
QStringList m_decoderOptions;
|
||||
QStringList m_frameDetectionMethods;
|
||||
QList<SerialStudio::FrameDetection> m_frameDetectionMethodsValues;
|
||||
|
||||
QMap<QString, QString> m_eolSequences;
|
||||
QMap<QString, QString> m_groupWidgets;
|
||||
QMap<QString, QString> m_datasetWidgets;
|
||||
|
@ -142,6 +142,9 @@ public:
|
||||
PlainText, /**< Standard decoding, interprets data as plain text. */
|
||||
Hexadecimal, /**< Decodes data assuming a hexadecimal-encoded format. */
|
||||
Base64 /**< Decodes data assuming a Base64-encoded format. */
|
||||
/* IMPORTANT: When adding other modes, please don't modify the order of the
|
||||
* enums to ensure backward compatiblity with previous project
|
||||
* files!! */
|
||||
};
|
||||
Q_ENUM(DecoderMethod)
|
||||
|
||||
@ -153,15 +156,19 @@ public:
|
||||
* of a data frame in a continuous stream. Each value represents a specific
|
||||
* detection method.
|
||||
*/
|
||||
// clang-format off
|
||||
enum FrameDetection
|
||||
{
|
||||
EndDelimiterOnly, /**< Detects frames based only on an end delimiter. */
|
||||
StartAndEndDelimiter, /**< Detects frames based on both start and end
|
||||
delimiters. */
|
||||
NoDelimiters /**< Disables frame detection and processes incoming
|
||||
data directly */
|
||||
EndDelimiterOnly = 0x00, /**< Detects frames based only on an end delimiter. */
|
||||
StartAndEndDelimiter = 0x01, /**< Detects frames based on both start and end delimiters. */
|
||||
NoDelimiters = 0x02, /**< Disables frame detection and processes incoming data directly */
|
||||
StartDelimiterOnly = 0x03 /**< Detects frames with only a header */
|
||||
/* IMPORTANT: When adding other modes, please don't modify the order of the
|
||||
* enums to ensure backward compatiblity with previous project
|
||||
* files!! */
|
||||
};
|
||||
Q_ENUM(FrameDetection)
|
||||
// clang-format on
|
||||
|
||||
/**
|
||||
* @enum OperationMode
|
||||
@ -176,6 +183,9 @@ public:
|
||||
ProjectFile, /**< Builds the dashboard using a predefined project file. */
|
||||
DeviceSendsJSON, /**< Builds the dashboard from device-sent JSON. */
|
||||
QuickPlot, /**< Quick and simple data plotting mode. */
|
||||
/* IMPORTANT: When adding other modes, please don't modify the order of the
|
||||
* enums to ensure backward compatiblity with previous project
|
||||
* files!! */
|
||||
};
|
||||
Q_ENUM(OperationMode)
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user