diff --git a/Serial-Studio.pro b/Serial-Studio.pro index 8c5b2a17..f6c74e37 100644 --- a/Serial-Studio.pro +++ b/Serial-Studio.pro @@ -120,8 +120,12 @@ RESOURCES += \ assets/assets.qrc DISTFILES += \ + assets/qml/SetupPanes/Network.qml \ + assets/qml/SetupPanes/Serial.qml \ + assets/qml/SetupPanes/Settings.qml \ assets/qml/Widgets/*.qml \ assets/qml/Windows/*.qml \ + assets/qml/SetupPanes/*.qml \ assets/qml/*.qml TRANSLATIONS += \ diff --git a/assets/assets.qrc b/assets/assets.qrc index dba0617e..dc5bae12 100644 --- a/assets/assets.qrc +++ b/assets/assets.qrc @@ -85,5 +85,8 @@ icons/scroll-top.svg icons/scroll-up.svg qml/Widgets/SimpleDial.qml + qml/SetupPanes/Network.qml + qml/SetupPanes/Serial.qml + qml/SetupPanes/Settings.qml diff --git a/assets/qml/SetupPanes/Network.qml b/assets/qml/SetupPanes/Network.qml new file mode 100644 index 00000000..e399edc5 --- /dev/null +++ b/assets/qml/SetupPanes/Network.qml @@ -0,0 +1,127 @@ +/* + * Copyright (c) 2020-2021 Alex Spataru + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +import QtQuick 2.12 +import QtQuick.Layouts 1.12 +import QtQuick.Controls 2.12 + +Control { + id: root + + // + // Access to properties + // + property alias port: _portText.text + property alias address: _ipText.text + property alias socketType: _typeCombo.currentIndex + + // + // Layout + // + ColumnLayout { + anchors.fill: parent + anchors.margins: app.spacing / 2 + + GridLayout { + columns: 2 + Layout.fillWidth: true + rowSpacing: app.spacing + columnSpacing: app.spacing + + // + // Socket type + // + Label { + opacity: enabled ? 1 : 0.5 + enabled: !Cpp_IO_Manager.connected + Behavior on opacity {NumberAnimation{}} + text: qsTr("Socket type") + ":" + } ComboBox { + id: _typeCombo + Layout.fillWidth: true + model: Cpp_IO_Network.socketTypes + currentIndex: Cpp_IO_Network.socketTypeIndex + onCurrentIndexChanged: { + if (currentIndex !== Cpp_IO_Network.socketTypeIndex) + Cpp_IO_Network.socketTypeIndex = currentIndex + } + + opacity: enabled ? 1 : 0.5 + enabled: !Cpp_IO_Manager.connected + Behavior on opacity {NumberAnimation{}} + } + + // + // Address + // + Label { + opacity: enabled ? 1 : 0.5 + enabled: !Cpp_IO_Manager.connected + Behavior on opacity {NumberAnimation{}} + text: qsTr("IP Address") + ":" + } TextField { + id: _ipText + Layout.fillWidth: true + placeholderText: Cpp_IO_Network.defaultHost + text: Cpp_IO_Network.host + onTextChanged: { + if (Cpp_IO_Network.host !== text) + Cpp_IO_Network.host = text + } + + opacity: enabled ? 1 : 0.5 + enabled: !Cpp_IO_Manager.connected + Behavior on opacity {NumberAnimation{}} + } + + // + // Port + // + Label { + opacity: enabled ? 1 : 0.5 + enabled: !Cpp_IO_Manager.connected + Behavior on opacity {NumberAnimation{}} + text: qsTr("Port") + ":" + } TextField { + id: _portText + Layout.fillWidth: true + text: Cpp_IO_Network.port + placeholderText: Cpp_IO_Network.defaultPort + onTextChanged: { + if (Cpp_IO_Network.port !== text) + Cpp_IO_Network.port = text + } + + opacity: enabled ? 1 : 0.5 + enabled: !Cpp_IO_Manager.connected + Behavior on opacity {NumberAnimation{}} + } + } + + // + // Vertical spacer + // + Item { + Layout.fillHeight: true + } + } +} diff --git a/assets/qml/SetupPanes/Serial.qml b/assets/qml/SetupPanes/Serial.qml new file mode 100644 index 00000000..cef26caa --- /dev/null +++ b/assets/qml/SetupPanes/Serial.qml @@ -0,0 +1,210 @@ +/* + * Copyright (c) 2020-2021 Alex Spataru + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +import QtQuick 2.12 +import QtQuick.Layouts 1.12 +import QtQuick.Controls 2.12 + +Control { + id: root + + // + // Access to properties + // + property alias port: _portCombo.currentIndex + property alias baudRate: _baudCombo.currentIndex + property alias dataBits: _dataCombo.currentIndex + property alias parity: _parityCombo.currentIndex + property alias flowControl: _flowCombo.currentIndex + property alias stopBits: _stopBitsCombo.currentIndex + + // + // Update listbox models when translation is changed + // + Connections { + target: Cpp_Misc_Translator + function onLanguageChanged() { + var oldParityIndex = _parityCombo.currentIndex + var oldFlowControlIndex = _flowCombo.currentIndex + + _parityCombo.model = Cpp_IO_Serial.parityList + _flowCombo.model = Cpp_IO_Serial.flowControlList + + _parityCombo.currentIndex = oldParityIndex + _flowCombo.currentIndex = oldFlowControlIndex + } + } + + // + // Control layout + // + ColumnLayout { + anchors.fill: parent + anchors.margins: app.spacing / 2 + + // + // Controls + // + GridLayout { + columns: 2 + Layout.fillWidth: true + rowSpacing: app.spacing + columnSpacing: app.spacing + + // + // COM port selector + // + Label { + opacity: enabled ? 1 : 0.5 + enabled: !Cpp_IO_Manager.connected + Behavior on opacity {NumberAnimation{}} + text: qsTr("COM Port") + ":" + } ComboBox { + id: _portCombo + Layout.fillWidth: true + model: Cpp_IO_Serial.portList + currentIndex: Cpp_IO_Serial.portIndex + onCurrentIndexChanged: { + if (currentIndex !== Cpp_IO_Serial.portIndex) + Cpp_IO_Serial.portIndex = currentIndex + } + + opacity: enabled ? 1 : 0.5 + enabled: !Cpp_IO_Manager.connected + Behavior on opacity {NumberAnimation{}} + } + + // + // Baud rate selector + // + Label { + opacity: enabled ? 1 : 0.5 + Behavior on opacity {NumberAnimation{}} + text: qsTr("Baud Rate") + ":" + } ComboBox { + id: _baudCombo + editable: true + currentIndex: 4 + Layout.fillWidth: true + model: Cpp_IO_Serial.baudRateList + + validator: IntValidator { + bottom: 1 + } + + onAccepted: { + if (find(editText) === -1) + Cpp_IO_Serial.appendBaudRate(editText) + } + + onCurrentTextChanged: { + var value = currentText + Cpp_IO_Serial.baudRate = value + } + } + + // + // Spacer + // + Item { + Layout.minimumHeight: app.spacing / 2 + Layout.maximumHeight: app.spacing / 2 + } Item { + Layout.minimumHeight: app.spacing / 2 + Layout.maximumHeight: app.spacing / 2 + } + + // + // Data bits selector + // + Label { + text: qsTr("Data Bits") + ":" + } ComboBox { + id: _dataCombo + Layout.fillWidth: true + model: Cpp_IO_Serial.dataBitsList + currentIndex: Cpp_IO_Serial.dataBitsIndex + onCurrentIndexChanged: { + if (Cpp_IO_Serial.dataBitsIndex !== currentIndex) + Cpp_IO_Serial.dataBitsIndex = currentIndex + } + } + + // + // Parity selector + // + Label { + text: qsTr("Parity") + ":" + } ComboBox { + id: _parityCombo + Layout.fillWidth: true + model: Cpp_IO_Serial.parityList + currentIndex: Cpp_IO_Serial.parityIndex + onCurrentIndexChanged: { + if (Cpp_IO_Serial.parityIndex !== currentIndex) + Cpp_IO_Serial.parityIndex = currentIndex + } + } + + // + // Stop bits selector + // + Label { + text: qsTr("Stop Bits") + ":" + } ComboBox { + id: _stopBitsCombo + Layout.fillWidth: true + model: Cpp_IO_Serial.stopBitsList + currentIndex: Cpp_IO_Serial.stopBitsIndex + onCurrentIndexChanged: { + if (Cpp_IO_Serial.stopBitsIndex !== currentIndex) + Cpp_IO_Serial.stopBitsIndex = currentIndex + } + } + + // + // Flow control selector + // + Label { + text: qsTr("Flow Control") + ":" + } ComboBox { + id: _flowCombo + Layout.fillWidth: true + model: Cpp_IO_Serial.flowControlList + currentIndex: Cpp_IO_Serial.flowControlIndex + onCurrentIndexChanged: { + if (Cpp_IO_Serial.flowControlIndex !== currentIndex) + Cpp_IO_Serial.flowControlIndex = currentIndex + } + } + } + + // + // Vertical spacer + // + Item { + Layout.fillHeight: true + } + } +} + + diff --git a/assets/qml/SetupPanes/Settings.qml b/assets/qml/SetupPanes/Settings.qml new file mode 100644 index 00000000..15c463aa --- /dev/null +++ b/assets/qml/SetupPanes/Settings.qml @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2020-2021 Alex Spataru + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +import QtQuick 2.12 +import QtQuick.Layouts 1.12 +import QtQuick.Controls 2.12 + +Control { + id: root + + // + // Access to properties + // + property alias endSequence: _endSequence.text + property alias language: _langCombo.currentIndex + property alias startSequence: _startSequence.text + + // + // Layout + // + ColumnLayout { + anchors.fill: parent + anchors.margins: app.spacing / 2 + + // + // Controls + // + GridLayout { + columns: 2 + Layout.fillWidth: true + rowSpacing: app.spacing + columnSpacing: app.spacing + + // + // Language selector + // + Label { + text: qsTr("Language") + ":" + } ComboBox { + id: _langCombo + Layout.fillWidth: true + model: Cpp_Misc_Translator.availableLanguages + onCurrentIndexChanged: Cpp_Misc_Translator.setLanguage(currentIndex) + } + + // + // Start sequence + // + Label { + text: qsTr("Start sequence") + ": " + } TextField { + id: _startSequence + Layout.fillWidth: true + placeholderText: "/*" + text: "/*" + onTextChanged: { + if (text !== Cpp_IO_Manager.startSequence) + Cpp_IO_Manager.startSequence = text + } + } + + // + // End sequence + // + Label { + text: qsTr("End sequence") + ": " + } TextField { + id: _endSequence + Layout.fillWidth: true + placeholderText: "*/" + text: "*/" + onTextChanged: { + if (text !== Cpp_IO_Manager.finishSequence) + Cpp_IO_Manager.finishSequence = text + } + } + } + + // + // Vertical spacer + // + Item { + Layout.fillHeight: true + } + } +} diff --git a/assets/qml/Windows/Console.qml b/assets/qml/Windows/Console.qml index e086840b..c65b033b 100644 --- a/assets/qml/Windows/Console.qml +++ b/assets/qml/Windows/Console.qml @@ -48,7 +48,6 @@ Control { // Clears console output // function clearConsole() { - console.log("clear console") Cpp_IO_Console.clear() textEdit.clear() } diff --git a/assets/qml/Windows/Setup.qml b/assets/qml/Windows/Setup.qml index 2dc44e49..f6f07902 100644 --- a/assets/qml/Windows/Setup.qml +++ b/assets/qml/Windows/Setup.qml @@ -28,6 +28,7 @@ import Qt.labs.settings 1.0 import QtGraphicalEffects 1.0 import "../Widgets" as Widgets +import "../SetupPanes" as SetupPanes Control { id: root @@ -39,35 +40,36 @@ Control { // Save settings // Settings { - category: "Setup" - property alias dmAuto: commAuto.checked - property alias dmManual: commManual.checked - property alias dmParity: parity.currentIndex - property alias dmCsvExport: csvLogging.checked - property alias dmStopBits: stopBits.currentIndex - property alias dmDataBits: dataBits.currentIndex - property alias dmBaudValue: baudRate.currentIndex - property alias dmFlowControl: flowControl.currentIndex - property alias dmStartSequence: startSeqText.text - property alias dmEndSequence: endSeqText.text - property alias appLanguage: languageCombo.currentIndex - } + // + // Misc settings + // + property alias auto: commAuto.checked + property alias manual: commManual.checked + property alias csvExport: csvLogging.checked + property alias tabIndex: tab.currentIndex - // - // Update listbox models when translation is changed - // - Connections { - target: Cpp_Misc_Translator - function onLanguageChanged() { - var oldParityIndex = parity.currentIndex - var oldFlowControlIndex = flowControl.currentIndex + // + // Serial settings + // + property alias baudRate: serial.baudRate + property alias stopBits: serial.stopBits + property alias parity: serial.parity + property alias flowControl: serial.flowControl + property alias dataBits: serial.dataBits - parity.model = Cpp_IO_Serial.parityList - flowControl.model = Cpp_IO_Serial.flowControlList + // + // Network settings + // + property alias address: network.address + property alias port: network.port + property alias socketType: network.socketType - parity.currentIndex = oldParityIndex - flowControl.currentIndex = oldFlowControlIndex - } + // + // App settings + // + property alias language: settings.language + property alias endSequence: settings.endSequence + property alias startSequence: settings.startSequence } // @@ -175,194 +177,55 @@ Control { } // - // A lot of comboboxes + // Tab bar // - GridLayout { - columns: 2 + TabBar { + height: 24 + id: tab Layout.fillWidth: true - rowSpacing: app.spacing - columnSpacing: app.spacing - - // - // COM port selector - // - Label { - opacity: enabled ? 1 : 0.5 - enabled: !Cpp_IO_Manager.connected - Behavior on opacity {NumberAnimation{}} - text: qsTr("COM Port") + ":" - } ComboBox { - id: portSelector - Layout.fillWidth: true - model: Cpp_IO_Serial.portList - currentIndex: Cpp_IO_Serial.portIndex - onCurrentIndexChanged: { - if (currentIndex !== Cpp_IO_Serial.portIndex) - Cpp_IO_Serial.portIndex = currentIndex - } - - opacity: enabled ? 1 : 0.5 - enabled: !Cpp_IO_Manager.connected - Behavior on opacity {NumberAnimation{}} + onCurrentIndexChanged: { + if (currentIndex < 2) + Cpp_IO_Manager.dataSource = currentIndex } - // - // Baud rate selector - // - Label { - opacity: enabled ? 1 : 0.5 - Behavior on opacity {NumberAnimation{}} - text: qsTr("Baud Rate") + ":" - } ComboBox { - id: baudRate - editable: true - currentIndex: 3 - Layout.fillWidth: true - model: Cpp_IO_Serial.baudRateList - - validator: IntValidator { - bottom: 1 - } - - onAccepted: { - if (find(editText) === -1) - Cpp_IO_Serial.appendBaudRate(editText) - } - - onCurrentTextChanged: { - var value = currentText - Cpp_IO_Serial.baudRate = value - } + TabButton { + text: qsTr("Serial") + height: tab.height + 3 + width: implicitWidth + 2 * app.spacing } - // - // Spacer - // - Item { - Layout.minimumHeight: app.spacing / 2 - Layout.maximumHeight: app.spacing / 2 - } Item { - Layout.minimumHeight: app.spacing / 2 - Layout.maximumHeight: app.spacing / 2 + TabButton { + text: qsTr("Network") + height: tab.height + 3 + width: implicitWidth + 2 * app.spacing } - // - // Data bits selector - // - Label { - text: qsTr("Data Bits") + ":" - } ComboBox { - id: dataBits - Layout.fillWidth: true - model: Cpp_IO_Serial.dataBitsList - currentIndex: Cpp_IO_Serial.dataBitsIndex - onCurrentIndexChanged: { - if (Cpp_IO_Serial.dataBitsIndex !== currentIndex) - Cpp_IO_Serial.dataBitsIndex = currentIndex - } + TabButton { + text: qsTr("Settings") + height: tab.height + 3 + width: implicitWidth + 2 * app.spacing + } + } + + // + // Tab bar contents + // + StackLayout { + clip: true + Layout.fillWidth: true + Layout.fillHeight: true + currentIndex: tab.currentIndex + + SetupPanes.Serial { + id: serial } - // - // Parity selector - // - Label { - text: qsTr("Parity") + ":" - } ComboBox { - id: parity - Layout.fillWidth: true - model: Cpp_IO_Serial.parityList - currentIndex: Cpp_IO_Serial.parityIndex - onCurrentIndexChanged: { - if (Cpp_IO_Serial.parityIndex !== currentIndex) - Cpp_IO_Serial.parityIndex = currentIndex - } + SetupPanes.Network { + id: network } - // - // Stop bits selector - // - Label { - text: qsTr("Stop Bits") + ":" - } ComboBox { - id: stopBits - Layout.fillWidth: true - model: Cpp_IO_Serial.stopBitsList - currentIndex: Cpp_IO_Serial.stopBitsIndex - onCurrentIndexChanged: { - if (Cpp_IO_Serial.stopBitsIndex !== currentIndex) - Cpp_IO_Serial.stopBitsIndex = currentIndex - } - } - - // - // Flow control selector - // - Label { - text: qsTr("Flow Control") + ":" - } ComboBox { - id: flowControl - Layout.fillWidth: true - model: Cpp_IO_Serial.flowControlList - currentIndex: Cpp_IO_Serial.flowControlIndex - onCurrentIndexChanged: { - if (Cpp_IO_Serial.flowControlIndex !== currentIndex) - Cpp_IO_Serial.flowControlIndex = currentIndex - } - } - - // - // Spacer - // - Item { - Layout.minimumHeight: app.spacing / 2 - Layout.maximumHeight: app.spacing / 2 - } Item { - Layout.minimumHeight: app.spacing / 2 - Layout.maximumHeight: app.spacing / 2 - } - - // - // Language selector - // - Label { - text: qsTr("Language") + ":" - } ComboBox { - id: languageCombo - Layout.fillWidth: true - model: Cpp_Misc_Translator.availableLanguages - onCurrentIndexChanged: Cpp_Misc_Translator.setLanguage(currentIndex) - } - - // - // Start sequence - // - Label { - text: qsTr("Start sequence") + ": " - } TextField { - id: startSeqText - Layout.fillWidth: true - placeholderText: "/*" - text: "/*" - onTextChanged: { - if (text !== Cpp_IO_Manager.startSequence) - Cpp_IO_Manager.startSequence = text - } - } - - // - // End sequence - // - Label { - text: qsTr("End sequence") + ": " - } TextField { - id: endSeqText - Layout.fillWidth: true - placeholderText: "*/" - text: "*/" - onTextChanged: { - if (text !== Cpp_IO_Manager.finishSequence) - Cpp_IO_Manager.finishSequence = text - } + SetupPanes.Settings { + id: settings } } @@ -370,8 +233,8 @@ Control { // Spacer // Item { - Layout.fillHeight: true - Layout.minimumHeight: app.spacing * 2 + Layout.minimumHeight: app.spacing / 2 + Layout.maximumHeight: app.spacing / 2 } // @@ -445,8 +308,7 @@ Control { // Spacer // Item { - Layout.fillHeight: true - Layout.minimumHeight: app.spacing * 2 + height: app.spacing * 2 } } } diff --git a/assets/qml/main.qml b/assets/qml/main.qml index 901fffd1..a121ae90 100644 --- a/assets/qml/main.qml +++ b/assets/qml/main.qml @@ -234,7 +234,7 @@ ApplicationWindow { target: Cpp_JSON_Generator enabled: !app.firstValidPacket function onFrameChanged() { - if (Cpp_IO_Manager.connected || Cpp_CSV_Player.isOpen) { + if ((Cpp_IO_Manager.connected || Cpp_CSV_Player.isOpen) && Cpp_JSON_Generator.frameValid()) { app.firstValidPacket = true setup.hide() toolbar.dataClicked() diff --git a/src/IO/Console.cpp b/src/IO/Console.cpp index 168e2d29..1f53f0e2 100644 --- a/src/IO/Console.cpp +++ b/src/IO/Console.cpp @@ -55,6 +55,7 @@ Console::Console() auto dm = Manager::getInstance(); auto te = Misc::TimerEvents::getInstance(); connect(te, SIGNAL(timeout42Hz()), this, SLOT(displayData())); + connect(dm, &Manager::dataSent, this, &Console::onDataSent); connect(dm, &Manager::dataReceived, this, &Console::onDataReceived); // Log something to look like a pro @@ -321,27 +322,7 @@ void Console::send(const QString &data) } // Write data to device - auto bytes = Manager::getInstance()->writeData(bin); - - // Write success, notify UI & log bytes written - if (bytes > 0) - { - // Get sent byte array - auto sent = bin; - sent.chop(bin.length() - bytes); - - // Display sent data on console (if allowed) - if (echo()) - { - m_isStartingLine = true; - append(dataToString(bin), showTimestamp()); - m_isStartingLine = true; - } - } - - // Write error - else - LOG_WARNING() << Manager::getInstance()->device()->errorString(); + Manager::getInstance()->writeData(bin); } /** @@ -484,6 +465,17 @@ void Console::displayData() m_dataBuffer.clear(); } +/** + * Displays the given @a data in the console. @c QByteArray to ~@c QString conversion is + * done by the @c dataToString() function, which displays incoming data either in UTF-8 + * or in hexadecimal mode. + */ +void Console::onDataSent(const QByteArray &data) +{ + if (echo()) + append(dataToString(data) + "\n", showTimestamp()); +} + /** * Adds the given @a data to the incoming data buffer, which is read later by the UI * refresh functions (displayData()) diff --git a/src/IO/Console.h b/src/IO/Console.h index 7134b50d..66bd7982 100644 --- a/src/IO/Console.h +++ b/src/IO/Console.h @@ -133,6 +133,7 @@ public slots: private slots: void displayData(); + void onDataSent(const QByteArray &data); void addToHistory(const QString &command); void onDataReceived(const QByteArray &data); diff --git a/src/IO/DataSources/Network.cpp b/src/IO/DataSources/Network.cpp index 8fab12ba..24b3f020 100644 --- a/src/IO/DataSources/Network.cpp +++ b/src/IO/DataSources/Network.cpp @@ -21,25 +21,34 @@ */ #include "Network.h" +#include using namespace IO::DataSources; - static Network *INSTANCE = nullptr; +/** + * Constructor function + */ Network::Network() { - setPort(0); setHost(""); + setPort(defaultPort()); setSocketType(QAbstractSocket::TcpSocket); connect(&m_tcpSocket, &QTcpSocket::errorOccurred, this, &Network::onErrorOccurred); connect(&m_udpSocket, &QTcpSocket::errorOccurred, this, &Network::onErrorOccurred); } +/** + * Destructor function + */ Network::~Network() { disconnectDevice(); } +/** + * Returns the only instance of this class + */ Network *Network::getInstance() { if (!INSTANCE) @@ -48,26 +57,75 @@ Network *Network::getInstance() return INSTANCE; } +/** + * Returns the host address + */ QString Network::host() const { return m_host; } +/** + * Returns the network port number + */ quint16 Network::port() const { return m_port; } -bool Network::configurationOk() const +/** + * Returns the current socket type as an index of the list returned by the @c socketType + * function. + */ +int Network::socketTypeIndex() const { - return port() >= 0 && !QHostAddress(host()).isNull(); + switch (socketType()) + { + case QAbstractSocket::TcpSocket: + return 0; + break; + case QAbstractSocket::UdpSocket: + return 1; + break; + default: + return -1; + break; + } } +/** + * Returns @c true if the port is greater than 0 and the host address is valid. + */ +bool Network::configurationOk() const +{ + return true; + // return port() > 0 && !QHostAddress(host()).isNull(); +} + +/** + * Returns a list with the available socket types + */ +QStringList Network::socketTypes() const +{ + return QStringList { "TCP", "UDP" }; +} + +/** + * Returns the socket type. Valid return values are: + * + * @c QAbstractSocket::TcpSocket + * @c QAbstractSocket::UdpSocket + * @c QAbstractSocket::SctpSocket + * @c QAbstractSocket::UnknownSocketType + */ QAbstractSocket::SocketType Network::socketType() const { return m_socketType; } +/** + * Attempts to make a connection to the given host, port and TCP/UDP socket type. + */ QIODevice *Network::openNetworkPort() { // Disconnect all sockets @@ -76,59 +134,122 @@ QIODevice *Network::openNetworkPort() // Init socket pointer QAbstractSocket *socket = nullptr; + // Get host & port + auto hostAddr = host(); + auto portAddr = port(); + if (hostAddr.isEmpty()) + hostAddr = defaultHost(); + if (portAddr <= 0) + portAddr = defaultPort(); + // TCP connection, assign socket pointer & connect to host if (socketType() == QAbstractSocket::TcpSocket) { socket = &m_tcpSocket; - m_tcpSocket.connectToHost(host(), port()); + m_tcpSocket.connectToHost(hostAddr, portAddr); } // UDP connection, assign socket pointer & connect to host else if (socketType() == QAbstractSocket::UdpSocket) { socket = &m_udpSocket; - m_udpSocket.connectToHost(host(), port()); + m_udpSocket.connectToHost(hostAddr, portAddr); } // Convert socket to IO device pointer return static_cast(socket); } +/** + * Instructs the module to communicate via a TCP socket. + */ void Network::setTcpSocket() { setSocketType(QAbstractSocket::TcpSocket); } +/** + * Instructs the module to communicate via an UDP socket. + */ void Network::setUdpSocket() { setSocketType(QAbstractSocket::UdpSocket); } +/** + * Disconnects the TCP/UDP sockets from the host + */ void Network::disconnectDevice() { m_tcpSocket.disconnectFromHost(); m_udpSocket.disconnectFromHost(); } +/** + * Sets the @c port number + */ void Network::setPort(const quint16 port) { m_port = port; emit portChanged(); } +/** + * Sets the IPv4 or IPv6 address specified by the input string representation + */ void Network::setHost(const QString &host) { m_host = host; emit hostChanged(); } +/** + * Changes the current socket type given an index of the list returned by the + * @c socketType() function. + */ +void Network::setSocketTypeIndex(const int index) +{ + switch (index) + { + case 0: + setTcpSocket(); + break; + case 1: + setUdpSocket(); + break; + default: + break; + } +} + +/** + * Changes the socket type. Valid input values are: + * + * @c QAbstractSocket::TcpSocket + * @c QAbstractSocket::UdpSocket + * @c QAbstractSocket::SctpSocket + * @c QAbstractSocket::UnknownSocketType + */ void Network::setSocketType(const QAbstractSocket::SocketType type) { m_socketType = type; emit socketTypeChanged(); } +/** + * This function is called whenever a socket error occurs, it disconnects the socket + * from the host and displays the error in a message box. + */ void Network::onErrorOccurred(const QAbstractSocket::SocketError socketError) { - qDebug() << socketError; + QString error; + if (socketType() == QAbstractSocket::TcpSocket) + error = m_tcpSocket.errorString(); + else if (socketType() == QAbstractSocket::UdpSocket) + error = m_udpSocket.errorString(); + else + error = QString::number(socketError); + + Misc::Utilities::showMessageBox(tr("Socket error"), error); + disconnectDevice(); } diff --git a/src/IO/DataSources/Network.h b/src/IO/DataSources/Network.h index 14727cee..f6ebea50 100644 --- a/src/IO/DataSources/Network.h +++ b/src/IO/DataSources/Network.h @@ -1,23 +1,10 @@ /* - * Copyright (c) 2020-2021 Alex Spataru + * Copyright (C) MATUSICA S.A. de C.V. - All Rights Reserved * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: + * Unauthorized copying of this file, via any medium is strictly prohibited. + * Proprietary and confidential. * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. + * Written by Alex Spataru , February 2021 */ #ifndef IO_DATA_SOURCES_NETWORK_H @@ -49,6 +36,19 @@ class Network : public QObject READ socketType WRITE setSocketType NOTIFY socketTypeChanged) + Q_PROPERTY(int socketTypeIndex + READ socketTypeIndex + WRITE setSocketTypeIndex + NOTIFY socketTypeChanged) + Q_PROPERTY(QStringList socketTypes + READ socketTypes + CONSTANT) + Q_PROPERTY(QString defaultHost + READ defaultHost + CONSTANT) + Q_PROPERTY(quint16 defaultPort + READ defaultPort + CONSTANT) // clang-format on signals: @@ -61,9 +61,15 @@ public: QString host() const; quint16 port() const; + int socketTypeIndex() const; bool configurationOk() const; + QStringList socketTypes() const; QAbstractSocket::SocketType socketType() const; + static QString defaultHost() { return "127.0.0.1"; } + + static quint16 defaultPort() { return 23; } + QIODevice *openNetworkPort(); public slots: @@ -72,6 +78,7 @@ public slots: void disconnectDevice(); void setPort(const quint16 port); void setHost(const QString &host); + void setSocketTypeIndex(const int index); void setSocketType(const QAbstractSocket::SocketType type); private slots: diff --git a/src/IO/Manager.cpp b/src/IO/Manager.cpp index f4245da3..9c326d02 100644 --- a/src/IO/Manager.cpp +++ b/src/IO/Manager.cpp @@ -271,17 +271,23 @@ QStringList Manager::dataSourcesList() const */ qint64 Manager::writeData(const QByteArray &data) { - // Write data to device - qint64 bytes = 0; - if (readWrite()) - bytes = device()->write(data); + if (connected()) + { + qint64 bytes = device()->write(data); - // Flash UI lights (if any) - if (bytes > 0) - emit tx(); + if (bytes > 0) + { + auto writtenData = data; + writtenData.chop(data.length() - bytes); - // Return number of bytes written - return bytes; + emit tx(); + emit dataSent(writtenData); + } + + return bytes; + } + + return -1; } /** @@ -392,6 +398,9 @@ void Manager::setDataSource(const DataSource source) // Change data source m_dataSource = source; emit dataSourceChanged(); + + // Log changes + LOG_INFO() << "Data source set to" << source; } /** diff --git a/src/IO/Manager.h b/src/IO/Manager.h index f2f15211..9077501c 100644 --- a/src/IO/Manager.h +++ b/src/IO/Manager.h @@ -80,6 +80,7 @@ signals: void finishSequenceChanged(); void watchdogIntervalChanged(); void frameValidationRegexChanged(); + void dataSent(const QByteArray &data); void dataReceived(const QByteArray &data); void frameReceived(const QByteArray &frame); diff --git a/src/JSON/Generator.h b/src/JSON/Generator.h index 6737a8e5..06fba6e1 100644 --- a/src/JSON/Generator.h +++ b/src/JSON/Generator.h @@ -100,6 +100,14 @@ public: QString jsonMapFilepath() const; OperationMode operationMode() const; + Q_INVOKABLE bool frameValid() + { + if (frame()) + return frame()->groupCount() > 0; + + return false; + } + public slots: void loadJsonMap(); void setOperationMode(const OperationMode mode);