mirror of
https://github.com/Serial-Studio/Serial-Studio.git
synced 2025-01-15 05:22:53 +08:00
Allow sending binary data through actions (@AnnabellePundaky)
This commit is contained in:
parent
e238d8cdd1
commit
9f53bb6212
@ -421,6 +421,77 @@ ColumnLayout {
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// HexTextEdit editor
|
||||
//
|
||||
Loader {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Layout.minimumWidth: root.valueWidth
|
||||
Layout.maximumWidth: root.valueWidth
|
||||
active: model.widgetType === ProjectModel.HexTextField
|
||||
visible: model.widgetType === ProjectModel.HexTextField
|
||||
|
||||
sourceComponent: TextField {
|
||||
id: _hexComponent
|
||||
text: model.editableValue
|
||||
font: Cpp_Misc_CommonFonts.monoFont
|
||||
placeholderText: model.placeholderValue
|
||||
color: Cpp_ThemeManager.colors["table_text"]
|
||||
|
||||
//
|
||||
// Add space automatically in hex view
|
||||
//
|
||||
onTextChanged: {
|
||||
// Get the current cursor position
|
||||
const currentCursorPosition = _hexComponent.cursorPosition;
|
||||
const cursorAtEnd = (currentCursorPosition === _hexComponent.text.length);
|
||||
|
||||
// Format the text
|
||||
const originalText = _hexComponent.text;
|
||||
const formattedText = Cpp_IO_Console.formatUserHex(_hexComponent.text);
|
||||
const isValid = Cpp_IO_Console.validateUserHex(formattedText);
|
||||
|
||||
// Update the text only if it has changed
|
||||
if (originalText !== formattedText) {
|
||||
_hexComponent.text = formattedText;
|
||||
|
||||
// Restore the cursor position, adjusting for added spaces
|
||||
if (!cursorAtEnd) {
|
||||
// Remove spaces from originalText and formattedText to compare lengths
|
||||
const cleanedOriginalText = originalText.replace(/ /g, '');
|
||||
const cleanedFormattedText = formattedText.replace(/ /g, '');
|
||||
|
||||
// Calculate the difference in length due to formatting
|
||||
const lengthDifference = cleanedFormattedText.length - cleanedOriginalText.length;
|
||||
|
||||
// Count spaces before the cursor in both texts
|
||||
let spacesBeforeCursorOriginal = (originalText.slice(0, currentCursorPosition).match(/ /g) || []).length;
|
||||
let spacesBeforeCursorFormatted = (formattedText.slice(0, currentCursorPosition).match(/ /g) || []).length;
|
||||
|
||||
// Calculate adjustment factor
|
||||
const adjustment = spacesBeforeCursorFormatted - spacesBeforeCursorOriginal + lengthDifference;
|
||||
|
||||
// Restore the cursor position with adjustment
|
||||
_hexComponent.cursorPosition = Math.min(currentCursorPosition + adjustment, send.text.length);
|
||||
}
|
||||
}
|
||||
|
||||
// Update the palette based on validation
|
||||
_hexComponent.color = isValid
|
||||
? Cpp_ThemeManager.colors["table_text"]
|
||||
: Cpp_ThemeManager.colors["alarm"]
|
||||
|
||||
// Set model data
|
||||
root.modelPointer.setData(
|
||||
view.index(row, column),
|
||||
formattedText,
|
||||
ProjectModel.EditableValue)
|
||||
}
|
||||
|
||||
background: Item {}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Separator + Spacer
|
||||
//
|
||||
|
@ -270,6 +270,49 @@ QString IO::Console::formatUserHex(const QString &text)
|
||||
return str;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the given @a data in HEX format into real binary data.
|
||||
*/
|
||||
QByteArray IO::Console::hexToBytes(const QString &data)
|
||||
{
|
||||
// Remove spaces from the input data
|
||||
QString withoutSpaces = data;
|
||||
withoutSpaces.replace(QStringLiteral(" "), "");
|
||||
|
||||
// Check if the length of the string is even
|
||||
if (withoutSpaces.length() % 2 != 0)
|
||||
{
|
||||
qWarning() << data << "is not a valid hexadecimal array";
|
||||
return QByteArray();
|
||||
}
|
||||
|
||||
// Iterate over the string in steps of 2
|
||||
bool ok;
|
||||
QByteArray array;
|
||||
for (int i = 0; i < withoutSpaces.length(); i += 2)
|
||||
{
|
||||
// Get two characters (a hex pair)
|
||||
auto chr1 = withoutSpaces.at(i);
|
||||
auto chr2 = withoutSpaces.at(i + 1);
|
||||
|
||||
// Convert the hex pair into a byte
|
||||
QString byteStr = QStringLiteral("%1%2").arg(chr1, chr2);
|
||||
int byte = byteStr.toInt(&ok, 16);
|
||||
|
||||
// If the conversion fails, return an empty array
|
||||
if (!ok)
|
||||
{
|
||||
qWarning() << data << "is not a valid hexadecimal array";
|
||||
return QByteArray();
|
||||
}
|
||||
|
||||
// Append the byte to the result array
|
||||
array.append(static_cast<char>(byte));
|
||||
}
|
||||
|
||||
return array;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows the user to export the information displayed on the console
|
||||
*/
|
||||
@ -607,49 +650,6 @@ void IO::Console::addToHistory(const QString &command)
|
||||
Q_EMIT historyItemChanged();
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the given @a data in HEX format into real binary data.
|
||||
*/
|
||||
QByteArray IO::Console::hexToBytes(const QString &data)
|
||||
{
|
||||
// Remove spaces from the input data
|
||||
QString withoutSpaces = data;
|
||||
withoutSpaces.replace(QStringLiteral(" "), "");
|
||||
|
||||
// Check if the length of the string is even
|
||||
if (withoutSpaces.length() % 2 != 0)
|
||||
{
|
||||
qWarning() << data << "is not a valid hexadecimal array";
|
||||
return QByteArray();
|
||||
}
|
||||
|
||||
// Iterate over the string in steps of 2
|
||||
bool ok;
|
||||
QByteArray array;
|
||||
for (int i = 0; i < withoutSpaces.length(); i += 2)
|
||||
{
|
||||
// Get two characters (a hex pair)
|
||||
auto chr1 = withoutSpaces.at(i);
|
||||
auto chr2 = withoutSpaces.at(i + 1);
|
||||
|
||||
// Convert the hex pair into a byte
|
||||
QString byteStr = QStringLiteral("%1%2").arg(chr1, chr2);
|
||||
int byte = byteStr.toInt(&ok, 16);
|
||||
|
||||
// If the conversion fails, return an empty array
|
||||
if (!ok)
|
||||
{
|
||||
qWarning() << data << "is not a valid hexadecimal array";
|
||||
return QByteArray();
|
||||
}
|
||||
|
||||
// Append the byte to the result array
|
||||
array.append(static_cast<char>(byte));
|
||||
}
|
||||
|
||||
return array;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the given @a data to a string according to the console display mode
|
||||
* set by the user.
|
||||
|
@ -138,6 +138,8 @@ public:
|
||||
Q_INVOKABLE bool validateUserHex(const QString &text);
|
||||
Q_INVOKABLE QString formatUserHex(const QString &text);
|
||||
|
||||
static QByteArray hexToBytes(const QString &data);
|
||||
|
||||
public slots:
|
||||
void save();
|
||||
void clear();
|
||||
@ -159,7 +161,6 @@ private slots:
|
||||
void onDataReceived(const QByteArray &data);
|
||||
|
||||
private:
|
||||
QByteArray hexToBytes(const QString &data);
|
||||
QString dataToString(const QByteArray &data);
|
||||
QString plainTextStr(const QByteArray &data);
|
||||
QString hexadecimalStr(const QByteArray &data);
|
||||
|
@ -55,6 +55,7 @@ static QVariant SAFE_READ(const QJsonObject &object, const QString &key,
|
||||
*/
|
||||
JSON::Action::Action(const int actionId)
|
||||
: m_actionId(actionId)
|
||||
, m_binaryData(false)
|
||||
, m_icon("Play Property")
|
||||
, m_title("")
|
||||
, m_txData("")
|
||||
@ -72,6 +73,16 @@ int JSON::Action::actionId() const
|
||||
return m_actionId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Checks if the user wants to send binary data to the connected device.
|
||||
*
|
||||
* @return @c true if binary data encoding is enabled, @c false otherwise.
|
||||
*/
|
||||
bool JSON::Action::binaryData() const
|
||||
{
|
||||
return m_binaryData;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Gets the icon associated with the action.
|
||||
*
|
||||
@ -126,6 +137,7 @@ QJsonObject JSON::Action::serialize() const
|
||||
object.insert(QStringLiteral("icon"), m_icon);
|
||||
object.insert(QStringLiteral("txData"), m_txData);
|
||||
object.insert(QStringLiteral("eol"), m_eolSequence);
|
||||
object.insert(QStringLiteral("binary"), m_binaryData);
|
||||
object.insert(QStringLiteral("title"), m_title.simplified());
|
||||
return object;
|
||||
}
|
||||
@ -147,6 +159,7 @@ bool JSON::Action::read(const QJsonObject &object)
|
||||
{
|
||||
if (!object.isEmpty())
|
||||
{
|
||||
m_binaryData = SAFE_READ(object, "binary", false).toBool();
|
||||
m_txData = SAFE_READ(object, "txData", "").toString();
|
||||
m_eolSequence = SAFE_READ(object, "eol", "").toString();
|
||||
m_icon = SAFE_READ(object, "icon", "").toString().simplified();
|
||||
|
@ -49,6 +49,8 @@ public:
|
||||
Action(const int actionId = -1);
|
||||
|
||||
[[nodiscard]] int actionId() const;
|
||||
[[nodiscard]] bool binaryData() const;
|
||||
|
||||
[[nodiscard]] const QString &icon() const;
|
||||
[[nodiscard]] const QString &title() const;
|
||||
[[nodiscard]] const QString &txData() const;
|
||||
@ -59,6 +61,8 @@ public:
|
||||
|
||||
private:
|
||||
int m_actionId;
|
||||
bool m_binaryData;
|
||||
|
||||
QString m_icon;
|
||||
QString m_title;
|
||||
QString m_txData;
|
||||
|
@ -95,10 +95,11 @@ typedef enum
|
||||
// clang-format off
|
||||
typedef enum
|
||||
{
|
||||
kActionView_Title, /**< Represents the action title item. */
|
||||
kActionView_Icon, /**< Represents the icon item. */
|
||||
kActionView_EOL, /**< Represents the EOL (end of line) item. */
|
||||
kActionView_Data /**< Represents the TX data item. */
|
||||
kActionView_Title, /**< Represents the action title item. */
|
||||
kActionView_Icon, /**< Represents the icon item. */
|
||||
kActionView_EOL, /**< Represents the EOL (end of line) item. */
|
||||
kActionView_Data, /**< Represents the TX data item. */
|
||||
kActionView_Binary, /**< Represents the binary data transmision status */
|
||||
} ActionItem;
|
||||
// clang-format on
|
||||
|
||||
@ -2267,45 +2268,75 @@ void JSON::ProjectModel::buildActionModel(const JSON::Action &action)
|
||||
icon->setData(tr("Icon to display in the dashboard"), ParameterDescription);
|
||||
m_actionModel->appendRow(icon);
|
||||
|
||||
// Add action data
|
||||
auto data = new QStandardItem();
|
||||
data->setEditable(true);
|
||||
data->setData(TextField, WidgetType);
|
||||
data->setData(action.txData(), EditableValue);
|
||||
data->setData(tr("TX Data"), ParameterName);
|
||||
data->setData(kActionView_Data, ParameterType);
|
||||
data->setData(tr("Command"), PlaceholderValue);
|
||||
data->setData(tr("Data to transmit when the action is triggered."),
|
||||
ParameterDescription);
|
||||
m_actionModel->appendRow(data);
|
||||
// Add binary selector checkbox
|
||||
auto binaryData = new QStandardItem();
|
||||
binaryData->setEditable(true);
|
||||
binaryData->setData(CheckBox, WidgetType);
|
||||
binaryData->setData(action.binaryData(), EditableValue);
|
||||
binaryData->setData(tr("Binary Data"), ParameterName);
|
||||
binaryData->setData(kActionView_Binary, ParameterType);
|
||||
binaryData->setData(0, PlaceholderValue);
|
||||
binaryData->setData(tr("Send binary data when the action is triggered."),
|
||||
ParameterDescription);
|
||||
m_actionModel->appendRow(binaryData);
|
||||
|
||||
// Get appropiate end of line index for current action
|
||||
int eolIndex = 0;
|
||||
bool found = false;
|
||||
for (auto it = m_eolSequences.begin(); it != m_eolSequences.end();
|
||||
++it, ++eolIndex)
|
||||
// Add binary action data
|
||||
if (action.binaryData())
|
||||
{
|
||||
if (it.key() == action.eolSequence())
|
||||
{
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
auto data = new QStandardItem();
|
||||
data->setEditable(true);
|
||||
data->setData(HexTextField, WidgetType);
|
||||
data->setData(action.txData(), EditableValue);
|
||||
data->setData(tr("TX Data (Hex)"), ParameterName);
|
||||
data->setData(kActionView_Data, ParameterType);
|
||||
data->setData(tr("Command"), PlaceholderValue);
|
||||
data->setData(tr("Data to transmit when the action is triggered."),
|
||||
ParameterDescription);
|
||||
m_actionModel->appendRow(data);
|
||||
}
|
||||
|
||||
// If not found, reset the index to 0
|
||||
if (!found)
|
||||
eolIndex = 0;
|
||||
// Add action data (non-binary)
|
||||
else
|
||||
{
|
||||
auto data = new QStandardItem();
|
||||
data->setEditable(true);
|
||||
data->setData(TextField, WidgetType);
|
||||
data->setData(action.txData(), EditableValue);
|
||||
data->setData(tr("TX Data"), ParameterName);
|
||||
data->setData(kActionView_Data, ParameterType);
|
||||
data->setData(tr("Command"), PlaceholderValue);
|
||||
data->setData(tr("Data to transmit when the action is triggered."),
|
||||
ParameterDescription);
|
||||
m_actionModel->appendRow(data);
|
||||
|
||||
// Add EOL combobox
|
||||
auto eol = new QStandardItem();
|
||||
eol->setEditable(true);
|
||||
eol->setData(ComboBox, WidgetType);
|
||||
eol->setData(m_eolSequences.values(), ComboBoxData);
|
||||
eol->setData(eolIndex, EditableValue);
|
||||
eol->setData(tr("EOL Sequence"), ParameterName);
|
||||
eol->setData(kActionView_EOL, ParameterType);
|
||||
eol->setData(tr("End-of-line (EOL) sequence to use"), ParameterDescription);
|
||||
m_actionModel->appendRow(eol);
|
||||
// Get appropiate end of line index for current action
|
||||
int eolIndex = 0;
|
||||
bool found = false;
|
||||
for (auto it = m_eolSequences.begin(); it != m_eolSequences.end();
|
||||
++it, ++eolIndex)
|
||||
{
|
||||
if (it.key() == action.eolSequence())
|
||||
{
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If not found, reset the index to 0
|
||||
if (!found)
|
||||
eolIndex = 0;
|
||||
|
||||
// Add EOL combobox
|
||||
auto eol = new QStandardItem();
|
||||
eol->setEditable(true);
|
||||
eol->setData(ComboBox, WidgetType);
|
||||
eol->setData(m_eolSequences.values(), ComboBoxData);
|
||||
eol->setData(eolIndex, EditableValue);
|
||||
eol->setData(tr("EOL Sequence"), ParameterName);
|
||||
eol->setData(kActionView_EOL, ParameterType);
|
||||
eol->setData(tr("End-of-line (EOL) sequence to use"), ParameterDescription);
|
||||
m_actionModel->appendRow(eol);
|
||||
}
|
||||
|
||||
// Handle edits
|
||||
connect(m_actionModel, &CustomModel::itemChanged, this,
|
||||
@ -2909,6 +2940,10 @@ void JSON::ProjectModel::onActionItemChanged(QStandardItem *item)
|
||||
m_selectedAction.m_icon = value.toString();
|
||||
Q_EMIT actionModelChanged();
|
||||
break;
|
||||
case kActionView_Binary:
|
||||
m_selectedAction.m_binaryData = value.toBool();
|
||||
buildActionModel(m_selectedAction);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
@ -181,6 +181,7 @@ public:
|
||||
enum EditorWidget
|
||||
{
|
||||
TextField,
|
||||
HexTextField,
|
||||
IntField,
|
||||
FloatField,
|
||||
CheckBox,
|
||||
|
@ -23,6 +23,7 @@
|
||||
|
||||
#include "SIMD/SIMD.h"
|
||||
#include "IO/Manager.h"
|
||||
#include "IO/Console.h"
|
||||
#include "CSV/Player.h"
|
||||
#include "MQTT/Client.h"
|
||||
#include "Misc/TimerEvents.h"
|
||||
@ -568,8 +569,14 @@ void UI::Dashboard::activateAction(const int index)
|
||||
if (index >= 0 && index < m_actions.count())
|
||||
{
|
||||
const auto &action = m_actions[index];
|
||||
const auto &data = action.txData() + action.eolSequence();
|
||||
IO::Manager::instance().writeData(data.toUtf8());
|
||||
|
||||
QByteArray bin;
|
||||
if (action.binaryData())
|
||||
bin = IO::Console::hexToBytes(action.txData());
|
||||
else
|
||||
bin = QString(action.txData() + action.eolSequence()).toUtf8();
|
||||
|
||||
IO::Manager::instance().writeData(bin);
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user