1. 增加了嵌入式rpc的dto封装,负责中转来自嵌入式设备的信息,支持tcp,websocket,serial常见的通信方式。嵌入式rpc框架可见Yosuga_embedded.

This commit is contained in:
Misaki
2026-04-25 08:53:34 +08:00
parent 46da4870c9
commit 3437329193
15 changed files with 834 additions and 26 deletions
+1 -1
View File
@@ -19,7 +19,7 @@
_**本项目为Yosuga.**_ _**本项目为Yosuga.**_
本项目使用CMake构建,基于C++Qt6.6.3以及Live2D官方SDK(CubismSdkForNative-5-r.4.1)实现Live2D桌面宠物 本项目使用CMake构建,基于C++Qt6.6.3以及Live2D官方SDK(CubismSdkForNative-5-r.4.1)实现Live2D桌面助手
(本项目由[Yosuga-qt5](https://github.com/Misakityan/Yosuga-qt5) 发展更新而来,项目架构与代码都有所不同,最显著的特点是本项目支持多平台) (本项目由[Yosuga-qt5](https://github.com/Misakityan/Yosuga-qt5) 发展更新而来,项目架构与代码都有所不同,最显著的特点是本项目支持多平台)
环境为: 环境为:
+17 -11
View File
@@ -11,32 +11,38 @@
*/ */
#include <QMutex> #include <QMutex>
#include <QObject> #include <QObject>
#include <QHash>
#include "serialportmanager.h"
class DeviceTcpServer;
class DeviceWebSocketServer;
class AppCore final : public QObject { class AppCore final : public QObject {
Q_OBJECT Q_OBJECT
Q_DISABLE_COPY(AppCore) // 禁用拷贝 Q_DISABLE_COPY(AppCore)
private: private:
/** explicit AppCore(QObject *parent = nullptr);
* 构造函数私有化
* @param parent
*/
explicit AppCore(QObject *parent = nullptr); // 并不将本模块挂在对象树当中,因为本模块为单例类,内存自行管理
static QScopedPointer<AppCore> m_instance; // 单例类 static QScopedPointer<AppCore> m_instance;
static QMutex m_mutex; static QMutex m_mutex;
DeviceTcpServer *m_deviceTcpServer = nullptr;
DeviceWebSocketServer *m_deviceWsServer = nullptr;
private slots: private slots:
// 业务接收槽函数
void onRecordingFinished_Byte(const QByteArray &wavData); void onRecordingFinished_Byte(const QByteArray &wavData);
public: public:
// 单例访问点
static AppCore *getInstance(); static AppCore *getInstance();
// 显式销毁
static void destroy(); static void destroy();
~AppCore() override; ~AppCore() override;
void registerEmbeddedDevice(const QString &deviceId, SerialPortClient *client);
void unregisterEmbeddedDevice(const QString &deviceId);
public: public:
// 单次对话
void SingleExchange(); void SingleExchange();
void tryToInit() { return; };
}; };
+46 -5
View File
@@ -8,10 +8,13 @@
#include "AudioDataHandle.h" #include "AudioDataHandle.h"
#include "AutoAgentHandle.h" #include "AutoAgentHandle.h"
#include "ScreenShotReqDataHandle.h" #include "ScreenShotReqDataHandle.h"
#include "DeviceDataHandle.h"
#include "AudioInput.h" #include "AudioInput.h"
#include "NetWorkDO.h" #include "NetWorkDO.h"
#include "websocketmanager.h" #include "websocketmanager.h"
#include "DeviceTcpServer.h"
#include "DeviceWebSocketServer.h"
// 初始化静态成员 // 初始化静态成员
QScopedPointer<AppCore> AppCore::m_instance; QScopedPointer<AppCore> AppCore::m_instance;
QMutex AppCore::m_mutex; QMutex AppCore::m_mutex;
@@ -39,6 +42,32 @@ void AppCore::destroy()
AppCore::AppCore(QObject *parent) : QObject(parent) AppCore::AppCore(QObject *parent) : QObject(parent)
{ {
DeviceDataHandle *deviceHandle = DeviceDataHandle::getInstance();
// 启动嵌入式设备 TCP 服务器
m_deviceTcpServer = new DeviceTcpServer(10001, this);
connect(m_deviceTcpServer, &DeviceTcpServer::deviceConnected,
deviceHandle, [=](const QString &deviceId, const QString &) {
deviceHandle->registerDevice(deviceId, "tcp", m_deviceTcpServer);
});
connect(m_deviceTcpServer, &DeviceTcpServer::deviceDisconnected,
deviceHandle, &DeviceDataHandle::unregisterDevice);
connect(m_deviceTcpServer, &DeviceTcpServer::jsonReceived,
deviceHandle, &DeviceDataHandle::onTcpDeviceData);
m_deviceTcpServer->start();
// 启动嵌入式设备 WebSocket 服务器
m_deviceWsServer = new DeviceWebSocketServer(10002, this);
connect(m_deviceWsServer, &DeviceWebSocketServer::deviceConnected,
deviceHandle, [=](const QString &deviceId, const QString &) {
deviceHandle->registerDevice(deviceId, "websocket", m_deviceWsServer);
});
connect(m_deviceWsServer, &DeviceWebSocketServer::deviceDisconnected,
deviceHandle, &DeviceDataHandle::unregisterDevice);
connect(m_deviceWsServer, &DeviceWebSocketServer::jsonReceived,
deviceHandle, &DeviceDataHandle::onWsDeviceData);
m_deviceWsServer->start();
// 初始化业务解析单例 // 初始化业务解析单例
AudioDataHandle::getInstance(); AudioDataHandle::getInstance();
AutoAgentHandle::getInstance(); AutoAgentHandle::getInstance();
@@ -51,20 +80,32 @@ AppCore::AppCore(QObject *parent) : QObject(parent)
AudioInput::getInstance()->setAudioPath(QDir::currentPath(), "/temp.wav"); AudioInput::getInstance()->setAudioPath(QDir::currentPath(), "/temp.wav");
// 连接必要的信号 // 连接必要的信号
connect(AudioInput::getInstance(), &AudioInput::recordingFinished_Byte, connect(AudioInput::getInstance(), &AudioInput::recordingFinished_Byte,
this, &AppCore::onRecordingFinished_Byte); // 录音完成信号 this, &AppCore::onRecordingFinished_Byte);
} }
AppCore::~AppCore() AppCore::~AppCore()
{ {
// 析构业务解析单例 if (m_deviceTcpServer) m_deviceTcpServer->stop();
ScreenShotReqDataHandle::destroy(); if (m_deviceWsServer) m_deviceWsServer->stop();
AutoAgentHandle::destroy(); // 显式销毁
AudioDataHandle::destroy();
ScreenShotReqDataHandle::destroy();
AutoAgentHandle::destroy();
AudioDataHandle::destroy();
DeviceDataHandle::destroy();
qDebug() << "AppCore destroyed"; qDebug() << "AppCore destroyed";
} }
void AppCore::registerEmbeddedDevice(const QString &deviceId, SerialPortClient *client)
{
DeviceDataHandle::getInstance()->registerDevice(deviceId, QStringLiteral("serial"), client);
}
void AppCore::unregisterEmbeddedDevice(const QString &deviceId)
{
DeviceDataHandle::getInstance()->unregisterDevice(deviceId);
}
void AppCore::SingleExchange() { void AppCore::SingleExchange() {
// 开始录音,录音结束后会触发录音完成信号 // 开始录音,录音结束后会触发录音完成信号
AudioInput::getInstance()->startAutoStopAudio(AudioInput::getInstance()->getSilenceThreshold(), 800); AudioInput::getInstance()->startAutoStopAudio(AudioInput::getInstance()->getSilenceThreshold(), 800);
+37
View File
@@ -0,0 +1,37 @@
//
// Created by Yosuga on 2026/4/25.
//
#pragma once
#include <QObject>
#include <QJsonObject>
#include <QJsonValue>
#include <QJsonArray>
#include <QJsonDocument>
#include "DataTransferObjectBase.h"
class DeviceDataTransferObject final : public DataTransferObjectBase {
public:
explicit DeviceDataTransferObject(
QString action = "",
QString deviceId = "",
QJsonObject payload = {}
);
static DeviceDataTransferObject fromJson(const QJsonObject& json);
// WebSocket 类型固定为 "device_data"
[[nodiscard]] QString type() const override { return QStringLiteral("device_data"); }
[[nodiscard]] QJsonObject toJson() const override;
DeviceDataTransferObject& setData(const QString& key, const QJsonValue& value) override;
[[nodiscard]] QString action() const { return m_action; }
[[nodiscard]] QString deviceId() const { return m_deviceId; }
[[nodiscard]] QJsonObject payload() const { return m_payload; }
private:
QString m_action;
QString m_deviceId;
QJsonObject m_payload;
};
+2
View File
@@ -29,6 +29,7 @@
#include "AudioDataTransferObject.h" #include "AudioDataTransferObject.h"
#include "AutoAgentDataObject.h" #include "AutoAgentDataObject.h"
#include "ScreenShotDataTransferObject.h" #include "ScreenShotDataTransferObject.h"
#include "DeviceDataTransferObject.h"
/** /**
* NetworkDO * NetworkDO
*/ */
@@ -58,6 +59,7 @@ signals:
void audioPacketReceived(const AudioDataTransferObject& packet); // 音频数据准备完成信号 void audioPacketReceived(const AudioDataTransferObject& packet); // 音频数据准备完成信号
void autoAgentPacketReceived(const AutoAgentDataObject& packet); // 自动代理数据包接收信号 void autoAgentPacketReceived(const AutoAgentDataObject& packet); // 自动代理数据包接收信号
void screenShotPacketReceived(const ScreenShotDataTransferObject& packet); // 截图数据包接收信号 void screenShotPacketReceived(const ScreenShotDataTransferObject& packet); // 截图数据包接收信号
void deviceCommandReceived(const DeviceDataTransferObject& packet); // 设备控制命令(服务端→客户端)
void errorOccurred(const QString& errorMsg); // 错误信号 void errorOccurred(const QString& errorMsg); // 错误信号
+48
View File
@@ -0,0 +1,48 @@
//
// Created by Yosuga on 2026/4/25.
//
#include "DeviceDataTransferObject.h"
DeviceDataTransferObject::DeviceDataTransferObject(
QString action,
QString deviceId,
QJsonObject payload
) : m_action(std::move(action))
, m_deviceId(std::move(deviceId))
, m_payload(std::move(payload))
{
}
DeviceDataTransferObject DeviceDataTransferObject::fromJson(const QJsonObject& json)
{
DeviceDataTransferObject obj;
obj.m_action = json.value("action").toString("device_command");
obj.m_deviceId = json.value("device_id").toString("");
QJsonValue payloadVal = json.value("payload");
if (payloadVal.isString()) {
obj.m_payload["rpc_call"] = payloadVal.toString();
} else if (payloadVal.isObject()) {
obj.m_payload = payloadVal.toObject();
}
return obj;
}
QJsonObject DeviceDataTransferObject::toJson() const
{
QJsonObject json;
json["action"] = m_action;
if (!m_deviceId.isEmpty()) {
json["device_id"] = m_deviceId;
}
json["payload"] = m_payload;
return json;
}
DeviceDataTransferObject& DeviceDataTransferObject::setData(const QString& key, const QJsonValue& value)
{
if (key == "action") m_action = value.toString();
else if (key == "device_id") m_deviceId = value.toString();
else if (key == "payload") m_payload = value.toObject();
return *this;
}
+3
View File
@@ -75,6 +75,9 @@ void NetworkDO::onDataReceived(const QString& type, const QJsonObject& data)
else if (type == "screenshot_data") { else if (type == "screenshot_data") {
emit screenShotPacketReceived(ScreenShotDataTransferObject::fromJson(data)); emit screenShotPacketReceived(ScreenShotDataTransferObject::fromJson(data));
} }
else if (type == "device_command") {
emit deviceCommandReceived(DeviceDataTransferObject::fromJson(data));
}
else { else {
qWarning() << "[NetworkDO] Received unknown type:" << type; qWarning() << "[NetworkDO] Received unknown type:" << type;
} }
@@ -0,0 +1,77 @@
//
// Created by Yosuga on 2026/4/25.
//
/**
* 设备数据处理模块 — 统一路由器
*
* 管理三种设备连接通道:
* - TCP (RK3566 等通过 TCP 接入)
* - WebSocket (ESP32 等通过 WebSocket 接入)
* - 串口 (STM32 等通过串口接入)
*
* 数据流向:
* Device →(TCP/WS/Serial)→ DeviceDataHandle → NetworkDO → WebSocket → YosugaServer
* YosugaServer → WebSocket → NetworkDO → DeviceDataHandle →(TCP/WS/Serial)→ Device
*/
#pragma once
#include <QObject>
#include <QMutex>
#include <QScopedPointer>
#include <QHash>
#include <functional>
#include "DeviceDataTransferObject.h"
#include "serialportmanager.h"
class DeviceDataHandle final : public QObject
{
Q_OBJECT
Q_DISABLE_COPY(DeviceDataHandle)
private:
explicit DeviceDataHandle(QObject *parent = nullptr);
static QScopedPointer<DeviceDataHandle> m_instance;
static QMutex m_mutex;
public:
static DeviceDataHandle *getInstance();
static void destroy();
~DeviceDataHandle() override;
// 注册设备到路由表(由各 Server/Client 在设备握手完成后调用)
void registerDevice(const QString &deviceId, const QString &deviceType, QObject *connection);
// 移除设备
void unregisterDevice(const QString &deviceId);
// 向设备发送数据(自动选择正确的通道)
void sendToDevice(const QString &deviceId, const QString &type, const QJsonObject &data);
// 获取设备连接类型
[[nodiscard]] QString deviceConnectionType(const QString &deviceId) const;
public slots:
// 收到来自 YosugaServer 的设备命令(通过 device_command 信号)
void onDeviceCommandReceived(const DeviceDataTransferObject &packet);
// 收到来自 TCP 设备的 JSON 数据
void onTcpDeviceData(const QString &deviceId, const QString &type, const QJsonObject &data);
// 收到来自 WebSocket 设备的 JSON 数据
void onWsDeviceData(const QString &deviceId, const QString &type, const QJsonObject &data);
// 收到来自串口设备的 JSON 数据
void onSerialDeviceData(const QString &deviceId, const QString &type, const QJsonObject &data);
private:
// 转发设备数据到 YosugaServer
void forwardToServer(const QString &deviceId, const QString &type, const QJsonObject &data);
struct DeviceEntry {
QString deviceId;
QString deviceType; // "tcp", "websocket", "serial"
QObject *connection; // DeviceTcpServer / DeviceWebSocketServer / SerialPortClient
};
QHash<QString, DeviceEntry> m_devices;
};
@@ -0,0 +1,141 @@
//
// Created by Yosuga on 2026/4/25.
//
#include "DeviceDataHandle.h"
#include "NetWorkDO.h"
#include <QDebug>
#include <QMetaMethod>
QScopedPointer<DeviceDataHandle> DeviceDataHandle::m_instance;
QMutex DeviceDataHandle::m_mutex;
DeviceDataHandle *DeviceDataHandle::getInstance()
{
if (m_instance.isNull()) {
QMutexLocker locker(&m_mutex);
if (m_instance.isNull()) {
m_instance.reset(new DeviceDataHandle());
}
}
return m_instance.data();
}
void DeviceDataHandle::destroy()
{
QMutexLocker locker(&m_mutex);
m_instance.reset();
}
DeviceDataHandle::DeviceDataHandle(QObject *parent) : QObject(parent)
{
qRegisterMetaType<QJsonObject>("QJsonObject");
connect(NetworkDO::getInstance(), &NetworkDO::deviceCommandReceived,
this, &DeviceDataHandle::onDeviceCommandReceived);
}
DeviceDataHandle::~DeviceDataHandle()
{
qDebug() << "[DeviceDataHandle] destroyed";
}
void DeviceDataHandle::registerDevice(const QString &deviceId, const QString &deviceType, QObject *connection)
{
DeviceEntry entry;
entry.deviceId = deviceId;
entry.deviceType = deviceType;
entry.connection = connection;
m_devices.insert(deviceId, entry);
qDebug() << "[DeviceDataHandle] 设备已注册:" << deviceId << "类型:" << deviceType;
}
void DeviceDataHandle::unregisterDevice(const QString &deviceId)
{
m_devices.remove(deviceId);
qDebug() << "[DeviceDataHandle] 设备已移除:" << deviceId;
}
void DeviceDataHandle::sendToDevice(const QString &deviceId, const QString &type, const QJsonObject &data)
{
DeviceEntry entry = m_devices.value(deviceId);
if (entry.connection == nullptr) {
qWarning() << "[DeviceDataHandle] 未知设备:" << deviceId;
return;
}
// 通过 QMetaObject::invokeMethod 动态调用对应 Server 的 sendToDevice
bool ok = QMetaObject::invokeMethod(
entry.connection,
"sendToDevice",
Qt::QueuedConnection,
Q_ARG(QString, deviceId),
Q_ARG(QString, type),
Q_ARG(QJsonObject, data)
);
if (!ok) {
// 兜底:如果是串口设备,直接调用 SerialPortClient::sendJson
auto *serialClient = qobject_cast<SerialPortClient*>(entry.connection);
if (serialClient) {
serialClient->sendJson(type, data);
ok = true;
}
}
if (!ok) {
qWarning() << "[DeviceDataHandle] 发送失败到设备:" << deviceId;
}
}
QString DeviceDataHandle::deviceConnectionType(const QString &deviceId) const
{
return m_devices.value(deviceId).deviceType;
}
void DeviceDataHandle::onDeviceCommandReceived(const DeviceDataTransferObject &packet)
{
const QString deviceId = packet.deviceId();
if (deviceId.isEmpty()) {
qWarning() << "[DeviceDataHandle] device_command 缺少 device_id";
return;
}
// 提取 RPC 调用字符串
QJsonObject payload = packet.payload();
QString rpcCall;
if (payload.contains("rpc_call")) {
rpcCall = payload.value("rpc_call").toString();
} else {
rpcCall = QString::fromUtf8(QJsonDocument(payload).toJson(QJsonDocument::Compact));
}
QJsonObject forwardPayload;
forwardPayload["rpc_call"] = rpcCall;
sendToDevice(deviceId, "rpc_call", forwardPayload);
qDebug() << "[DeviceDataHandle] 已转发命令到设备:" << deviceId;
}
void DeviceDataHandle::onTcpDeviceData(const QString &deviceId, const QString &type, const QJsonObject &data)
{
forwardToServer(deviceId, type, data);
}
void DeviceDataHandle::onWsDeviceData(const QString &deviceId, const QString &type, const QJsonObject &data)
{
forwardToServer(deviceId, type, data);
}
void DeviceDataHandle::onSerialDeviceData(const QString &deviceId, const QString &type, const QJsonObject &data)
{
forwardToServer(deviceId, type, data);
}
void DeviceDataHandle::forwardToServer(const QString &deviceId, const QString &type, const QJsonObject &data)
{
// 所有设备数据统一封装为 DeviceDataTransferObject 发往 YosugaServer
DeviceDataTransferObject packet(type, deviceId, data);
NetworkDO::getInstance()->sendPacket(packet);
qDebug() << "[DeviceDataHandle] 设备数据已转发到服务端:" << deviceId << type;
}
@@ -0,0 +1,56 @@
//
// Created by misaki on 2026/4/25.
//
#pragma once
#include <QObject>
#include <QTcpServer>
#include <QTcpSocket>
#include <QHash>
#include <QJsonObject>
#include <QJsonDocument>
#include <QByteArray>
class DeviceTcpServer final : public QObject
{
Q_OBJECT
Q_DISABLE_COPY(DeviceTcpServer)
public:
explicit DeviceTcpServer(quint16 port = 10001, QObject *parent = nullptr);
~DeviceTcpServer() override;
bool start();
void stop();
[[nodiscard]] bool isListening() const { return m_server && m_server->isListening(); }
[[nodiscard]] quint16 serverPort() const { return m_port; }
Q_INVOKABLE void sendToDevice(const QString &deviceId, const QString &type, const QJsonObject &data);
signals:
void deviceConnected(const QString &deviceId, const QString &deviceName);
void deviceDisconnected(const QString &deviceId);
void jsonReceived(const QString &deviceId, const QString &type, const QJsonObject &data);
void serverError(const QString &errorMsg);
private slots:
void onNewConnection();
void onClientDisconnected();
void onReadyRead();
private:
struct DeviceSession {
QString deviceId;
QString deviceName;
QTcpSocket *socket;
QByteArray buffer;
};
QTcpServer *m_server;
quint16 m_port;
QHash<QString, DeviceSession*> m_deviceSessions; // deviceId -> session
QHash<QTcpSocket*, DeviceSession*> m_socketSessions; // socket -> session
void parseIncomingData(DeviceSession *session);
void removeSession(QTcpSocket *socket);
};
@@ -0,0 +1,52 @@
//
// Created by misaki on 2026/4/25.
//
#pragma once
#include <QObject>
#include <QWebSocketServer>
#include <QWebSocket>
#include <QHash>
#include <QJsonObject>
class DeviceWebSocketServer final : public QObject
{
Q_OBJECT
Q_DISABLE_COPY(DeviceWebSocketServer)
public:
explicit DeviceWebSocketServer(quint16 port = 10002, QObject *parent = nullptr);
~DeviceWebSocketServer() override;
bool start();
void stop();
[[nodiscard]] bool isListening() const { return m_server && m_server->isListening(); }
[[nodiscard]] quint16 serverPort() const { return m_port; }
Q_INVOKABLE void sendToDevice(const QString &deviceId, const QString &type, const QJsonObject &data);
signals:
void deviceConnected(const QString &deviceId, const QString &deviceName);
void deviceDisconnected(const QString &deviceId);
void jsonReceived(const QString &deviceId, const QString &type, const QJsonObject &data);
void serverError(const QString &errorMsg);
private slots:
void onNewConnection();
void onClientDisconnected();
void onTextMessageReceived(const QString &message);
private:
struct DeviceSession {
QString deviceId;
QString deviceName;
QWebSocket *socket;
};
QWebSocketServer *m_server;
quint16 m_port;
QHash<QString, DeviceSession*> m_deviceSessions;
QHash<QWebSocket*, DeviceSession*> m_socketSessions;
void removeSession(QWebSocket *socket);
};
@@ -0,0 +1,179 @@
//
// Created by misaki on 2026/4/25.
//
#include "DeviceTcpServer.h"
#include <QDebug>
#include <QJsonParseError>
#include <QJsonObject>
#include <QJsonDocument>
#include <QDateTime>
DeviceTcpServer::DeviceTcpServer(quint16 port, QObject *parent)
: QObject(parent)
, m_server(new QTcpServer(this))
, m_port(port)
{
connect(m_server, &QTcpServer::newConnection,
this, &DeviceTcpServer::onNewConnection);
}
DeviceTcpServer::~DeviceTcpServer()
{
stop();
}
bool DeviceTcpServer::start()
{
if (m_server->isListening()) {
qDebug() << "[DeviceTcpServer] Already listening on port" << m_port;
return true;
}
if (!m_server->listen(QHostAddress::Any, m_port)) {
QString err = QString("Failed to listen on TCP port %1: %2")
.arg(m_port).arg(m_server->errorString());
qWarning() << "[DeviceTcpServer]" << err;
emit serverError(err);
return false;
}
qDebug() << "[DeviceTcpServer] Listening for embedded devices on TCP port" << m_port;
return true;
}
void DeviceTcpServer::stop()
{
for (auto it = m_socketSessions.begin(); it != m_socketSessions.end(); ++it) {
DeviceSession *session = it.value();
if (session->socket->state() == QAbstractSocket::ConnectedState) {
session->socket->disconnectFromHost();
}
delete session;
}
m_deviceSessions.clear();
m_socketSessions.clear();
if (m_server->isListening()) {
m_server->close();
}
}
void DeviceTcpServer::sendToDevice(const QString &deviceId, const QString &type, const QJsonObject &data)
{
DeviceSession *session = m_deviceSessions.value(deviceId);
if (!session || !session->socket) {
qWarning() << "[DeviceTcpServer] Cannot send to unknown device:" << deviceId;
return;
}
QJsonObject msg;
msg["type"] = type;
msg["data"] = data;
msg["timestamp"] = QDateTime::currentMSecsSinceEpoch();
QByteArray payload = QJsonDocument(msg).toJson(QJsonDocument::Compact);
session->socket->write(payload);
session->socket->write("\n");
session->socket->flush();
}
void DeviceTcpServer::onNewConnection()
{
while (m_server->hasPendingConnections()) {
QTcpSocket *socket = m_server->nextPendingConnection();
if (!socket) continue;
auto *session = new DeviceSession{};
session->socket = socket;
m_socketSessions.insert(socket, session);
connect(socket, &QTcpSocket::disconnected,
this, &DeviceTcpServer::onClientDisconnected);
connect(socket, &QTcpSocket::readyRead,
this, &DeviceTcpServer::onReadyRead);
qDebug() << "[DeviceTcpServer] New TCP connection from"
<< socket->peerAddress().toString() << ":" << socket->peerPort();
}
}
void DeviceTcpServer::onClientDisconnected()
{
auto *socket = qobject_cast<QTcpSocket*>(sender());
if (socket) {
removeSession(socket);
}
}
void DeviceTcpServer::onReadyRead()
{
auto *socket = qobject_cast<QTcpSocket*>(sender());
if (!socket) return;
DeviceSession *session = m_socketSessions.value(socket);
if (!session) return;
session->buffer.append(socket->readAll());
parseIncomingData(session);
}
void DeviceTcpServer::parseIncomingData(DeviceSession *session)
{
// TCP: messages are newline-delimited JSON
int newlinePos;
while ((newlinePos = session->buffer.indexOf('\n')) >= 0) {
QByteArray line = session->buffer.left(newlinePos).trimmed();
session->buffer.remove(0, newlinePos + 1);
if (line.isEmpty()) continue;
QJsonParseError err;
QJsonDocument doc = QJsonDocument::fromJson(line, &err);
if (err.error != QJsonParseError::NoError || !doc.isObject()) {
qWarning() << "[DeviceTcpServer] Invalid JSON from device:" << err.errorString();
continue;
}
QJsonObject msg = doc.object();
QString type = msg.value("type").toString();
QString deviceId = msg.value("device_id").toString();
QJsonObject payload = msg.value("payload").toObject();
// If this is a registration message, process it
if (type == "register" || !session->deviceId.isEmpty()) {
if (session->deviceId.isEmpty() && type == "register") {
session->deviceId = deviceId;
session->deviceName = payload.value("device").toObject().value("name").toString(deviceId);
m_deviceSessions.insert(session->deviceId, session);
qDebug() << "[DeviceTcpServer] Device registered:"
<< session->deviceId << "(" << session->deviceName << ")";
// Send ack
QJsonObject ack;
ack["status"] = "ok";
ack["device_id"] = session->deviceId;
sendToDevice(session->deviceId, "register_ack", ack);
emit deviceConnected(session->deviceId, session->deviceName);
}
if (!session->deviceId.isEmpty()) {
emit jsonReceived(session->deviceId, type, payload);
}
}
}
}
void DeviceTcpServer::removeSession(QTcpSocket *socket)
{
DeviceSession *session = m_socketSessions.take(socket);
if (!session) return;
QString deviceId = session->deviceId;
if (!deviceId.isEmpty()) {
m_deviceSessions.remove(deviceId);
emit deviceDisconnected(deviceId);
qDebug() << "[DeviceTcpServer] Device disconnected:" << deviceId;
}
delete session;
}
@@ -0,0 +1,165 @@
//
// Created by misaki on 2026/4/25.
//
#include "DeviceWebSocketServer.h"
#include <QDebug>
#include <QJsonParseError>
#include <QJsonDocument>
#include <QJsonObject>
#include <QDateTime>
DeviceWebSocketServer::DeviceWebSocketServer(quint16 port, QObject *parent)
: QObject(parent)
, m_server(nullptr)
, m_port(port)
{
}
DeviceWebSocketServer::~DeviceWebSocketServer()
{
stop();
}
bool DeviceWebSocketServer::start()
{
if (m_server && m_server->isListening()) {
qDebug() << "[DeviceWsServer] Already listening on port" << m_port;
return true;
}
m_server = new QWebSocketServer("Yosuga-Device-WS", QWebSocketServer::NonSecureMode, this);
if (!m_server->listen(QHostAddress::Any, m_port)) {
QString err = QString("Failed to listen on WS port %1: %2")
.arg(m_port).arg(m_server->errorString());
qWarning() << "[DeviceWsServer]" << err;
emit serverError(err);
m_server->deleteLater();
m_server = nullptr;
return false;
}
connect(m_server, &QWebSocketServer::newConnection,
this, &DeviceWebSocketServer::onNewConnection);
qDebug() << "[DeviceWsServer] Listening for embedded devices on WS port" << m_port;
return true;
}
void DeviceWebSocketServer::stop()
{
for (auto it = m_socketSessions.begin(); it != m_socketSessions.end(); ++it) {
DeviceSession *session = it.value();
if (session->socket->state() == QAbstractSocket::ConnectedState) {
session->socket->close();
}
delete session;
}
m_deviceSessions.clear();
m_socketSessions.clear();
if (m_server) {
m_server->close();
m_server->deleteLater();
m_server = nullptr;
}
}
void DeviceWebSocketServer::sendToDevice(const QString &deviceId, const QString &type, const QJsonObject &data)
{
DeviceSession *session = m_deviceSessions.value(deviceId);
if (!session || !session->socket) {
qWarning() << "[DeviceWsServer] Cannot send to unknown device:" << deviceId;
return;
}
QJsonObject msg;
msg["type"] = type;
msg["data"] = data;
msg["timestamp"] = QDateTime::currentMSecsSinceEpoch();
QByteArray payload = QJsonDocument(msg).toJson(QJsonDocument::Compact);
session->socket->sendTextMessage(QString::fromUtf8(payload));
}
void DeviceWebSocketServer::onNewConnection()
{
while (m_server->hasPendingConnections()) {
QWebSocket *socket = m_server->nextPendingConnection();
if (!socket) continue;
auto *session = new DeviceSession{};
session->socket = socket;
m_socketSessions.insert(socket, session);
connect(socket, &QWebSocket::disconnected,
this, &DeviceWebSocketServer::onClientDisconnected);
connect(socket, &QWebSocket::textMessageReceived,
this, &DeviceWebSocketServer::onTextMessageReceived);
qDebug() << "[DeviceWsServer] New WS connection from"
<< socket->peerAddress().toString() << ":" << socket->peerPort();
}
}
void DeviceWebSocketServer::onClientDisconnected()
{
auto *socket = qobject_cast<QWebSocket*>(sender());
if (socket) {
removeSession(socket);
}
}
void DeviceWebSocketServer::onTextMessageReceived(const QString &message)
{
auto *socket = qobject_cast<QWebSocket*>(sender());
if (!socket) return;
DeviceSession *session = m_socketSessions.value(socket);
if (!session) return;
QJsonParseError err;
QJsonDocument doc = QJsonDocument::fromJson(message.toUtf8(), &err);
if (err.error != QJsonParseError::NoError || !doc.isObject()) {
qWarning() << "[DeviceWsServer] Invalid JSON:" << err.errorString();
return;
}
QJsonObject msg = doc.object();
QString type = msg.value("type").toString();
QString deviceId = msg.value("device_id").toString();
QJsonObject payload = msg.value("payload").toObject();
if (type == "register") {
session->deviceId = deviceId;
session->deviceName = payload.value("device").toObject().value("name").toString(deviceId);
m_deviceSessions.insert(session->deviceId, session);
qDebug() << "[DeviceWsServer] Device registered:" << session->deviceId;
QJsonObject ack;
ack["status"] = "ok";
ack["device_id"] = session->deviceId;
sendToDevice(session->deviceId, "register_ack", ack);
emit deviceConnected(session->deviceId, session->deviceName);
}
if (!session->deviceId.isEmpty()) {
emit jsonReceived(session->deviceId, type, payload);
}
}
void DeviceWebSocketServer::removeSession(QWebSocket *socket)
{
DeviceSession *session = m_socketSessions.take(socket);
if (!session) return;
QString deviceId = session->deviceId;
if (!deviceId.isEmpty()) {
m_deviceSessions.remove(deviceId);
emit deviceDisconnected(deviceId);
qDebug() << "[DeviceWsServer] Device disconnected:" << deviceId;
}
delete session;
}
+1
View File
@@ -56,6 +56,7 @@ void Menu::createMenu()
// 设置按钮 // 设置按钮
connect(settingsAction, &QAction::triggered, this, [this]() { connect(settingsAction, &QAction::triggered, this, [this]() {
qDebug() << "Settings triggered"; qDebug() << "Settings triggered";
AppCore::getInstance()->tryToInit();
// 打开设置窗口 // 打开设置窗口
// 如果 Setting 窗口已经存在,则不再创建 // 如果 Setting 窗口已经存在,则不再创建
+1 -1
View File
@@ -131,7 +131,7 @@ ModelPage::ModelPage(QWidget *parent)
return nullptr; return nullptr;
} }
// 执行真正的耗时加载 // 执行真正的耗时加载
// 调用我们在 Manager 里新写的函数 // 调用在 Manager 里新写的函数
LAppModel *model = LAppLive2DManager::GetInstance()->LoadModelInstance(dir, filename); LAppModel *model = LAppLive2DManager::GetInstance()->LoadModelInstance(dir, filename);
// 清理子线程资源 // 清理子线程资源
context->doneCurrent(); context->doneCurrent();