aboutsummaryrefslogtreecommitdiffstats
path: root/src/plugins/serialterminal/serialcontrol.cpp
blob: 9452fa721e63c135e89f599e7a2d3a8ed5a199ba (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
// Copyright (C) 2018 Benjamin Balga
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0

#include "serialcontrol.h"
#include "serialterminalconstants.h"

#include <utils/outputformatter.h>

namespace SerialTerminal {
namespace Internal {

SerialControl::SerialControl(const Settings &settings, QObject *parent) :
    QObject(parent)
{
    m_serialPort.setBaudRate(settings.baudRate);
    m_serialPort.setDataBits(settings.dataBits);
    m_serialPort.setParity(settings.parity);
    m_serialPort.setStopBits(settings.stopBits);
    m_serialPort.setFlowControl(settings.flowControl);

    if (!settings.portName.isEmpty())
        m_serialPort.setPortName(settings.portName);

    m_initialDtrState = settings.initialDtrState;
    m_initialRtsState = settings.initialRtsState;
    m_clearInputOnSend = settings.clearInputOnSend;

    m_reconnectTimer.setInterval(Constants::RECONNECT_DELAY);
    m_reconnectTimer.setSingleShot(true);

    connect(&m_serialPort, &QSerialPort::readyRead,
            this, &SerialControl::handleReadyRead);
    connect(&m_serialPort, &QSerialPort::errorOccurred,
            this, &SerialControl::handleError);
    connect(&m_reconnectTimer, &QTimer::timeout,
            this, &SerialControl::reconnectTimeout);
}

bool SerialControl::start()
{
    stop();

    if (!m_serialPort.open(QIODevice::ReadWrite)) {
        if (!m_retrying) {
            appendMessage(tr("Unable to open port %1: %2.")
                          .arg(portName(), m_serialPort.errorString()),
                          Utils::ErrorMessageFormat);
        }
        return false;
    }

    m_serialPort.setDataTerminalReady(m_initialDtrState);
    m_serialPort.setRequestToSend(m_initialRtsState);

    if (m_retrying)
        appendMessage(tr("Session resumed.") + QString("\n\n"), Utils::NormalMessageFormat);
    else
        appendMessage(tr("Starting new session on %1...").arg(portName()) + "\n", Utils::NormalMessageFormat);

    m_retrying = false;

    m_running = true;
    emit started();
    emit runningChanged(true);
    return true;
}

void SerialControl::stop(bool force)
{
    if (force) {
        // Stop retries
        m_reconnectTimer.stop();
        m_retrying = false;
    }

    // Close if opened
    if (m_serialPort.isOpen())
        m_serialPort.close();

    // Print paused or finished message
    if (force || (m_running && !m_retrying)) {
        appendMessage(QString("\n")
                      + tr("Session finished on %1.").arg(portName())
                      + QString("\n\n"),
                      Utils::NormalMessageFormat);

        m_running = false;
        emit finished();
        emit runningChanged(false);
    } else if (m_running && m_retrying) {
        appendMessage(QString("\n")
                      + tr("Session paused...")
                      + QString("\n"),
                      Utils::NormalMessageFormat);
        m_running = false;
        // MAYBE: send paused() signals?
    }
}

bool SerialControl::isRunning() const
{
    // Considered "running" if "paused" (i.e. trying to reconnect)
    return m_running || m_retrying;
}

QString SerialControl::displayName() const
{
    return portName().isEmpty() ? tr("No Port") : portName();
}

bool SerialControl::canReUseOutputPane(const SerialControl *other) const
{
    return other->portName() == portName();
}

void SerialControl::appendMessage(const QString &msg, Utils::OutputFormat format)
{
    emit appendMessageRequested(this, msg, format);
}

QString SerialControl::portName() const
{
    return m_serialPort.portName();
}

void SerialControl::setPortName(const QString &name)
{
    if (m_serialPort.portName() == name)
        return;
    m_serialPort.setPortName(name);
}

qint32 SerialControl::baudRate() const
{
    return m_serialPort.baudRate();
}

void SerialControl::setBaudRate(qint32 baudRate)
{
    if (m_serialPort.baudRate() == baudRate)
        return;
    m_serialPort.setBaudRate(baudRate);
}

QString SerialControl::baudRateText() const
{
    return QString::number(baudRate());
}

void SerialControl::pulseDataTerminalReady()
{
    m_serialPort.setDataTerminalReady(!m_initialDtrState);
    QTimer::singleShot(Constants::RESET_DELAY, [&]() {
        m_serialPort.setDataTerminalReady(m_initialDtrState);
    });
}

qint64 SerialControl::writeData(const QByteArray& data)
{
    return m_serialPort.write(data);
}

void SerialControl::handleReadyRead()
{
    const QByteArray ba = m_serialPort.readAll();
    // For now, UTF8 should be safe for most use cases
    appendMessage(QString::fromUtf8(ba), Utils::StdOutFormat);
    // TODO: add config for string format conversion
}

void SerialControl::reconnectTimeout()
{
    // No port name set, stop reconnecting
    if (portName().isEmpty()) {
        m_retrying = false;
        return;
    }

    // Try to reconnect, restart timer if failed
    if (start())
        m_retrying = false;
    else
        m_reconnectTimer.start();
}

void SerialControl::handleError(QSerialPort::SerialPortError error)
{
    if (!isRunning()) // No auto reconnect if not running
        return;

    if (!m_retrying && error != QSerialPort::NoError)
        appendMessage(QString("\n")
                      + tr("Serial port error: %1 (%2)").arg(m_serialPort.errorString()).arg(error)
                      + QString("\n"),
                      Utils::ErrorMessageFormat);

    // Activate auto-reconnect on some resource errors
    // TODO: add auto-reconnect option to settings
    switch (error) {
    case QSerialPort::OpenError:
    case QSerialPort::DeviceNotFoundError:
    case QSerialPort::WriteError:
    case QSerialPort::ReadError:
    case QSerialPort::ResourceError:
    case QSerialPort::UnsupportedOperationError:
    case QSerialPort::UnknownError:
    case QSerialPort::TimeoutError:
    case QSerialPort::NotOpenError:
        // Enable auto-reconnect if needed
        if (!m_reconnectTimer.isActive() && !portName().isEmpty()) {
            m_retrying = true;
            m_reconnectTimer.start();
        }
        break;

    default:
        break;
    }
}

} // namespace Internal
} // namespace SerialTerminal