Allow sending binary data through actions (@AnnabellePundaky)

This commit is contained in:
Alex Spataru 2025-01-01 15:57:54 -05:00
parent e238d8cdd1
commit 9f53bb6212
8 changed files with 216 additions and 84 deletions

View File

@ -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
//

View File

@ -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.

View File

@ -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);

View File

@ -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();

View File

@ -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;

View File

@ -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;
}

View File

@ -181,6 +181,7 @@ public:
enum EditorWidget
{
TextField,
HexTextField,
IntField,
FloatField,
CheckBox,

View File

@ -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);
}
}