增加了引擎的暂停与结束控制

This commit is contained in:
jinchao 2025-05-29 14:38:09 +08:00
parent 0296daa268
commit 04767fe971
16 changed files with 517 additions and 101 deletions

Binary file not shown.

View File

@ -37,6 +37,8 @@ add_library(XNMonitorServer SHARED
SystemInfoMonitor.cpp
ModelInfoMonitor.h
ModelInfoMonitor.cpp
SystemControl.h
SystemControl.cpp
${DDS_XNIDL_SOURCES_CXX}
)

View File

@ -1,8 +1,4 @@
#include "ModelInfoMonitor.h"
#include <nlohmann/json.hpp>
#include <iostream>
using json = nlohmann::json;
ModelInfoMonitor::~ModelInfoMonitor()
{

View File

@ -1,9 +1,5 @@
#pragma once
#include "XNMonitorServer_global.h"
#include <map>
#include <mutex>
#include "TypeDefine.h"
#include "TopicManager.h"
#include "../XNCore/XNIDL/XNSimStatusPubSubTypes.hpp"

View File

@ -0,0 +1,58 @@
#include "SystemControl.h"
#include "../XNCore/XNIDL/XNSimStatusPubSubTypes.hpp"
SystemControl::~SystemControl()
{
//注销引擎控制发布者
TopicManager::Instance()->unregisterPublisher("XNSim::XNSimControl::XNRuntimeControl");
m_EngineControlWriter = nullptr;
}
std::string SystemControl::Initialize()
{
m_EngineControlWriter = nullptr;
XNDDSErrorCode ret = TopicManager::Instance()
->registerPublisher<XNSim::XNSimControl::XNRuntimeControlPubSubType>(
"XNSim::XNSimControl::XNRuntimeControl", m_EngineControlWriter);
if (ret != XNDDSErrorCode::SUCCESS || m_EngineControlWriter == nullptr) {
return "Failed to register engine control publisher, error code: "
+ std::to_string(static_cast<int>(ret));
}
return "Success";
}
void SystemControl::Pause()
{
// 暂停引擎
if (m_EngineControlWriter == nullptr) {
return;
}
XNSim::XNSimControl::XNRuntimeControl cmd;
cmd.XNSimCmd(1);
cmd.XNThrCmd(0);
m_EngineControlWriter->write(&cmd);
}
void SystemControl::Resume()
{
// 恢复引擎
if (m_EngineControlWriter == nullptr) {
return;
}
XNSim::XNSimControl::XNRuntimeControl cmd;
cmd.XNSimCmd(2);
cmd.XNThrCmd(0);
m_EngineControlWriter->write(&cmd);
}
void SystemControl::Stop()
{
// 停止引擎
if (m_EngineControlWriter == nullptr) {
return;
}
XNSim::XNSimControl::XNRuntimeControl cmd;
cmd.XNSimCmd(3);
cmd.XNThrCmd(0);
m_EngineControlWriter->write(&cmd);
}

View File

@ -0,0 +1,22 @@
#pragma once
#include "XNMonitorServer_global.h"
#include "TypeDefine.h"
#include "TopicManager.h"
#include "../XNCore/XNIDL/XNSimStatusPubSubTypes.hpp"
class XNMONITORSERVER_EXPORT SystemControl
{
public:
SystemControl() {};
virtual ~SystemControl();
public:
std::string Initialize();
void Pause();
void Resume();
void Stop();
private:
XNDataWriter *m_EngineControlWriter;
};

View File

@ -1,7 +1,4 @@
#include "SystemInfoMonitor.h"
#include <nlohmann/json.hpp>
using json = nlohmann::json;
SystemInfoMonitor::~SystemInfoMonitor()
{

View File

@ -1,10 +1,5 @@
#ifndef SYSTEMINFOMONITOR_H
#define SYSTEMINFOMONITOR_H
#pragma once
#include "XNMonitorServer_global.h"
#include <map>
#include <mutex>
#include "TypeDefine.h"
#include "TopicManager.h"
#include "../XNCore/XNIDL/XNSimStatusPubSubTypes.hpp"
@ -55,5 +50,3 @@ private:
*/
std::map<uint32_t, uint64_t> m_ThreadCycleCount;
};
#endif // SYSTEMINFOMONITOR_H

View File

@ -10,12 +10,8 @@
*/
#pragma once
#include <map>
#include <mutex>
#include <memory>
#include <string>
#include "XNMonitorServer_global.h"
#include "DataReaderListenerImpl.h"
#include <fastdds/dds/core/status/StatusMask.hpp>
/**
* @brief
@ -101,8 +97,7 @@ public:
* @return XNDataWriter*:
*/
template <typename T>
XNDDSErrorCode registerPublisher(const std::string &topicName,
XNDataWriter *dataWriter = nullptr)
XNDDSErrorCode registerPublisher(const std::string &topicName, XNDataWriter *&dataWriter)
{
std::lock_guard<std::mutex> locker(m_Mutex);
if (topics_.find(topicName) == topics_.end()) {

View File

@ -6,6 +6,10 @@
#include <string>
#include <vector>
#include <memory>
#include <map>
#include <mutex>
#include <nlohmann/json.hpp>
#include <iostream>
#include <fastdds/dds/domain/DomainParticipant.hpp>
#include <fastdds/dds/domain/DomainParticipantFactory.hpp>
#include <fastdds/dds/topic/TypeSupport.hpp>
@ -21,6 +25,7 @@
#include <fastdds/dds/publisher/qos/PublisherQos.hpp>
#include <fastdds/dds/publisher/DataWriter.hpp>
#include <fastdds/dds/publisher/qos/DataWriterQos.hpp>
#include <fastdds/dds/core/status/StatusMask.hpp>
/**
* @brief
@ -83,6 +88,7 @@ using XNTopic = eprosima::fastdds::dds::Topic;
*/
using XNTopicDataType = eprosima::fastdds::dds::TopicDataType;
using json = nlohmann::json;
/**
* @brief
*/

View File

@ -3,11 +3,9 @@
* @brief
*/
#include "XNMonitorInterface.h"
#include "TopicManager.h"
#include <string>
#include <mutex>
#include "SystemInfoMonitor.h"
#include "ModelInfoMonitor.h"
#include "SystemControl.h"
// 全局变量
static bool g_initialized = false;
@ -16,6 +14,8 @@ SystemInfoMonitor *systemInfoMonitor = nullptr;
bool g_systemInfoMonitorStarted = false;
ModelInfoMonitor *modelInfoMonitor = nullptr;
bool g_modelInfoMonitorStarted = false;
SystemControl *systemControl = nullptr;
bool g_systemControlStarted = false;
// 初始化函数实现
int XN_Initialize(const char *domainId, int domainIdLen, char *errorMsg, int errorMsgSize)
@ -288,6 +288,71 @@ void XN_Cleanup()
}
}
int XN_InitializeEngineControl(char *errorMsg, int errorMsgSize)
{
if (g_systemControlStarted) {
if (errorMsg && errorMsgSize > 0) {
strncpy(errorMsg, "Engine Control Already Started", errorMsgSize - 1);
errorMsg[errorMsgSize - 1] = '\0';
}
return 0;
}
systemControl = new SystemControl();
std::string ret = systemControl->Initialize();
if (ret == "Success") {
g_systemControlStarted = true;
if (errorMsg && errorMsgSize > 0) {
strncpy(errorMsg, "Engine Control Initialized Successfully", errorMsgSize - 1);
errorMsg[errorMsgSize - 1] = '\0';
}
return 0;
} else {
delete systemControl;
systemControl = nullptr;
if (errorMsg && errorMsgSize > 0) {
strncpy(errorMsg, ret.c_str(), errorMsgSize - 1);
errorMsg[errorMsgSize - 1] = '\0';
}
return -1;
}
}
void XN_PauseEngine(char *errorMsg, int errorMsgSize)
{
if (!g_systemControlStarted) {
if (errorMsg && errorMsgSize > 0) {
strncpy(errorMsg, "Engine Control Not Started", errorMsgSize - 1);
errorMsg[errorMsgSize - 1] = '\0';
}
return;
}
systemControl->Pause();
}
void XN_ResumeEngine(char *errorMsg, int errorMsgSize)
{
if (!g_systemControlStarted) {
if (errorMsg && errorMsgSize > 0) {
strncpy(errorMsg, "Engine Control Not Started", errorMsgSize - 1);
errorMsg[errorMsgSize - 1] = '\0';
}
return;
}
systemControl->Resume();
}
void XN_StopEngine(char *errorMsg, int errorMsgSize)
{
if (!g_systemControlStarted) {
if (errorMsg && errorMsgSize > 0) {
strncpy(errorMsg, "Engine Control Not Started", errorMsgSize - 1);
errorMsg[errorMsgSize - 1] = '\0';
}
return;
}
systemControl->Stop();
}
// // 注册发布者实现
// XNDDSErrorCode XN_RegisterPublisher(const char *topicName, void **dataWriter)
// {

View File

@ -40,6 +40,18 @@ extern "C"
// 停止监控模型信息
void XNMONITORSERVER_EXPORT XN_StopMonitorModelInfo();
// 初始化引擎控制
int XNMONITORSERVER_EXPORT XN_InitializeEngineControl(char *errorMsg, int errorMsgSize);
// 暂停引擎
void XNMONITORSERVER_EXPORT XN_PauseEngine(char *errorMsg, int errorMsgSize);
// 恢复引擎
void XNMONITORSERVER_EXPORT XN_ResumeEngine(char *errorMsg, int errorMsgSize);
// 停止引擎
void XNMONITORSERVER_EXPORT XN_StopEngine(char *errorMsg, int errorMsgSize);
// // 主题管理接口
// XNDDSErrorCode XN_RegisterPublisher(const char *topicName, void **dataWriter);
// XNDDSErrorCode XN_UnregisterPublisher(const char *topicName);

View File

@ -10,6 +10,7 @@ class RunSim extends HTMLElement {
this.currentSimulationId = null;
this.reconnectAttempts = 0;
this.maxReconnectAttempts = 3;
this.isPaused = false;
}
connectedCallback() {
@ -286,10 +287,68 @@ class RunSim extends HTMLElement {
}
// 连接到SSE事件源获取实时输出
async checkAndConnectToExistingSimulation() {
try {
const response = await fetch('/api/check-xnengine');
const data = await response.json();
if (data.running) {
this.showMessage('检测到正在运行的仿真,正在重新连接...');
// 更新UI以反映运行状态
const runButton = this.shadowRoot.querySelector('#run-simulation-button');
const pauseButton = this.shadowRoot.querySelector('#pause-simulation-button');
const stopButton = this.shadowRoot.querySelector('#stop-simulation-button');
if (runButton) {
runButton.disabled = true;
runButton.textContent = '运行中...';
}
if (pauseButton) {
pauseButton.disabled = false;
}
if (stopButton) {
stopButton.disabled = false;
}
// 使用进程ID作为仿真ID
this.currentSimulationId = data.pid.toString();
// 清空并初始化输出框
const outputContent = this.shadowRoot.querySelector('#output-content');
outputContent.innerHTML = '重新连接到运行中的仿真...\n';
// 检查现有SSE连接是否有效
if (!this.eventSource || this.eventSource.readyState === 2) { // 2表示CLOSED
// 只有在没有有效连接时才创建新连接
this.connectToEventSource(this.currentSimulationId);
}
// 检查引擎的暂停状态
pauseButton.textContent = this.isPaused ? '继续仿真' : '暂停仿真';
// 更新状态为已连接
this.showSuccess('已连接到运行中的仿真');
// 重置重连尝试次数
this.reconnectAttempts = 0;
} else {
// 如果没有运行中的仿真重置UI
this.resetSimulationButtons();
}
} catch (error) {
console.error('检查XNEngine进程失败:', error);
this.showError('检查仿真状态失败: ' + error.message);
this.resetSimulationButtons();
}
}
connectToEventSource(simulationId) {
// 关闭之前的连接
if (this.eventSource) {
this.eventSource.close();
// 只有在没有有效连接时才创建新连接
if (this.eventSource && this.eventSource.readyState !== 2) { // 2表示CLOSED
return; // 如果连接有效,直接返回
}
// 创建新的SSE连接
@ -452,6 +511,57 @@ class RunSim extends HTMLElement {
const outputContent = this.shadowRoot.querySelector('#output-content');
outputContent.innerHTML = '开始执行仿真...\n';
// 获取构型参数
const configResponse = await fetch(`/api/configurations/${confID}`);
if (!configResponse.ok) {
throw new Error('获取构型参数失败');
}
const configData = await configResponse.json();
// 从构型参数中提取域ID
const domainId = configData.DomainID;
if (!domainId) {
throw new Error('构型参数中未找到有效的域ID');
}
// 生成唯一的监控器ID
const monitorId = `sim_${Date.now()}`;
// 初始化DDS监控
const ddsInitResponse = await fetch('/api/dds-monitor/initialize', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ domainId, monitorId })
});
if (!ddsInitResponse.ok) {
throw new Error('初始化DDS监控失败');
}
const ddsInitResult = await ddsInitResponse.json();
if (ddsInitResult.error) {
throw new Error(ddsInitResult.error);
}
// 初始化引擎控制
const initResponse = await fetch('/api/system-control/initialize', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
}
});
if (!initResponse.ok) {
throw new Error('初始化引擎控制失败');
}
const initResult = await initResponse.json();
if (!initResult.success) {
throw new Error(initResult.message || '初始化引擎控制失败');
}
// 准备启动参数
const simulationArgs = ['-id', confID];
@ -506,59 +616,6 @@ class RunSim extends HTMLElement {
}
}
// 添加新方法:检查并连接到已有的仿真
async checkAndConnectToExistingSimulation() {
try {
const response = await fetch('/api/check-xnengine');
const data = await response.json();
if (data.running) {
this.showMessage('检测到正在运行的仿真,正在重新连接...');
// 更新UI以反映运行状态
const runButton = this.shadowRoot.querySelector('#run-simulation-button');
const pauseButton = this.shadowRoot.querySelector('#pause-simulation-button');
const stopButton = this.shadowRoot.querySelector('#stop-simulation-button');
if (runButton) {
runButton.disabled = true;
runButton.textContent = '运行中...';
}
if (pauseButton) {
pauseButton.disabled = false;
}
if (stopButton) {
stopButton.disabled = false;
}
// 使用进程ID作为仿真ID重新连接
this.currentSimulationId = data.pid.toString();
// 清空并初始化输出框
const outputContent = this.shadowRoot.querySelector('#output-content');
outputContent.innerHTML = '重新连接到运行中的仿真...\n';
// 连接到SSE获取输出
this.connectToEventSource(this.currentSimulationId);
// 更新状态为已连接
this.showSuccess('已连接到运行中的仿真');
// 重置重连尝试次数
this.reconnectAttempts = 0;
} else {
// 如果没有运行中的仿真重置UI
this.resetSimulationButtons();
}
} catch (error) {
console.error('检查XNEngine进程失败:', error);
this.showError('检查仿真状态失败: ' + error.message);
this.resetSimulationButtons();
}
}
// 添加reactivate方法用于从缓存中恢复时检查仿真状态
async reactivate() {
// 检查是否有XNEngine进程在运行
@ -971,27 +1028,41 @@ class RunSim extends HTMLElement {
try {
const button = this.shadowRoot.querySelector('#pause-simulation-button');
const isPaused = button.textContent === '继续仿真';
// 调用后端API暂停/继续仿真
const response = await fetch('/api/pause-simulation', {
// 检查DDS状态
const ddsStatusResponse = await fetch('/api/dds-monitor/status');
if (!ddsStatusResponse.ok) {
throw new Error('获取DDS状态失败');
}
const ddsStatus = await ddsStatusResponse.json();
if (!ddsStatus.isInitialized) {
throw new Error('DDS监控未初始化');
}
// 根据当前暂停状态调用不同的接口
const apiEndpoint = this.isPaused ? '/api/system-control/resume' : '/api/system-control/pause';
const response = await fetch(apiEndpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
id: simulationId,
pause: !isPaused
})
}
});
if (!response.ok) {
throw new Error('操作失败');
}
const result = await response.json();
if (!result.success) {
throw new Error(result.message || '操作失败');
}
// 更新暂停状态
this.isPaused = !this.isPaused;
// 更新按钮状态
button.textContent = isPaused ? '暂停仿真' : '继续仿真';
this.showMessage(isPaused ? '仿真已继续' : '仿真已暂停');
button.textContent = this.isPaused ? '继续仿真' : '暂停仿真';
this.showMessage(this.isPaused ? '仿真已暂停' : '仿真已继续');
} catch (error) {
console.error('暂停/继续仿真失败:', error);
this.showError('操作失败: ' + error.message);
@ -1003,8 +1074,39 @@ class RunSim extends HTMLElement {
if (!simulationId) return;
try {
// 调用后端API停止仿真
const response = await fetch('/api/stop-simulation', {
// 检查DDS状态
const ddsStatusResponse = await fetch('/api/dds-monitor/status');
if (!ddsStatusResponse.ok) {
throw new Error('获取DDS状态失败');
}
const ddsStatus = await ddsStatusResponse.json();
if (!ddsStatus.isInitialized) {
throw new Error('DDS监控未初始化');
}
// 首先使用新的引擎控制接口
const response = await fetch('/api/system-control/stop', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
}
});
if (!response.ok) {
throw new Error('停止引擎失败');
}
const result = await response.json();
if (!result.success) {
throw new Error(result.message || '停止引擎失败');
}
// 等待5秒
await new Promise(resolve => setTimeout(resolve, 5000));
// 调用老接口确保完全停止
const fallbackResponse = await fetch('/api/stop-simulation', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
@ -1014,8 +1116,8 @@ class RunSim extends HTMLElement {
})
});
if (!response.ok) {
throw new Error('停止仿真失败');
if (!fallbackResponse.ok) {
console.warn('调用老接口停止仿真失败,但引擎已停止');
}
// 关闭SSE连接
@ -1044,6 +1146,7 @@ class RunSim extends HTMLElement {
pauseButton.disabled = true;
stopButton.disabled = true;
pauseButton.textContent = '暂停仿真';
this.isPaused = false;
}
}

View File

@ -0,0 +1,99 @@
const express = require('express');
const router = express.Router();
const {
initializeEngineControl,
pauseEngine,
resumeEngine,
stopEngine
} = require('../utils/systemMonitor');
/**
* @brief 初始化引擎控制
* @route POST /api/system-control/initialize
* @returns {Object} 返回初始化结果
*/
router.post('/initialize', async (req, res) => {
try {
const result = initializeEngineControl();
if (result.includes('失败')) {
res.status(500).json({
success: false,
message: result
});
return;
}
res.json({
success: true,
message: result
});
} catch (error) {
res.status(500).json({
success: false,
message: `初始化引擎控制失败: ${error.message}`
});
}
});
/**
* @brief 暂停引擎
* @route POST /api/system-control/pause
* @returns {Object} 返回暂停操作结果
*/
router.post('/pause', async (req, res) => {
try {
const result = pauseEngine();
res.json({
success: !result.includes('失败'),
message: result
});
} catch (error) {
res.status(500).json({
success: false,
message: `暂停引擎失败: ${error.message}`
});
}
});
/**
* @brief 恢复引擎
* @route POST /api/system-control/resume
* @returns {Object} 返回恢复操作结果
*/
router.post('/resume', async (req, res) => {
try {
const result = resumeEngine();
res.json({
success: !result.includes('失败'),
message: result
});
} catch (error) {
res.status(500).json({
success: false,
message: `恢复引擎失败: ${error.message}`
});
}
});
/**
* @brief 停止引擎
* @route POST /api/system-control/stop
* @returns {Object} 返回停止操作结果
*/
router.post('/stop', async (req, res) => {
try {
const result = stopEngine();
res.json({
success: !result.includes('失败'),
message: result
});
} catch (error) {
res.status(500).json({
success: false,
message: `停止引擎失败: ${error.message}`
});
}
});
module.exports = router;

View File

@ -29,6 +29,7 @@ const systemLogRoutes = require('./routes/system-log');
const ddsMonitorRoutes = require('./routes/DDSMonitor');
const systemMonitorRoutes = require('./routes/SystemMonitor');
const modelMonitorRoutes = require('./routes/ModelMonitor');
const systemControlRoutes = require('./routes/SystemControl');
const app = express();
const PORT = process.env.PORT || 3000;
@ -99,6 +100,7 @@ app.use('/api/system-log', systemLogRoutes);
app.use('/api/dds-monitor', ddsMonitorRoutes);
app.use('/api/system-monitor', systemMonitorRoutes);
app.use('/api/model-monitor', modelMonitorRoutes);
app.use('/api/system-control', systemControlRoutes);
// 接口配置页面路由
app.get('/interface-config', (req, res) => {

View File

@ -49,7 +49,11 @@ try {
'XN_StopMonitorSystemInfo': ['void', []],
'XN_StartMonitorModelInfo': ['int', [StringType, 'int']],
'XN_GetModelInfo': ['int', [StringType, 'int']],
'XN_StopMonitorModelInfo': ['void', []]
'XN_StopMonitorModelInfo': ['void', []],
'XN_InitializeEngineControl': ['int', [StringType, 'int']],
'XN_PauseEngine': ['void', [StringType, 'int']],
'XN_ResumeEngine': ['void', [StringType, 'int']],
'XN_StopEngine': ['void', [StringType, 'int']]
});
} catch (error) {
console.error(`加载 ${monitorLibName} 失败:`, error);
@ -225,6 +229,68 @@ function stopMonitorModelInfo() {
}
}
// 初始化引擎控制
function initializeEngineControl() {
if (!monitorLib) {
return '监控服务器库未加载';
}
try {
const errorMsg = Buffer.alloc(1024);
const result = monitorLib.XN_InitializeEngineControl(errorMsg, errorMsg.length);
if (result !== 0) {
return `初始化引擎控制失败: ${errorMsg.toString('utf8').replace(/\0/g, '')}`;
}
return '初始化引擎控制成功';
} catch (error) {
return `初始化引擎控制失败: ${error.message}`;
}
}
// 暂停引擎
function pauseEngine() {
if (!monitorLib) {
return '监控服务器库未加载';
}
try {
const errorMsg = Buffer.alloc(1024);
monitorLib.XN_PauseEngine(errorMsg, errorMsg.length);
const error = errorMsg.toString('utf8').replace(/\0/g, '');
return error ? `暂停引擎失败: ${error}` : '暂停引擎成功';
} catch (error) {
return `暂停引擎失败: ${error.message}`;
}
}
// 恢复引擎
function resumeEngine() {
if (!monitorLib) {
return '监控服务器库未加载';
}
try {
const errorMsg = Buffer.alloc(1024);
monitorLib.XN_ResumeEngine(errorMsg, errorMsg.length);
const error = errorMsg.toString('utf8').replace(/\0/g, '');
return error ? `恢复引擎失败: ${error}` : '恢复引擎成功';
} catch (error) {
return `恢复引擎失败: ${error.message}`;
}
}
// 停止引擎
function stopEngine() {
if (!monitorLib) {
return '监控服务器库未加载';
}
try {
const errorMsg = Buffer.alloc(1024);
monitorLib.XN_StopEngine(errorMsg, errorMsg.length);
const error = errorMsg.toString('utf8').replace(/\0/g, '');
return error ? `停止引擎失败: ${error}` : '停止引擎成功';
} catch (error) {
return `停止引擎失败: ${error.message}`;
}
}
module.exports = {
loginLib,
monitorLib,
@ -238,5 +304,9 @@ module.exports = {
stopMonitorSystemInfo,
startMonitorModelInfo,
getModelInfo,
stopMonitorModelInfo
stopMonitorModelInfo,
initializeEngineControl,
pauseEngine,
resumeEngine,
stopEngine
};