You are a senior Qt C++ and Embedded Systems engin...
作成日: 2026年7月28日
使用モデル GPT-5.6 Thinking by Chat01
作成日: 2026年7月28日
使用モデル GPT-5.6 Thinking by Chat01
You are a senior Qt C++ and Embedded Systems engineer with experience building commercial desktop applications and embedded firmware.
Your task is to create a production-quality communication library between a Qt Desktop application and an ESP32-S3.
The implementation must be clean, reliable, asynchronous, and easily extensible.
Generate ONLY:
No explanations.
No markdown.
No extra text.
This is production code.
Design it as if it will be used inside a commercial application.
The code must be:
Do not create unnecessary architecture.
Do NOT create:
Everything should stay inside Hardware_Control.
Use only Qt built-in libraries.
Use:
QObject
QSerialPort
QSerialPortInfo
Signals & Slots
Use readyRead().
Never poll.
Never use while(true).
Never use sleep().
Never freeze the UI.
Do not use threads unless absolutely necessary.
The user should NEVER specify a COM port.
There must be only:
hardware.connect();
Hardware_Control automatically scans every available serial port.
For every detected port it should:
Open the port.
Send a binary handshake packet.
Wait a short timeout for a reply.
If the reply is correct:
The library should automatically find the ESP32-S3.
No manual COM selection.
Design a robust binary handshake.
Example:
PC sends:
CMD_HANDSHAKE
ESP replies:
MAGIC_NUMBER
Choose your own protocol if better.
The handshake must reject:
Arduino
Bluetooth COM ports
Unknown USB Serial devices
Any other serial device.
Only connect if the correct binary response is received.
If the ESP32 disconnects:
Automatically detect it.
Emit disconnected().
Automatically begin searching again until the device reconnects.
No user interaction required.
The application should recover automatically after unplugging and reconnecting USB.
class Hardware_Control : public QObject
Functions:
connect();
disconnect();
isConnected();
led1(bool on);
led2(bool on);
requestTemperature();
Signals:
connected();
disconnected();
packetReceived(QByteArray packet);
communicationError(QString message);
The class should hide every serial implementation.
The application should never access QSerialPort directly.
Use a compact binary protocol.
No ASCII.
No JSON.
No CSV.
No text.
Use packet framing.
Include:
Start byte
Command
Length
Payload
Checksum or CRC
End byte if needed.
Packets must survive corrupted bytes.
Parser must recover automatically after invalid packets.
Future commands should be easy to add.
Generate a production-quality Arduino sketch.
The firmware must:
Never block.
Never use delay().
Never interfere with future tasks.
Be event-driven.
Handle serial communication continuously.
Use a packet parser.
Validate checksum.
Ignore invalid packets safely.
Implement:
Handshake
LED1
LED2
Temperature request
LED1:
GPIO 5
LED2:
GPIO 6
Temperature:
Read analog pin.
Scale result to uint8 (0-255).
When requestTemperature() is received:
Immediately return the current value.
Use enums for command IDs.
Use constexpr where appropriate.
Avoid magic numbers.
Avoid duplicated code.
Use private helper functions.
Protect against:
Partial packets
Corrupted packets
Unexpected bytes
Serial disconnects
Reconnects
Buffer overflow
Invalid packet length
Checksum failures
The parser must always recover correctly after invalid data.
Hardware_Control hardware;
connect(&hardware,
&Hardware_Control::connected,
{
qDebug() << "ESP32 Connected";
});
connect(&hardware,
&Hardware_Control::disconnected,
{
qDebug() << "ESP32 Disconnected";
});
connect(&hardware,
&Hardware_Control::packetReceived,
this,
&Widget::read_esp32);
hardware.connect();
hardware.led1(true);
hardware.led2(false);
hardware.requestTemperature();
Example slot:
void Widget::read_esp32(const QByteArray &packet)
{
qDebug() << packet;
}
Keep the implementation simple, compact, production-ready, and easily extensible for future commands like RGB, Servo, Fan, Pump, Relays, PWM, Sensors, etc., without changing the core communication architecture.
// Hardware_Control.h
#pragma once
#include <QObject> #include <QByteArray> #include <QList> #include <QSerialPortInfo> #include <QString>
#include <atomic> #include <optional>
class QSerialPort; class QTimer;
class Hardware_Control final : public QObject { Q_OBJECT Q_DISABLE_COPY(Hardware_Control)
public: explicit Hardware_Control(QObject *parent = nullptr); ~Hardware_Control() override;
textvoid connect(); void disconnect(); [[nodiscard]] bool isConnected() const noexcept; void led1(bool on); void led2(bool on); void requestTemperature();
signals: void connected(); void disconnected(); void packetReceived(QByteArray packet); void communicationError(QString message);
private slots: void onReadyRead(); void onBytesWritten(qint64 bytes); void onSerialError(int error); void onHandshakeTimeout(); void onRescanTimeout(); void onFrameTimeout();
private: enum class State : quint8 { Idle, Scanning, Handshaking, Connected };
textenum class Command : quint8 { HandshakeRequest = 0x01, SetLed1 = 0x10, SetLed2 = 0x11, TemperatureRequest = 0x20, HandshakeResponse = 0x81, TemperatureResponse = 0xA0 }; static constexpr quint8 kStartByte = 0xA5; static constexpr quint8 kEndByte = 0x5A; static constexpr quint8 kProtocolVersion = 0x01; static constexpr qint32 kBaudRate = 115200; static constexpr int kMaximumPayloadSize = 256; static constexpr int kMaximumRxBufferSize = 4096; static constexpr int kMaximumTxBufferSize = 8192; static constexpr int kMaximumFrameSize = kMaximumPayloadSize + 9; static constexpr int kHandshakeTimeoutMs = 450; static constexpr int kHandshakeMaximumAttempts = 3; static constexpr int kRescanIntervalMs = 1000; static constexpr int kReconnectDelayMs = 300; static constexpr int kFrameAssemblyTimeoutMs = 300; static constexpr qint64 kWriteHighWaterMark = 4096; static constexpr qint64 kWriteChunkSize = 1024; void connectImpl(); void disconnectImpl(); void led1Impl(bool on); void led2Impl(bool on); void requestTemperatureImpl(); void beginScanCycle(); void tryNextPort(); void advanceToNextPort(); void scheduleRescan(int delayMs); void closeCurrentPort(); void sendHandshakeAttempt(); void handleReceivedPacket(quint8 command, quint8 sequence, const QByteArray &payload); void handleConnectionLost(); void flushDesiredState(); bool sendCommand(Command command, const QByteArray &payload = {}); bool enqueuePacket(Command command, quint8 sequence, const QByteArray &payload); void flushTx(); QByteArray buildFrame(Command command, quint8 sequence, const QByteArray &payload) const; void parseIncoming(); quint8 nextSequence(); static quint16 crc16Ccitt(const char *data, int size); static quint16 updateCrc16Ccitt(quint16 crc, quint8 byte); static bool isFatalSerialError(int error); QSerialPort *m_serial{nullptr}; QTimer *m_handshakeTimer{nullptr}; QTimer *m_rescanTimer{nullptr}; QTimer *m_frameTimer{nullptr}; State m_state{State::Idle}; bool m_searchEnabled{false}; QList<QSerialPortInfo> m_candidates; int m_candidateIndex{0}; QString m_currentPortName; QByteArray m_rxBuffer; QByteArray m_txBuffer; QByteArray m_handshakeNonce; quint8 m_handshakeSequence{0}; int m_handshakeAttempts{0}; quint8 m_nextSequence{1}; std::optional<bool> m_desiredLed1; std::optional<bool> m_desiredLed2; bool m_temperaturePending{false}; std::atomic_bool m_connected{false};
};
// Hardware_Control.cpp
#include "Hardware_Control.h"
#include <QMetaObject> #include <QRandomGenerator> #include <QSerialPort> #include <QThread> #include <QTimer>
#include <algorithm>
Hardware_Control::Hardware_Control(QObject *parent) : QObject(parent), m_serial(new QSerialPort(this)), m_handshakeTimer(new QTimer(this)), m_rescanTimer(new QTimer(this)), m_frameTimer(new QTimer(this)) { m_handshakeTimer->setSingleShot(true); m_rescanTimer->setSingleShot(true); m_frameTimer->setSingleShot(true);
textm_serial->setReadBufferSize(kMaximumRxBufferSize); QObject::connect(m_serial, &QSerialPort::readyRead, this, &Hardware_Control::onReadyRead); QObject::connect(m_serial, &QSerialPort::bytesWritten, this, &Hardware_Control::onBytesWritten); QObject::connect( m_serial, &QSerialPort::errorOccurred, this, [this](QSerialPort::SerialPortError error) { onSerialError(static_cast<int>(error)); }); QObject::connect(m_handshakeTimer, &QTimer::timeout, this, &Hardware_Control::onHandshakeTimeout); QObject::connect(m_rescanTimer, &QTimer::timeout, this, &Hardware_Control::onRescanTimeout); QObject::connect(m_frameTimer, &QTimer::timeout, this, &Hardware_Control::onFrameTimeout);
}
Hardware_Control::~Hardware_Control() { m_searchEnabled = false; m_connected.store(false, std::memory_order_release);
textm_handshakeTimer->stop(); m_rescanTimer->stop(); m_frameTimer->stop(); m_serial->blockSignals(true); if (m_serial->isOpen()) m_serial->close();
}
void Hardware_Control::connect() { if (QThread::currentThread() != thread()) { QMetaObject::invokeMethod( this, this { connectImpl(); }, Qt::QueuedConnection); return; }
connectImpl();
}
void Hardware_Control::disconnect() { if (QThread::currentThread() != thread()) { QMetaObject::invokeMethod( this, this { disconnectImpl(); }, Qt::QueuedConnection); return; }
disconnectImpl();
}
bool Hardware_Control::isConnected() const noexcept { return m_connected.load(std::memory_order_acquire); }
void Hardware_Control::led1(bool on) { if (QThread::currentThread() != thread()) { QMetaObject::invokeMethod( this, this, on { led1Impl(on); }, Qt::QueuedConnection); return; }
led1Impl(on);
}
void Hardware_Control::led2(bool on) { if (QThread::currentThread() != thread()) { QMetaObject::invokeMethod( this, this, on { led2Impl(on); }, Qt::QueuedConnection); return; }
led2Impl(on);
}
void Hardware_Control::requestTemperature() { if (QThread::currentThread() != thread()) { QMetaObject::invokeMethod( this, this { requestTemperatureImpl(); }, Qt::QueuedConnection); return; }
requestTemperatureImpl();
}
void Hardware_Control::connectImpl() { if (m_searchEnabled) return;
textm_searchEnabled = true; m_state = State::Scanning; m_rescanTimer->stop(); QTimer::singleShot(0, this, &Hardware_Control::beginScanCycle);
}
void Hardware_Control::disconnectImpl() { const bool wasConnected = m_connected.exchange(false, std::memory_order_acq_rel);
textm_searchEnabled = false; m_state = State::Idle; m_handshakeTimer->stop(); m_rescanTimer->stop(); m_frameTimer->stop(); m_candidates.clear(); m_candidateIndex = 0; closeCurrentPort(); if (wasConnected) emit disconnected();
}
void Hardware_Control::led1Impl(bool on) { m_desiredLed1 = on;
textif (!isConnected()) return; const QByteArray payload(1, on ? char(1) : char(0)); sendCommand(Command::SetLed1, payload);
}
void Hardware_Control::led2Impl(bool on) { m_desiredLed2 = on;
textif (!isConnected()) return; const QByteArray payload(1, on ? char(1) : char(0)); sendCommand(Command::SetLed2, payload);
}
void Hardware_Control::requestTemperatureImpl() { if (!isConnected()) { m_temperaturePending = true; return; }
sendCommand(Command::TemperatureRequest);
}
void Hardware_Control::beginScanCycle() { if (!m_searchEnabled || isConnected()) return;
textm_state = State::Scanning; closeCurrentPort(); m_candidates = QSerialPortInfo::availablePorts(); m_candidateIndex = 0; tryNextPort();
}
void Hardware_Control::tryNextPort() { if (!m_searchEnabled || isConnected()) return;
textif (m_candidateIndex >= m_candidates.size()) { scheduleRescan(kRescanIntervalMs); return; } const QSerialPortInfo portInfo = m_candidates.at(m_candidateIndex++); closeCurrentPort(); m_currentPortName = portInfo.portName(); m_serial->setPort(portInfo); m_serial->setBaudRate(kBaudRate); m_serial->setDataBits(QSerialPort::Data8); m_serial->setParity(QSerialPort::NoParity); m_serial->setStopBits(QSerialPort::OneStop); m_serial->setFlowControl(QSerialPort::NoFlowControl); m_serial->clearError(); if (!m_serial->open(QIODevice::ReadWrite)) { QTimer::singleShot(0, this, &Hardware_Control::tryNextPort); return; } m_serial->clear(QSerialPort::AllDirections); m_serial->clearError(); m_rxBuffer.clear(); m_txBuffer.clear(); m_handshakeNonce.resize(8); for (int offset = 0; offset < m_handshakeNonce.size(); offset += 4) { const quint32 randomValue = QRandomGenerator::global()->generate(); m_handshakeNonce[offset] = static_cast<char>(randomValue & 0xFFU); m_handshakeNonce[offset + 1] = static_cast<char>((randomValue >> 8U) & 0xFFU); m_handshakeNonce[offset + 2] = static_cast<char>((randomValue >> 16U) & 0xFFU); m_handshakeNonce[offset + 3] = static_cast<char>((randomValue >> 24U) & 0xFFU); } m_handshakeSequence = nextSequence(); m_handshakeAttempts = 0; m_state = State::Handshaking; sendHandshakeAttempt();
}
void Hardware_Control::advanceToNextPort() { if (!m_searchEnabled || isConnected()) return;
textm_state = State::Scanning; closeCurrentPort(); QTimer::singleShot(0, this, &Hardware_Control::tryNextPort);
}
void Hardware_Control::scheduleRescan(int delayMs) { if (!m_searchEnabled || isConnected()) return;
textm_state = State::Scanning; m_rescanTimer->start(delayMs);
}
void Hardware_Control::closeCurrentPort() { m_handshakeTimer->stop(); m_frameTimer->stop();
textm_rxBuffer.clear(); m_txBuffer.clear(); if (m_serial->isOpen()) m_serial->close(); m_serial->clearError(); m_currentPortName.clear();
}
void Hardware_Control::sendHandshakeAttempt() { if (m_state != State::Handshaking || !m_serial->isOpen()) { advanceToNextPort(); return; }
textQByteArray payload; payload.reserve(13); payload.append(m_handshakeNonce); payload.append("QTHW", 4); payload.append(static_cast<char>(kProtocolVersion)); ++m_handshakeAttempts; if (!enqueuePacket(Command::HandshakeRequest, m_handshakeSequence, payload)) { advanceToNextPort(); return; } m_handshakeTimer->start(kHandshakeTimeoutMs);
}
void Hardware_Control::onHandshakeTimeout() { if (m_state != State::Handshaking) return;
textif (m_handshakeAttempts < kHandshakeMaximumAttempts) { sendHandshakeAttempt(); return; } advanceToNextPort();
}
void Hardware_Control::onRescanTimeout() { beginScanCycle(); }
void Hardware_Control::onFrameTimeout() { if (m_rxBuffer.isEmpty()) return;
textm_rxBuffer.remove(0, 1); parseIncoming();
}
void Hardware_Control::onReadyRead() { const QByteArray received = m_serial->readAll();
textif (received.isEmpty()) return; m_frameTimer->stop(); m_rxBuffer.append(received); if (m_rxBuffer.size() > kMaximumRxBufferSize) { const int lastStart = m_rxBuffer.lastIndexOf(static_cast<char>(kStartByte)); if (lastStart >= 0 && (m_rxBuffer.size() - lastStart) <= kMaximumFrameSize) { m_rxBuffer = m_rxBuffer.mid(lastStart); } else { m_rxBuffer.clear(); } if (m_state == State::Connected) { emit communicationError( QStringLiteral( "Receive buffer overflow. The serial stream was resynchronized.")); } } parseIncoming();
}
void Hardware_Control::parseIncoming() { constexpr int minimumFrameSize = 9;
textfor (;;) { if (m_rxBuffer.isEmpty()) { m_frameTimer->stop(); return; } const int startIndex = m_rxBuffer.indexOf(static_cast<char>(kStartByte)); if (startIndex < 0) { m_rxBuffer.clear(); m_frameTimer->stop(); return; } if (startIndex > 0) m_rxBuffer.remove(0, startIndex); if (m_rxBuffer.size() < 6) { m_frameTimer->start(kFrameAssemblyTimeoutMs); return; } const auto byteAt = [this](int index) -> quint8 { return static_cast<quint8>( static_cast<unsigned char>(m_rxBuffer.at(index))); }; if (byteAt(1) != kProtocolVersion) { m_rxBuffer.remove(0, 1); continue; } const quint8 command = byteAt(2); const quint8 sequence = byteAt(3); const quint16 payloadLength = static_cast<quint16>(byteAt(4)) | static_cast<quint16>( static_cast<quint16>(byteAt(5)) << 8U); if (payloadLength > kMaximumPayloadSize) { m_rxBuffer.remove(0, 1); continue; } const int frameSize = minimumFrameSize + static_cast<int>(payloadLength); if (m_rxBuffer.size() < frameSize) { m_frameTimer->start(kFrameAssemblyTimeoutMs); return; } if (byteAt(frameSize - 1) != kEndByte) { m_rxBuffer.remove(0, 1); continue; } const int crcIndex = 6 + payloadLength; const quint16 receivedCrc = static_cast<quint16>(byteAt(crcIndex)) | static_cast<quint16>( static_cast<quint16>(byteAt(crcIndex + 1)) << 8U); const quint16 calculatedCrc = crc16Ccitt(m_rxBuffer.constData() + 1, 5 + payloadLength); if (receivedCrc != calculatedCrc) { m_rxBuffer.remove(0, 1); continue; } const QByteArray payload = m_rxBuffer.mid(6, payloadLength); m_rxBuffer.remove(0, frameSize); m_frameTimer->stop(); handleReceivedPacket(command, sequence, payload); }
}
void Hardware_Control::handleReceivedPacket( quint8 command, quint8 sequence, const QByteArray &payload) { if (m_state == State::Handshaking && command == static_cast<quint8>(Command::HandshakeResponse)) { if (sequence != m_handshakeSequence) return;
textif (payload.size() != 13) return; if (payload.left(8) != m_handshakeNonce) return; if (payload.mid(8, 4) != QByteArrayLiteral("ES32")) return; const quint8 deviceProtocolVersion = static_cast<quint8>( static_cast<unsigned char>(payload.at(12))); if (deviceProtocolVersion != kProtocolVersion) return; m_handshakeTimer->stop(); m_serial->clearError(); m_state = State::Connected; m_connected.store(true, std::memory_order_release); emit connected(); flushDesiredState(); return; } if (m_state != State::Connected) return; if (command == static_cast<quint8>(Command::HandshakeResponse)) { return; } QByteArray packet; packet.reserve(payload.size() + 1); packet.append(static_cast<char>(command)); packet.append(payload); emit packetReceived(packet);
}
void Hardware_Control::flushDesiredState() { if (!isConnected()) return;
textif (m_desiredLed1.has_value()) { const QByteArray payload( 1, m_desiredLed1.value() ? char(1) : char(0)); if (!sendCommand(Command::SetLed1, payload)) return; } if (m_desiredLed2.has_value()) { const QByteArray payload( 1, m_desiredLed2.value() ? char(1) : char(0)); if (!sendCommand(Command::SetLed2, payload)) return; } if (m_temperaturePending) { if (sendCommand(Command::TemperatureRequest)) m_temperaturePending = false; }
}
bool Hardware_Control::sendCommand( Command command, const QByteArray &payload) { if (!isConnected()) return false;
textif (!m_serial->isOpen()) { emit communicationError( QStringLiteral("The connected serial port is no longer open.")); handleConnectionLost(); return false; } return enqueuePacket(command, nextSequence(), payload);
}
bool Hardware_Control::enqueuePacket( Command command, quint8 sequence, const QByteArray &payload) { if (!m_serial->isOpen()) return false;
textif (payload.size() > kMaximumPayloadSize) { if (m_state == State::Connected) { emit communicationError( QStringLiteral("The outgoing packet payload is too large.")); } return false; } const QByteArray frame = buildFrame(command, sequence, payload); if ((m_txBuffer.size() + frame.size()) > kMaximumTxBufferSize) { if (m_state == State::Connected) { emit communicationError( QStringLiteral("The serial transmit buffer is full.")); } return false; } m_txBuffer.append(frame); flushTx(); return true;
}
void Hardware_Control::flushTx() { if (!m_serial->isOpen()) return;
textwhile (!m_txBuffer.isEmpty()) { const qint64 queuedBySerial = m_serial->bytesToWrite(); const qint64 availableRoom = kWriteHighWaterMark - queuedBySerial; if (availableRoom <= 0) return; const qint64 chunkSize = std::min({ static_cast<qint64>(m_txBuffer.size()), availableRoom, kWriteChunkSize }); const qint64 written = m_serial->write(m_txBuffer.constData(), chunkSize); if (written < 0) { if (m_state == State::Connected) { emit communicationError( QStringLiteral("Serial write failed: %1") .arg(m_serial->errorString())); handleConnectionLost(); } else if (m_state == State::Handshaking) { advanceToNextPort(); } return; } if (written == 0) return; m_txBuffer.remove(0, static_cast<int>(written)); }
}
void Hardware_Control::onBytesWritten(qint64 bytes) { Q_UNUSED(bytes) flushTx(); }
void Hardware_Control::onSerialError(int errorValue) { if (errorValue == static_cast<int>(QSerialPort::NoError)) { return; }
textif (!isFatalSerialError(errorValue)) return; if (m_state == State::Connected) { const QString portName = m_currentPortName.isEmpty() ? QStringLiteral("<unknown>") : m_currentPortName; emit communicationError( QStringLiteral("Serial connection error on %1: %2") .arg(portName, m_serial->errorString())); handleConnectionLost(); return; } if (m_state == State::Handshaking) advanceToNextPort();
}
void Hardware_Control::handleConnectionLost() { if (m_state != State::Connected) return;
textm_state = State::Scanning; const bool wasConnected = m_connected.exchange(false, std::memory_order_acq_rel); closeCurrentPort(); if (wasConnected) emit disconnected(); scheduleRescan(kReconnectDelayMs);
}
QByteArray Hardware_Control::buildFrame( Command command, quint8 sequence, const QByteArray &payload) const { const quint16 payloadLength = static_cast<quint16>(payload.size());
textQByteArray frame; frame.reserve(9 + payload.size()); frame.append(static_cast<char>(kStartByte)); frame.append(static_cast<char>(kProtocolVersion)); frame.append(static_cast<char>(command)); frame.append(static_cast<char>(sequence)); frame.append( static_cast<char>(payloadLength & 0xFFU)); frame.append( static_cast<char>((payloadLength >> 8U) & 0xFFU)); frame.append(payload); const quint16 crc = crc16Ccitt(frame.constData() + 1, frame.size() - 1); frame.append(static_cast<char>(crc & 0xFFU)); frame.append(static_cast<char>((crc >> 8U) & 0xFFU)); frame.append(static_cast<char>(kEndByte)); return frame;
}
quint8 Hardware_Control::nextSequence() { const quint8 sequence = m_nextSequence++;
textif (m_nextSequence == 0) m_nextSequence = 1; return sequence;
}
quint16 Hardware_Control::crc16Ccitt( const char *data, int size) { quint16 crc = 0xFFFFU;
textfor (int index = 0; index < size; ++index) { const quint8 byte = static_cast<quint8>( static_cast<unsigned char>(data[index])); crc = updateCrc16Ccitt(crc, byte); } return crc;
}
quint16 Hardware_Control::updateCrc16Ccitt( quint16 crc, quint8 byte) { crc ^= static_cast<quint16>( static_cast<quint16>(byte) << 8U);
textfor (int bit = 0; bit < 8; ++bit) { if ((crc & 0x8000U) != 0U) { crc = static_cast<quint16>( (crc << 1U) ^ 0x1021U); } else { crc = static_cast<quint16>(crc << 1U); } } return crc;
}
bool Hardware_Control::isFatalSerialError(int errorValue) { const auto error = static_castQSerialPort::SerialPortError(errorValue);
textswitch (error) { case QSerialPort::DeviceNotFoundError: case QSerialPort::PermissionError: case QSerialPort::OpenError: case QSerialPort::NotOpenError: case QSerialPort::WriteError: case QSerialPort::ReadError: case QSerialPort::ResourceError: case QSerialPort::UnknownError: return true; case QSerialPort::NoError: case QSerialPort::ParityError: case QSerialPort::FramingError: case QSerialPort::BreakConditionError: case QSerialPort::UnsupportedOperationError: case QSerialPort::TimeoutError: return false; } return true;
}
// ESP32-S3_Hardware_Control.ino
#include <Arduino.h> #include <cstring>
namespace { constexpr uint32_t kSerialBaudRate = 115200;
constexpr uint8_t kStartByte = 0xA5; constexpr uint8_t kEndByte = 0x5A; constexpr uint8_t kProtocolVersion = 0x01;
constexpr uint8_t kLed1Pin = 5; constexpr uint8_t kLed2Pin = 6; constexpr uint8_t kTemperaturePin = 4;
constexpr uint16_t kMaximumPayloadSize = 256; constexpr uint16_t kMaximumFrameSize = kMaximumPayloadSize + 9;
constexpr size_t kTxBufferSize = 512; constexpr size_t kMaximumRxBytesPerLoop = 64; constexpr size_t kMaximumTxBytesPerLoop = 64;
constexpr uint32_t kFrameTimeoutMs = 150;
constexpr char kClientMagic[4] = {'Q', 'T', 'H', 'W'}; constexpr char kDeviceMagic[4] = {'E', 'S', '3', '2'};
enum class Command : uint8_t { HandshakeRequest = 0x01, SetLed1 = 0x10, SetLed2 = 0x11, TemperatureRequest = 0x20,
textHandshakeResponse = 0x81, TemperatureResponse = 0xA0
};
enum class RxState : uint8_t { WaitStart, Version, Command, Sequence, LengthLow, LengthHigh, Payload, CrcLow, CrcHigh, End };
RxState g_rxState = RxState::WaitStart;
uint8_t g_rxCommand = 0; uint8_t g_rxSequence = 0;
uint16_t g_rxPayloadLength = 0; uint16_t g_rxPayloadIndex = 0;
uint16_t g_rxCalculatedCrc = 0xFFFFU; uint16_t g_rxReceivedCrc = 0;
uint8_t g_rxPayload[kMaximumPayloadSize];
uint32_t g_lastRxByteTime = 0;
uint8_t g_txBuffer[kTxBufferSize]; size_t g_txHead = 0; size_t g_txTail = 0; size_t g_txCount = 0;
uint16_t updateCrc16Ccitt(uint16_t crc, uint8_t byte) { crc ^= static_cast<uint16_t>( static_cast<uint16_t>(byte) << 8U);
textfor (uint8_t bit = 0; bit < 8; ++bit) { if ((crc & 0x8000U) != 0U) { crc = static_cast<uint16_t>( (crc << 1U) ^ 0x1021U); } else { crc = static_cast<uint16_t>(crc << 1U); } } return crc;
}
uint16_t crc16Ccitt(const uint8_t *data, uint16_t size) { uint16_t crc = 0xFFFFU;
textfor (uint16_t index = 0; index < size; ++index) crc = updateCrc16Ccitt(crc, data[index]); return crc;
}
void resetParser() { g_rxState = RxState::WaitStart; g_rxCommand = 0; g_rxSequence = 0; g_rxPayloadLength = 0; g_rxPayloadIndex = 0; g_rxCalculatedCrc = 0xFFFFU; g_rxReceivedCrc = 0; }
void beginFrame() { g_rxState = RxState::Version; g_rxPayloadLength = 0; g_rxPayloadIndex = 0; g_rxCalculatedCrc = 0xFFFFU; g_rxReceivedCrc = 0; g_lastRxByteTime = millis(); }
void restartParserWith(uint8_t byte) { resetParser();
textif (byte == kStartByte) beginFrame();
}
size_t txFreeSpace() { return kTxBufferSize - g_txCount; }
void enqueueTxByte(uint8_t byte) { g_txBuffer[g_txHead] = byte; g_txHead = (g_txHead + 1U) % kTxBufferSize; ++g_txCount; }
bool queuePacket(Command command, uint8_t sequence, const uint8_t *payload, uint16_t payloadLength) { if (payloadLength > kMaximumPayloadSize) return false;
textconst uint16_t frameLength = static_cast<uint16_t>(payloadLength + 9U); if (txFreeSpace() < frameLength) return false; uint8_t frame[kMaximumFrameSize]; frame[0] = kStartByte; frame[1] = kProtocolVersion; frame[2] = static_cast<uint8_t>(command); frame[3] = sequence; frame[4] = static_cast<uint8_t>(payloadLength & 0xFFU); frame[5] = static_cast<uint8_t>( (payloadLength >> 8U) & 0xFFU); if (payloadLength > 0U && payload != nullptr) { std::memcpy(&frame[6], payload, payloadLength); } const uint16_t crc = crc16Ccitt(&frame[1], static_cast<uint16_t>(5U + payloadLength)); frame[6U + payloadLength] = static_cast<uint8_t>(crc & 0xFFU); frame[7U + payloadLength] = static_cast<uint8_t>((crc >> 8U) & 0xFFU); frame[8U + payloadLength] = kEndByte; for (uint16_t index = 0; index < frameLength; ++index) enqueueTxByte(frame[index]); return true;
}
bool validHandshakePayload(const uint8_t *payload, uint16_t payloadLength) { if (payloadLength != 13U) return false;
textif (std::memcmp(&payload[8], kClientMagic, sizeof(kClientMagic)) != 0) { return false; } return payload[12] == kProtocolVersion;
}
uint8_t readScaledTemperature() { const uint32_t rawValue = static_cast<uint32_t>(analogRead(kTemperaturePin));
textconst uint32_t scaledValue = ((rawValue * 255U) + 2047U) / 4095U; return static_cast<uint8_t>( scaledValue > 255U ? 255U : scaledValue);
}
void handlePacket(uint8_t commandValue, uint8_t sequence, const uint8_t *payload, uint16_t payloadLength) { const Command command = static_cast<Command>(commandValue);
textswitch (command) { case Command::HandshakeRequest: { if (!validHandshakePayload(payload, payloadLength)) { return; } uint8_t response[13]; std::memcpy(&response[0], payload, 8); std::memcpy(&response[8], kDeviceMagic, sizeof(kDeviceMagic)); response[12] = kProtocolVersion; queuePacket(Command::HandshakeResponse, sequence, response, sizeof(response)); return; } case Command::SetLed1: { if (payloadLength != 1U || payload[0] > 1U) { return; } digitalWrite(kLed1Pin, payload[0] != 0U ? HIGH : LOW); return; } case Command::SetLed2: { if (payloadLength != 1U || payload[0] > 1U) { return; } digitalWrite(kLed2Pin, payload[0] != 0U ? HIGH : LOW); return; } case Command::TemperatureRequest: { if (payloadLength != 0U) return; const uint8_t temperature = readScaledTemperature(); queuePacket(Command::TemperatureResponse, sequence, &temperature, 1); return; } case Command::HandshakeResponse: case Command::TemperatureResponse: return; }
}
void processReceivedByte(uint8_t byte) { switch (g_rxState) { case RxState::WaitStart: { if (byte == kStartByte) beginFrame();
textreturn; } case RxState::Version: { if (byte != kProtocolVersion) { restartParserWith(byte); return; } g_rxCalculatedCrc = updateCrc16Ccitt(g_rxCalculatedCrc, byte); g_rxState = RxState::Command; return; } case RxState::Command: { g_rxCommand = byte; g_rxCalculatedCrc = updateCrc16Ccitt(g_rxCalculatedCrc, byte); g_rxState = RxState::Sequence; return; } case RxState::Sequence: { g_rxSequence = byte; g_rxCalculatedCrc = updateCrc16Ccitt(g_rxCalculatedCrc, byte); g_rxState = RxState::LengthLow; return; } case RxState::LengthLow: { g_rxPayloadLength = byte; g_rxCalculatedCrc = updateCrc16Ccitt(g_rxCalculatedCrc, byte); g_rxState = RxState::LengthHigh; return; } case RxState::LengthHigh: { g_rxPayloadLength |= static_cast<uint16_t>( static_cast<uint16_t>(byte) << 8U); g_rxCalculatedCrc = updateCrc16Ccitt(g_rxCalculatedCrc, byte); if (g_rxPayloadLength > kMaximumPayloadSize) { restartParserWith(byte); return; } g_rxPayloadIndex = 0; g_rxState = g_rxPayloadLength == 0U ? RxState::CrcLow : RxState::Payload; return; } case RxState::Payload: { if (g_rxPayloadIndex >= kMaximumPayloadSize) { restartParserWith(byte); return; } g_rxPayload[g_rxPayloadIndex++] = byte; g_rxCalculatedCrc = updateCrc16Ccitt(g_rxCalculatedCrc, byte); if (g_rxPayloadIndex >= g_rxPayloadLength) { g_rxState = RxState::CrcLow; } return; } case RxState::CrcLow: { g_rxReceivedCrc = byte; g_rxState = RxState::CrcHigh; return; } case RxState::CrcHigh: { g_rxReceivedCrc |= static_cast<uint16_t>( static_cast<uint16_t>(byte) << 8U); g_rxState = RxState::End; return; } case RxState::End: { const bool validFrame = byte == kEndByte && g_rxReceivedCrc == g_rxCalculatedCrc; if (validFrame) { handlePacket(g_rxCommand, g_rxSequence, g_rxPayload, g_rxPayloadLength); resetParser(); return; } restartParserWith(byte); return; } } resetParser();
}
void serviceParserTimeout() { if (g_rxState == RxState::WaitStart) return;
textconst uint32_t elapsed = static_cast<uint32_t>( millis() - g_lastRxByteTime); if (elapsed > kFrameTimeoutMs) resetParser();
}
void serviceSerialRx() { for (size_t count = 0; count < kMaximumRxBytesPerLoop; ++count) { if (Serial.available() <= 0) break;
textconst int value = Serial.read(); if (value < 0) break; g_lastRxByteTime = millis(); processReceivedByte( static_cast<uint8_t>(value)); }
}
void serviceSerialTx() { if (g_txCount == 0U) return;
textconst int writable = Serial.availableForWrite(); if (writable <= 0) return; size_t bytesToWrite = g_txCount; if (bytesToWrite > kMaximumTxBytesPerLoop) { bytesToWrite = kMaximumTxBytesPerLoop; } if (bytesToWrite > static_cast<size_t>(writable)) { bytesToWrite = static_cast<size_t>(writable); } const size_t contiguousBytes = kTxBufferSize - g_txTail; if (bytesToWrite > contiguousBytes) bytesToWrite = contiguousBytes; const size_t written = Serial.write(&g_txBuffer[g_txTail], bytesToWrite); if (written == 0U) return; g_txTail = (g_txTail + written) % kTxBufferSize; g_txCount -= written;
} }
void setup() { pinMode(kLed1Pin, OUTPUT); pinMode(kLed2Pin, OUTPUT); pinMode(kTemperaturePin, INPUT);
textdigitalWrite(kLed1Pin, LOW); digitalWrite(kLed2Pin, LOW); analogReadResolution(12); Serial.begin(kSerialBaudRate);
}
void loop() { serviceParserTimeout(); serviceSerialRx(); serviceSerialTx(); }
// MinimalUsage.cpp
#include "Hardware_Control.h"
#include <QDebug> #include <QWidget>
class Widget final : public QWidget { Q_OBJECT
public: explicit Widget(QWidget *parent = nullptr) : QWidget(parent), hardware(this) { QObject::connect( &hardware, &Hardware_Control::connected, this, { qDebug() << "ESP32 Connected"; });
textQObject::connect( &hardware, &Hardware_Control::disconnected, this, []() { qDebug() << "ESP32 Disconnected"; }); QObject::connect( &hardware, &Hardware_Control::packetReceived, this, &Widget::read_esp32); QObject::connect( &hardware, &Hardware_Control::communicationError, this, [](const QString &message) { qWarning() << message; }); hardware.connect(); hardware.led1(true); hardware.led2(false); hardware.requestTemperature(); }
private slots: void read_esp32(const QByteArray &packet) { qDebug() << packet; }
private: Hardware_Control hardware; };
please dont give me as plain text give as files to able to download