Add network controls

This commit is contained in:
Alex Spataru 2021-02-15 16:35:04 -05:00
parent 3a5d664ad1
commit a9ad58b62e
15 changed files with 711 additions and 262 deletions

View File

@ -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 += \

View File

@ -85,5 +85,8 @@
<file>icons/scroll-top.svg</file>
<file>icons/scroll-up.svg</file>
<file>qml/Widgets/SimpleDial.qml</file>
<file>qml/SetupPanes/Network.qml</file>
<file>qml/SetupPanes/Serial.qml</file>
<file>qml/SetupPanes/Settings.qml</file>
</qresource>
</RCC>

View File

@ -0,0 +1,127 @@
/*
* Copyright (c) 2020-2021 Alex Spataru <https://github.com/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
}
}
}

View File

@ -0,0 +1,210 @@
/*
* Copyright (c) 2020-2021 Alex Spataru <https://github.com/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
}
}
}

View File

@ -0,0 +1,105 @@
/*
* Copyright (c) 2020-2021 Alex Spataru <https://github.com/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
}
}
}

View File

@ -48,7 +48,6 @@ Control {
// Clears console output
//
function clearConsole() {
console.log("clear console")
Cpp_IO_Console.clear()
textEdit.clear()
}

View File

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

View File

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

View File

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

View File

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

View File

@ -21,25 +21,34 @@
*/
#include "Network.h"
#include <Misc/Utilities.h>
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<QIODevice *>(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();
}

View File

@ -1,23 +1,10 @@
/*
* Copyright (c) 2020-2021 Alex Spataru <https://github.com/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 <https://alex-spataru.com/>, 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:

View File

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

View File

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

View File

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