Browse Source

feat:增强 WebSocket 处理机制,新增高级设置管理功能

- 新增 WebSocket 就绪状态标识,确保仅在连接完全建立后发送消息。
- 实现消息队列功能,用于处理 WebSocket 就绪前发送的消息。
- 升级 AdvancedSettings 组件,通过自定义钩子函数管理设备配置,涵盖配置的获取与保存功能。
- 新增完善的 WebSocket 指令处理逻辑,支持设备编号、数据帧率、告警配置、过滤规则及 MQTT 配置的相关指令。
- 优化用户反馈体验,添加加载状态指示器及连接状态告警提示。
- 编写高级设置中 WebSocket 协议的完整使用文档。
- 将 package.json 文件中的版本号更新至 1.1.0。
master
qinjian 3 weeks ago
parent
commit
505aa3f199
  1. 113
      client/src/sections/wuyuanbiaoba/actions/websocket.jsx
  2. 338
      client/src/sections/wuyuanbiaoba/components/AdvancedSettings.jsx
  3. 1
      client/src/sections/wuyuanbiaoba/components/CameraView.jsx
  4. 3
      client/src/sections/wuyuanbiaoba/container/index.jsx
  5. 479
      client/src/sections/wuyuanbiaoba/hooks/useAdvancedSettings.js
  6. 246
      client/src/sections/wuyuanbiaoba/hooks/高级配置说明.md
  7. 2
      package.json
  8. 34
      server/tcpProxy/index.js

113
client/src/sections/wuyuanbiaoba/actions/websocket.jsx

@ -125,6 +125,7 @@ const WebSocketContext = createContext();
// WebSocket Provider // WebSocket Provider
export const WebSocketProvider = ({ children }) => { export const WebSocketProvider = ({ children }) => {
const [isConnected, setIsConnected] = useState(false); const [isConnected, setIsConnected] = useState(false);
const [isReady, setIsReady] = useState(false); //
const [connectionStatus, setConnectionStatus] = useState("disconnected"); const [connectionStatus, setConnectionStatus] = useState("disconnected");
const [lastMessage, setLastMessage] = useState(null); // const [lastMessage, setLastMessage] = useState(null); //
const [messageHistory, setMessageHistory] = useState([]); // const [messageHistory, setMessageHistory] = useState([]); //
@ -132,9 +133,12 @@ export const WebSocketProvider = ({ children }) => {
const reconnectTimeoutRef = useRef(null); const reconnectTimeoutRef = useRef(null);
const reconnectAttemptsRef = useRef(0); const reconnectAttemptsRef = useRef(0);
const subscriptionsRef = useRef(new Map()); // const subscriptionsRef = useRef(new Map()); //
const messageQueueRef = useRef([]); //
const readyCheckTimeoutRef = useRef(null); //
const maxReconnectAttempts = 5; const maxReconnectAttempts = 5;
const reconnectInterval = 3000; // 3 const reconnectInterval = 3000; // 3
const maxHistoryLength = 100; // const maxHistoryLength = 100; //
const READY_TIMEOUT = 1500; // 1.5TCP
// WebSocket访 // WebSocket访
const getWebSocketUrl = () => { const getWebSocketUrl = () => {
@ -217,6 +221,24 @@ export const WebSocketProvider = ({ children }) => {
return; return;
} }
//
if (parsedData._from === 'proxy' && parsedData.cmd === 'ready') {
console.log('🟢 收到TCP连接就绪信号');
//
if (readyCheckTimeoutRef.current) {
clearTimeout(readyCheckTimeoutRef.current);
readyCheckTimeoutRef.current = null;
}
//
setIsReady(true);
//
flushMessageQueue();
return;
}
// console.log(` [${parsedData._from}:${parsedData.cmd}]:`, parsedData); // console.log(` [${parsedData._from}:${parsedData.cmd}]:`, parsedData);
} catch (error) { } catch (error) {
console.error("解析WebSocket消息失败:", error, data); console.error("解析WebSocket消息失败:", error, data);
@ -243,22 +265,60 @@ export const WebSocketProvider = ({ children }) => {
[notifySubscribers] [notifySubscribers]
); );
//
const flushMessageQueue = useCallback(() => {
if (messageQueueRef.current.length === 0) return;
console.log(`📤 开始发送队列中的 ${messageQueueRef.current.length} 条消息`);
const queue = [...messageQueueRef.current];
messageQueueRef.current = [];
queue.forEach((message, index) => {
setTimeout(() => {
if (socketRef.current?.readyState === WebSocket.OPEN) {
try {
socketRef.current.send(message);
console.log(`✅ 队列消息 ${index + 1}/${queue.length} 已发送`);
} catch (error) {
console.error(`❌ 发送队列消息 ${index + 1} 失败:`, error);
//
messageQueueRef.current.push(message);
}
}
}, index * 100); // 100ms
});
}, []);
// //
const sendMessage = useCallback((message) => { const sendMessage = useCallback((message) => {
if (socketRef.current?.readyState === WebSocket.OPEN) { //
if (socketRef.current?.readyState === WebSocket.OPEN && isReady) {
try { try {
socketRef.current.send(message); socketRef.current.send(message);
// console.log('WebSocket:', message);
return true; return true;
} catch (error) { } catch (error) {
console.error("发送WebSocket消息失败:", error); console.error("发送WebSocket消息失败:", error);
return false; return false;
} }
} else {
console.warn("WebSocket未连接,无法发送消息");
return false;
} }
}, []); //
else if (socketRef.current?.readyState === WebSocket.OPEN && !isReady) {
const cmdMatch = message.match(/"cmd"\s*:\s*"([^"]+)"/);
const cmd = cmdMatch ? cmdMatch[1] : 'unknown';
console.log(`⏳ 连接尚未完全就绪,消息 [${cmd}] 已加入队列`);
messageQueueRef.current.push(message);
return true;
}
//
else {
const cmdMatch = message.match(/"cmd"\s*:\s*"([^"]+)"/);
const cmd = cmdMatch ? cmdMatch[1] : 'unknown';
console.warn(`⚠️ WebSocket未连接,消息 [${cmd}] 已加入队列,等待连接建立`);
messageQueueRef.current.push(message);
return true;
}
}, [isReady]);
// WebSocket // WebSocket
const connect = useCallback(() => { const connect = useCallback(() => {
@ -267,23 +327,24 @@ export const WebSocketProvider = ({ children }) => {
} }
setConnectionStatus("connecting"); setConnectionStatus("connecting");
setIsReady(false); //
console.log("尝试连接WebSocket:", websocketUrl); console.log("尝试连接WebSocket:", websocketUrl);
try { try {
socketRef.current = new WebSocket(websocketUrl); socketRef.current = new WebSocket(websocketUrl);
socketRef.current.onopen = () => { socketRef.current.onopen = () => {
console.log("WebSocket连接已建立"); console.log("WebSocket连接已建立");
setIsConnected(true); setIsConnected(true);
setConnectionStatus("connected"); setConnectionStatus("connected");
reconnectAttemptsRef.current = 0; reconnectAttemptsRef.current = 0;
// // TCP
// sendMessage(JSON.stringify({ // ready
// _from: 'setup', readyCheckTimeoutRef.current = setTimeout(() => {
// cmd: 'init', setIsReady(true);
// values: { timestamp: Date.now() } flushMessageQueue();
// })); }, READY_TIMEOUT);
}; };
socketRef.current.onmessage = (event) => { socketRef.current.onmessage = (event) => {
@ -300,21 +361,28 @@ export const WebSocketProvider = ({ children }) => {
}; };
socketRef.current.onclose = (event) => { socketRef.current.onclose = (event) => {
console.log("WebSocket连接已关闭:", event.code, event.reason); console.log("WebSocket连接已关闭:", event.code, event.reason);
setIsConnected(false); setIsConnected(false);
setIsReady(false); //
setConnectionStatus("disconnected"); setConnectionStatus("disconnected");
//
if (readyCheckTimeoutRef.current) {
clearTimeout(readyCheckTimeoutRef.current);
readyCheckTimeoutRef.current = null;
}
// //
if (reconnectAttemptsRef.current < maxReconnectAttempts) { if (reconnectAttemptsRef.current < maxReconnectAttempts) {
reconnectAttemptsRef.current++; reconnectAttemptsRef.current++;
console.log( console.log(
`尝试重连 ${reconnectAttemptsRef.current}/${maxReconnectAttempts}` `🔄 尝试重连 ${reconnectAttemptsRef.current}/${maxReconnectAttempts}`
); );
reconnectTimeoutRef.current = setTimeout(() => { reconnectTimeoutRef.current = setTimeout(() => {
connect(); connect();
}, reconnectInterval); }, reconnectInterval);
} else { } else {
console.log("达到最大重连次数,停止重连"); console.log("🛑 达到最大重连次数,停止重连");
setConnectionStatus("error"); setConnectionStatus("error");
} }
}; };
@ -326,8 +394,9 @@ export const WebSocketProvider = ({ children }) => {
} catch (error) { } catch (error) {
console.error("创建WebSocket连接失败:", error); console.error("创建WebSocket连接失败:", error);
setConnectionStatus("error"); setConnectionStatus("error");
setIsReady(false);
} }
}, [handleMessage, sendMessage]); }, [handleMessage, flushMessageQueue]);
// //
const disconnect = useCallback(() => { const disconnect = useCallback(() => {
@ -335,12 +404,18 @@ export const WebSocketProvider = ({ children }) => {
clearTimeout(reconnectTimeoutRef.current); clearTimeout(reconnectTimeoutRef.current);
} }
if (readyCheckTimeoutRef.current) {
clearTimeout(readyCheckTimeoutRef.current);
readyCheckTimeoutRef.current = null;
}
if (socketRef.current) { if (socketRef.current) {
socketRef.current.close(1000, "用户主动断开"); socketRef.current.close(1000, "用户主动断开");
socketRef.current = null; socketRef.current = null;
} }
setIsConnected(false); setIsConnected(false);
setIsReady(false);
setConnectionStatus("disconnected"); setConnectionStatus("disconnected");
reconnectAttemptsRef.current = 0; reconnectAttemptsRef.current = 0;
}, []); }, []);
@ -373,6 +448,9 @@ export const WebSocketProvider = ({ children }) => {
if (reconnectTimeoutRef.current) { if (reconnectTimeoutRef.current) {
clearTimeout(reconnectTimeoutRef.current); clearTimeout(reconnectTimeoutRef.current);
} }
if (readyCheckTimeoutRef.current) {
clearTimeout(readyCheckTimeoutRef.current);
}
if (socketRef.current) { if (socketRef.current) {
socketRef.current.close(); socketRef.current.close();
} }
@ -383,6 +461,7 @@ export const WebSocketProvider = ({ children }) => {
const value = { const value = {
// //
isConnected, isConnected,
isReady, //
connectionStatus, connectionStatus,
// //

338
client/src/sections/wuyuanbiaoba/components/AdvancedSettings.jsx

@ -1,4 +1,4 @@
import React, { useState } from "react"; import React, { useEffect } from "react";
import { import {
Card, Card,
Input, Input,
@ -12,6 +12,8 @@ import {
message, message,
Space, Space,
Typography, Typography,
Spin,
Alert,
} from "antd"; } from "antd";
import { import {
SettingOutlined, SettingOutlined,
@ -22,122 +24,251 @@ import {
BellOutlined, BellOutlined,
FilterOutlined, FilterOutlined,
CloudUploadOutlined, CloudUploadOutlined,
SyncOutlined,
} from "@ant-design/icons"; } from "@ant-design/icons";
import useAdvancedSettings from "../hooks/useAdvancedSettings";
const { Option } = Select; const { Option } = Select;
const { Title, Text } = Typography; const { Title, Text } = Typography;
const AdvancedSettings = ({ onLogout }) => { const AdvancedSettings = ({ onLogout }) => {
const [deviceId, setDeviceId] = useState(""); // 使 Hook
const [fps, setFps] = useState(5); const {
const [enableOfflineAlert, setEnableOfflineAlert] = useState(false); settings,
const [offlineThreshold, setOfflineThreshold] = useState(60); loading,
const [enableFiltering, setEnableFiltering] = useState(true); isConnected,
const [filterMethod, setFilterMethod] = useState("median"); isReady,
const [windowSize, setWindowSize] = useState(5); fetchAllSettings,
const [filterThreshold, setFilterThreshold] = useState(0.1); saveAllSettings,
const [flowThreshold, setFlowThreshold] = useState(10.0); resetSettings,
const [enableMqtt, setEnableMqtt] = useState(true); updateLocalSettings,
const [brokerAddress, setBrokerAddress] = useState(""); } = useAdvancedSettings();
const [mqttPort, setMqttPort] = useState("");
const [mqttTopic, setMqttTopic] = useState(""); // settings
const [mqttClientId, setMqttClientId] = useState(""); const deviceId = settings.deviceId;
const [mqttUsername, setMqttUsername] = useState(""); const fps = settings.dataFps;
const [mqttPassword, setMqttPassword] = useState(""); const enableOfflineAlert = settings.alertConfig?.enable ?? false;
const offlineThreshold = settings.alertConfig?.intervalSec ?? 60;
const enableFiltering = settings.filterConfig?.enable ?? true;
const filterMethod = settings.filterConfig?.method ?? 'median';
const windowSize = settings.filterConfig?.size ?? 5;
const filterThreshold = Math.abs(settings.filterConfig?.threshold ?? 0.1);
const flowThreshold = settings.filterConfig?.imgThreshold ?? 10.0;
const enableMqtt = settings.mqttConfig?.enable ?? true;
const brokerAddress = settings.mqttConfig?.mqtt?.broker ?? '';
const mqttPort = settings.mqttConfig?.mqtt?.port ?? 1883;
const mqttTopic = settings.mqttConfig?.mqtt?.topic ?? '';
const mqttClientId = settings.mqttConfig?.mqtt?.client_id ?? '';
const mqttUsername = settings.mqttConfig?.mqtt?.username ?? '';
const mqttPassword = settings.mqttConfig?.mqtt?.password ?? '';
//
const setDeviceId = (value) => updateLocalSettings({ deviceId: value });
const setFps = (value) => updateLocalSettings({ dataFps: value });
const setEnableOfflineAlert = (value) =>
updateLocalSettings({
alertConfig: {
enable: value,
intervalSec: settings.alertConfig?.intervalSec ?? 60
},
});
const setOfflineThreshold = (value) =>
updateLocalSettings({
alertConfig: {
enable: settings.alertConfig?.enable ?? false,
intervalSec: value
},
});
const setEnableFiltering = (value) =>
updateLocalSettings({
filterConfig: { ...settings.filterConfig, enable: value },
});
const setFilterMethod = (value) =>
updateLocalSettings({
filterConfig: { ...settings.filterConfig, method: value },
});
const setWindowSize = (value) =>
updateLocalSettings({
filterConfig: { ...settings.filterConfig, size: value },
});
const setFilterThreshold = (value) =>
updateLocalSettings({
filterConfig: { ...settings.filterConfig, threshold: -Math.abs(value) },
});
const setFlowThreshold = (value) =>
updateLocalSettings({
filterConfig: { ...settings.filterConfig, imgThreshold: value },
});
const setEnableMqtt = (value) =>
updateLocalSettings({
mqttConfig: {
enable: value,
mqtt: settings.mqttConfig?.mqtt ?? {
broker: '',
port: 1883,
topic: '',
username: '',
password: '',
client_id: ''
}
},
});
const setBrokerAddress = (value) =>
updateLocalSettings({
mqttConfig: {
...settings.mqttConfig,
mqtt: {
...(settings.mqttConfig?.mqtt ?? {}),
broker: value
},
},
});
const setMqttPort = (value) =>
updateLocalSettings({
mqttConfig: {
...settings.mqttConfig,
mqtt: {
...(settings.mqttConfig?.mqtt ?? {}),
port: value
},
},
});
const setMqttTopic = (value) =>
updateLocalSettings({
mqttConfig: {
...settings.mqttConfig,
mqtt: {
...(settings.mqttConfig?.mqtt ?? {}),
topic: value
},
},
});
const setMqttClientId = (value) =>
updateLocalSettings({
mqttConfig: {
...settings.mqttConfig,
mqtt: {
...(settings.mqttConfig?.mqtt ?? {}),
client_id: value
},
},
});
const setMqttUsername = (value) =>
updateLocalSettings({
mqttConfig: {
...settings.mqttConfig,
mqtt: {
...(settings.mqttConfig?.mqtt ?? {}),
username: value
},
},
});
const setMqttPassword = (value) =>
updateLocalSettings({
mqttConfig: {
...settings.mqttConfig,
mqtt: {
...(settings.mqttConfig?.mqtt ?? {}),
password: value
},
},
});
const handleSave = () => {
saveAllSettings();
};
const handleSave = () => message.success("配置已保存");
const handleReset = () => { const handleReset = () => {
setDeviceId(""); fetchAllSettings();
setFps(5);
setEnableOfflineAlert(false);
setOfflineThreshold(60);
setEnableFiltering(true);
setFilterMethod("median");
setWindowSize(5);
setFilterThreshold(0.1);
setFlowThreshold(10.0);
setEnableMqtt(true);
setBrokerAddress("");
setMqttPort("");
setMqttTopic("");
setMqttClientId("");
setMqttUsername("");
setMqttPassword("");
message.info("配置已重置");
}; };
return ( return (
<div <Spin spinning={loading} tip="正在加载配置...">
style={{ <div
padding: "12px",
backgroundColor: "#f5f7fa",
minHeight: "calc(100vh - 92px)",
}}
>
<Card
style={{ style={{
marginBottom: 12, padding: "12px",
background: backgroundColor: "#f5f7fa",
"linear-gradient(135deg, #01152cff 0%, #063b77ff 100%)", minHeight: "calc(100vh - 92px)",
borderRadius: 12,
}} }}
styles={{ padding: 12 }}
> >
<Row justify="space-between" align="middle"> {/* WebSocket 连接状态提示 */}
<Col> {!isConnected && (
<Space direction="vertical" size={4}> <Alert
<Title message="WebSocket 未连接"
level={2} description="当前未连接到设备,无法读取或保存配置。请检查网络连接。"
style={{ type="warning"
margin: 0, showIcon
color: "white", style={{ marginBottom: 12, borderRadius: 8 }}
display: "flex", />
alignItems: "center", )}
gap: 12,
}} <Card
> style={{
<SettingOutlined /> marginBottom: 12,
高级参数配置 background:
</Title> "linear-gradient(135deg, #01152cff 0%, #063b77ff 100%)",
<Text borderRadius: 12,
style={{ }}
color: "rgba(255,255,255,0.85)", styles={{ padding: 12 }}
fontSize: 14, >
}} <Row justify="space-between" align="middle">
> <Col>
针对下位机运行的参数配置修改后请及时保存(慎用) <Space direction="vertical" size={4}>
</Text> <Title
</Space> level={2}
</Col> style={{
<Col> margin: 0,
<Space size="middle"> color: "white",
<Button display: "flex",
icon={<ReloadOutlined />} alignItems: "center",
onClick={handleReset} gap: 12,
size="large" }}
style={{ height: 42, borderRadius: 8 }} >
> <SettingOutlined />
重置/刷新 高级参数配置
</Button> </Title>
<Button <Text
type="primary" style={{
icon={<SaveOutlined />} color: "rgba(255,255,255,0.85)",
onClick={handleSave} fontSize: 14,
size="large" }}
style={{ >
height: 42, 针对下位机运行的参数配置修改后请及时保存(慎用)
borderRadius: 8, </Text>
background: "white",
color: "#667eea", </Space>
borderColor: "white", </Col>
}} <Col>
> <Space size="middle">
保存更改 <Button
</Button> icon={<SyncOutlined spin={loading} />}
</Space> onClick={handleReset}
</Col> size="large"
</Row> disabled={!isConnected || loading}
</Card> style={{ height: 42, borderRadius: 8 }}
>
刷新配置
</Button>
<Button
type="primary"
icon={<SaveOutlined />}
onClick={handleSave}
size="large"
disabled={!isConnected || loading}
style={{
height: 42,
borderRadius: 8,
background: "white",
color: "#667eea",
borderColor: "white",
}}
>
保存更改
</Button>
</Space>
</Col>
</Row>
</Card>
<Row gutter={12}> <Row gutter={12}>
<Col xs={12} lg={12}> <Col xs={12} lg={12}>
@ -487,6 +618,7 @@ const AdvancedSettings = ({ onLogout }) => {
</Col> </Col>
</Row> </Row>
</div> </div>
</Spin>
); );
}; };

1
client/src/sections/wuyuanbiaoba/components/CameraView.jsx

@ -59,7 +59,6 @@ const CameraView = ({
if (window.env && window.env.FS_FLAG === "localdev") { if (window.env && window.env.FS_FLAG === "localdev") {
streamUrl = `http://10.8.30.179:2240/video_flow`; // streamUrl = `http://10.8.30.179:2240/video_flow`; //
} }
console.log(streamUrl,'测试')
// //
const applyTransform = () => { const applyTransform = () => {

3
client/src/sections/wuyuanbiaoba/container/index.jsx

@ -27,7 +27,7 @@ const { Title } = Typography;
// 使WebSocket hook // 使WebSocket hook
const WuyuanbiaobaContent = () => { const WuyuanbiaobaContent = () => {
const { isConnected, sendMessage } = useWebSocket(); const { isConnected, isReady, sendMessage } = useWebSocket();
// //
const realtimeDataSubscription = useWebSocketSubscription("dev", "data"); const realtimeDataSubscription = useWebSocketSubscription("dev", "data");
@ -513,6 +513,7 @@ const WuyuanbiaobaContent = () => {
> >
<Title level={3} style={{ color: "#333", margin: 0 }}> <Title level={3} style={{ color: "#333", margin: 0 }}>
视觉位移计配置工具 视觉位移计配置工具
</Title> </Title>
<Menu <Menu
mode="horizontal" mode="horizontal"

479
client/src/sections/wuyuanbiaoba/hooks/useAdvancedSettings.js

@ -0,0 +1,479 @@
/**
* useAdvancedSettings Hook
*
* 高级配置管理 Hook用于管理设备的高级参数配置
*
* 功能包括
* - 设备编码读取与设置 (getId/setId)
* - 数据帧率读取与设置 (getDataFps/setDataFps)
* - 异常超时监控读取与设置 (getAlert/setAlert)
* - 滤波配置读取与设置 (getWin/setWin)
* - MQTT 上报配置读取与设置 (getMqtt/setMqtt)
*
* 使用示例
* ```jsx
* const {
* settings,
* loading,
* fetchAllSettings,
* updateDeviceId,
* updateDataFps,
* updateAlertConfig,
* updateFilterConfig,
* updateMqttConfig,
* } = useAdvancedSettings();
* ```
*/
import { useState, useEffect, useCallback } from 'react';
import { message } from 'antd';
import { useWebSocket, useWebSocketSubscription } from '../actions/websocket.jsx';
/**
* 高级配置默认值
*/
const DEFAULT_SETTINGS = {
// 设备编码
deviceId: '',
// 数据帧率
dataFps: 10,
// 异常监控配置
alertConfig: {
enable: false,
intervalSec: 60
},
// 滤波配置
filterConfig: {
enable: true,
method: 'median',
size: 5,
threshold: -0.1,
imgThreshold: 10.0
},
// MQTT 配置
mqttConfig: {
enable: true,
mqtt: {
broker: '',
port: 1883,
topic: '',
username: '',
password: '',
client_id: ''
}
}
};
/**
* 高级配置管理 Hook
*/
const useAdvancedSettings = () => {
const { isConnected, isReady, sendMessage } = useWebSocket();
// 配置状态
const [settings, setSettings] = useState(DEFAULT_SETTINGS);
const [loading, setLoading] = useState(false);
const [fetchStatus, setFetchStatus] = useState({});
// WebSocket 订阅 - 监听所有来自设备的响应
const { latest: getIdResponse } = useWebSocketSubscription('dev', 'getId');
const { latest: setIdResponse } = useWebSocketSubscription('dev', 'setId');
const { latest: getDataFpsResponse } = useWebSocketSubscription('dev', 'getDataFps');
const { latest: setDataFpsResponse } = useWebSocketSubscription('dev', 'setDataFps');
const { latest: getAlertResponse } = useWebSocketSubscription('dev', 'getAlert');
const { latest: setAlertResponse } = useWebSocketSubscription('dev', 'setAlert');
const { latest: getWinResponse } = useWebSocketSubscription('dev', 'getWin');
const { latest: setWinResponse } = useWebSocketSubscription('dev', 'setWin');
const { latest: getMqttResponse } = useWebSocketSubscription('dev', 'getMqtt');
const { latest: setMqttResponse } = useWebSocketSubscription('dev', 'setMqtt');
/**
* 处理设备编码读取响应
*/
useEffect(() => {
if (getIdResponse?.values?.id !== undefined) {
console.log('📥 收到设备编码:', getIdResponse.values.id);
setSettings(prev => ({
...prev,
deviceId: getIdResponse.values.id
}));
setFetchStatus(prev => ({ ...prev, deviceId: 'success' }));
}
}, [getIdResponse]);
/**
* 处理设备编码设置响应
*/
useEffect(() => {
if (setIdResponse?.values?.operate !== undefined) {
if (setIdResponse.values.operate) {
message.success('设备编码设置成功');
} else {
message.error('设备编码设置失败');
}
}
}, [setIdResponse]);
/**
* 处理数据帧率读取响应
*/
useEffect(() => {
if (getDataFpsResponse?.values?.dataFps !== undefined) {
console.log('📥 收到数据帧率:', getDataFpsResponse.values.dataFps);
setSettings(prev => ({
...prev,
dataFps: getDataFpsResponse.values.dataFps
}));
setFetchStatus(prev => ({ ...prev, dataFps: 'success' }));
}
}, [getDataFpsResponse]);
/**
* 处理数据帧率设置响应
*/
useEffect(() => {
if (setDataFpsResponse?.values?.operate !== undefined) {
if (setDataFpsResponse.values.operate) {
message.success('数据帧率设置成功');
} else {
message.error('数据帧率设置失败');
}
}
}, [setDataFpsResponse]);
/**
* 处理异常监控配置读取响应
*/
useEffect(() => {
if (getAlertResponse?.values) {
console.log('📥 收到异常监控配置:', getAlertResponse.values);
setSettings(prev => ({
...prev,
alertConfig: getAlertResponse.values
}));
setFetchStatus(prev => ({ ...prev, alert: 'success' }));
}
}, [getAlertResponse]);
/**
* 处理异常监控设置响应
*/
useEffect(() => {
if (setAlertResponse?.values?.operate !== undefined) {
if (setAlertResponse.values.operate) {
message.success('异常监控配置设置成功');
} else {
message.error('异常监控配置设置失败');
}
}
}, [setAlertResponse]);
/**
* 处理滤波配置读取响应
*/
useEffect(() => {
if (getWinResponse?.values) {
console.log('📥 收到滤波配置:', getWinResponse.values);
setSettings(prev => ({
...prev,
filterConfig: getWinResponse.values
}));
setFetchStatus(prev => ({ ...prev, filter: 'success' }));
}
}, [getWinResponse]);
/**
* 处理滤波配置设置响应
*/
useEffect(() => {
if (setWinResponse?.values?.operate !== undefined) {
if (setWinResponse.values.operate) {
message.success('滤波配置设置成功');
} else {
message.error('滤波配置设置失败');
}
}
}, [setWinResponse]);
/**
* 处理 MQTT 配置读取响应
*/
useEffect(() => {
if (getMqttResponse?.values) {
console.log('📥 收到 MQTT 配置:', getMqttResponse.values);
setSettings(prev => ({
...prev,
mqttConfig: getMqttResponse.values
}));
setFetchStatus(prev => ({ ...prev, mqtt: 'success' }));
}
}, [getMqttResponse]);
/**
* 处理 MQTT 配置设置响应
*/
useEffect(() => {
if (setMqttResponse?.values?.operate !== undefined) {
if (setMqttResponse.values.operate) {
message.success('MQTT 配置设置成功');
} else {
message.error('MQTT 配置设置失败');
}
}
}, [setMqttResponse]);
/**
* 发送 WebSocket 命令
*/
const sendCommand = useCallback((cmd, values = {}) => {
if (!isConnected) {
message.warning('WebSocket 未连接,请稍后重试');
return false;
}
const command = {
_from: 'setup',
cmd,
values
};
console.log('📤 发送命令:', command);
return sendMessage(JSON.stringify(command));
}, [isConnected, sendMessage]);
/**
* 获取设备编码
*/
const fetchDeviceId = useCallback(() => {
setFetchStatus(prev => ({ ...prev, deviceId: 'loading' }));
return sendCommand('getId', {});
}, [sendCommand]);
/**
* 更新设备编码
*/
const updateDeviceId = useCallback((id) => {
if (!id || !id.trim()) {
message.warning('请输入设备编码');
return false;
}
return sendCommand('setId', { id });
}, [sendCommand]);
/**
* 获取数据帧率
*/
const fetchDataFps = useCallback(() => {
setFetchStatus(prev => ({ ...prev, dataFps: 'loading' }));
return sendCommand('getDataFps', {});
}, [sendCommand]);
/**
* 更新数据帧率
*/
const updateDataFps = useCallback((dataFps) => {
if (dataFps < 1 || dataFps > 30) {
message.warning('数据帧率范围为 1-30 Hz');
return false;
}
return sendCommand('setDataFps', { dataFps });
}, [sendCommand]);
/**
* 获取异常监控配置
*/
const fetchAlertConfig = useCallback(() => {
setFetchStatus(prev => ({ ...prev, alert: 'loading' }));
return sendCommand('getAlert', {});
}, [sendCommand]);
/**
* 更新异常监控配置
*/
const updateAlertConfig = useCallback((alertConfig) => {
const { enable, intervalSec } = alertConfig;
return sendCommand('setAlert', { enable, intervalSec });
}, [sendCommand]);
/**
* 获取滤波配置
*/
const fetchFilterConfig = useCallback(() => {
setFetchStatus(prev => ({ ...prev, filter: 'loading' }));
return sendCommand('getWin', {});
}, [sendCommand]);
/**
* 更新滤波配置
*/
const updateFilterConfig = useCallback((filterConfig) => {
const { enable, method, size, threshold, imgThreshold } = filterConfig;
return sendCommand('setWin', {
enable,
method,
size,
threshold,
imgThreshold
});
}, [sendCommand]);
/**
* 获取 MQTT 配置
*/
const fetchMqttConfig = useCallback(() => {
setFetchStatus(prev => ({ ...prev, mqtt: 'loading' }));
return sendCommand('getMqtt', {});
}, [sendCommand]);
/**
* 更新 MQTT 配置
*/
const updateMqttConfig = useCallback((mqttConfig) => {
const { enable, mqtt } = mqttConfig;
// 验证必填字段
if (enable) {
if (!mqtt.broker || !mqtt.broker.trim()) {
message.warning('请输入 Broker 地址');
return false;
}
if (!mqtt.port) {
message.warning('请输入端口号');
return false;
}
if (!mqtt.topic || !mqtt.topic.trim()) {
message.warning('请输入 Topic');
return false;
}
if (!mqtt.client_id || !mqtt.client_id.trim()) {
message.warning('请输入 Client ID');
return false;
}
}
return sendCommand('setMqtt', { enable, mqtt });
}, [sendCommand]);
/**
* 获取所有配置
*/
const fetchAllSettings = useCallback(async () => {
if (!isConnected) {
message.warning('WebSocket 未连接,无法获取配置');
return;
}
if (!isReady) {
message.info('连接正在建立中,指令已加入队列...');
}
setLoading(true);
setFetchStatus({});
console.log('🔄 开始获取所有高级配置...');
// 依次发送所有读取命令
fetchDeviceId();
// 添加延迟避免命令过快
setTimeout(() => fetchDataFps(), 100);
setTimeout(() => fetchAlertConfig(), 200);
setTimeout(() => fetchFilterConfig(), 300);
setTimeout(() => fetchMqttConfig(), 400);
// 等待所有响应
setTimeout(() => {
setLoading(false);
console.log('✅ 配置获取完成');
}, 2000);
}, [isConnected, isReady, fetchDeviceId, fetchDataFps, fetchAlertConfig, fetchFilterConfig, fetchMqttConfig]);
/**
* 保存所有配置
*/
const saveAllSettings = useCallback(() => {
if (!isConnected) {
message.warning('WebSocket 未连接,无法保存配置');
return false;
}
if (!isReady) {
message.info('连接正在建立中,指令已加入队列...');
} else {
message.info('正在保存配置...');
}
// 依次发送所有设置命令
updateDeviceId(settings.deviceId);
setTimeout(() => updateDataFps(settings.dataFps), 100);
setTimeout(() => updateAlertConfig(settings.alertConfig), 200);
setTimeout(() => updateFilterConfig(settings.filterConfig), 300);
setTimeout(() => updateMqttConfig(settings.mqttConfig), 400);
return true;
}, [isConnected, isReady, settings, updateDeviceId, updateDataFps, updateAlertConfig, updateFilterConfig, updateMqttConfig]);
/**
* 重置配置到默认值
*/
const resetSettings = useCallback(() => {
setSettings(DEFAULT_SETTINGS);
message.info('配置已重置为默认值');
}, []);
/**
* 更新本地设置不发送到设备
*/
const updateLocalSettings = useCallback((updates) => {
setSettings(prev => ({
...prev,
...updates
}));
}, []);
/**
* WebSocket 连接建立后自动获取配置
*/
useEffect(() => {
if (isConnected) {
console.log('🔌 WebSocket 已连接,自动获取高级配置');
// 延迟获取,避免连接刚建立时发送命令
setTimeout(() => {
fetchAllSettings();
}, 500);
}
}, [isConnected, fetchAllSettings]);
return {
// 状态
settings,
loading,
fetchStatus,
isConnected,
isReady,
// 操作方法
fetchAllSettings,
saveAllSettings,
resetSettings,
updateLocalSettings,
// 单项操作
fetchDeviceId,
updateDeviceId,
fetchDataFps,
updateDataFps,
fetchAlertConfig,
updateAlertConfig,
fetchFilterConfig,
updateFilterConfig,
fetchMqttConfig,
updateMqttConfig,
};
};
export default useAdvancedSettings;

246
client/src/sections/wuyuanbiaoba/hooks/高级配置说明.md

@ -0,0 +1,246 @@
# 高级配置 WebSocket 协议对接文档
1. 主机编码读取指令
```json
{
"_from": "setup",
"cmd": "getId",
"values": {}
}
```
响应示例
```json
{
"_from": "dev",
"cmd": "getId",
"values": {
"id": "uu1234"
}
}
```
2. 主机编码设置指令
```json
{
"_from": "setup",
"cmd": "setId",
"values": { "id": "uu1234" }
}
```
响应示例
```json
{
"_from": "dev",
"cmd": "setId",
"values": { "operate": true }
}
```
3. 帧率读取指令
```json
{
"_from": "setup",
"cmd": "getDataFps",
"values": {}
}
```
响应示例
```json
{
"_from": "dev",
"cmd": "getDataFps",
"values": { "dataFps": 10 }
}
```
4. 帧率设置指令
```json
{
"_from": "setup",
"cmd": "setDataFps",
"values": { "dataFps": 10 }
}
```
响应示例
```json
{
"_from": "dev",
"cmd": "setDataFps",
"values": { "operate": true }
}
```
5. 异常超时监控-读取指令
```json
{
"_from": "setup",
"cmd": "getAlert",
"values": {}
}
```
响应示例
```json
{
"_from": "dev",
"cmd": "getAlert",
"values": {
"enable": false,
"intervalSec": 6
}
}
```
6. 异常超时监控-设置指令
```json
{
"_from": "setup",
"cmd": "setAlert",
"values": {
"enable": false,
"intervalSec": 6
}
}
```
响应示例
```json
{
"_from": "dev",
"cmd": "setAlert",
"values": {
"operate": true
}
}
```
7. 滤波-读取
```json
{
"_from": "setup",
"cmd":"getWin",
"values": {}
}
```
响应示例
```json
{
"_from": "dev",
"cmd": "getWin",
"values": {
"enable": true,
"method": "median",
"size": 5,
"threshold": -0.1,
"imgThreshold": 10.0
}
}
```
8. 滤波-设置
```json
{
"_from": "setup",
"cmd":"setWin",
"values": {
"enable": true,
"method": "median",
"size": 5,
"threshold": -0.1,
"imgThreshold": 10.0
}
}
```
响应示例
```json
{
"_from": "dev",
"cmd": "setWin",
"values": {
"operate": true
}
}
```
9. mqtt 上报读取
```json
{
"_from": "setup",
"cmd":"getMqtt",
"values": {}
}
```
响应示例
```json
{
"_from": "dev",
"cmd": "getMqtt",
"values": {
"mqtt": {
"broker": "218.3.126.49",
"port": 1883,
"topic": "wybb/zj/mqtt110_debug",
"username": "",
"password": "",
"client_id": "wybb_debug"
},
"enable": true
}
}
```
10. mqtt上报设置
```json
{
"_from": "setup",
"cmd": "setMqtt",
"values": {
"mqtt": {
"broker": "218.3.126.49",
"port": 1883,
"topic": "wybb/zj/mqtt110_debug",
"username": "",
"password": "",
"client_id": "wybb_debug"
},
"enable": true
}
}
```
响应示例
```json
{
"_from": "dev",
"cmd": "setMqtt",
"values": {
"operate": true
}
}
```
10. 异常超时监控-读取
```json
{
"_from": "setup",
"cmd":"getAlert",
"values": {}
}
```
响应示例
```json
{
"_from": "dev",
"cmd": "getAlert",
"values": {
"enable": false,
"intervalSec": 6
}
}
```

2
package.json

@ -1,6 +1,6 @@
{ {
"name": "wuyuanbiaoba-web", "name": "wuyuanbiaoba-web",
"version": "1.0.4", "version": "1.1.0",
"main": "index.html", "main": "index.html",
"scripts": { "scripts": {
"test": "echo \"Error: no test specified\" && exit 1", "test": "echo \"Error: no test specified\" && exit 1",

34
server/tcpProxy/index.js

@ -5,7 +5,7 @@ let TCP_HOST = '127.0.0.1'; // 因为下位机和上位机在同一台机器上
const TCP_PORT = 2230; const TCP_PORT = 2230;
// 创建独立的WebSocket服务器用于TCP代理 // 创建独立的WebSocket服务器用于TCP代理
function setupTcpProxy(conf) { function setupTcpProxy(conf) {
if(conf && conf.flag === 'localdev') { if (conf && conf.flag === 'localdev') {
TCP_HOST = '10.8.30.179' //本地开发配置 TCP_HOST = '10.8.30.179' //本地开发配置
} }
console.log(`TCP代理目标地址: ${TCP_HOST}:${TCP_PORT}`); console.log(`TCP代理目标地址: ${TCP_HOST}:${TCP_PORT}`);
@ -21,17 +21,32 @@ function setupTcpProxy(conf) {
}); });
wss.on('connection', (ws, request) => { wss.on('connection', (ws, request) => {
// console.log(`WebSocket连接建立,来自: ${request.socket.remoteAddress}`); // console.log(`WebSocket连接建立,来自: ${request.socket.remoteAddress}`);
// 创建TCP连接 // 创建TCP连接
const tcpClient = new net.Socket(); const tcpClient = new net.Socket();
// TCP数据缓冲区 // TCP数据缓冲区
let tcpDataBuffer = ''; let tcpDataBuffer = '';
let tcpConnected = false; // 新增:TCP连接状态标志
// TCP连接成功 // TCP连接成功
tcpClient.connect(process.env.TCP_PORT || TCP_PORT, process.env.TCP_HOST || TCP_HOST, () => { tcpClient.connect(process.env.TCP_PORT || TCP_PORT, process.env.TCP_HOST || TCP_HOST, () => {
// console.log(process.env); // console.log(process.env);
// console.log(`TCP连接已建立到 ${TCP_HOST}:${TCP_PORT}`); // console.log(`TCP连接已建立到 ${TCP_HOST}:${TCP_PORT}`);
tcpConnected = true;
// 向WebSocket客户端发送就绪信号
if (ws.readyState === WebSocket.OPEN) {
const readySignal = JSON.stringify({
_from: 'proxy',
cmd: 'ready',
values: {
message: 'TCP connection established',
timestamp: Date.now()
}
});
ws.send(readySignal);
}
}); });
// TCP接收数据,转发到WebSocket // TCP接收数据,转发到WebSocket
@ -40,9 +55,8 @@ function setupTcpProxy(conf) {
// 尝试解析为文本 // 尝试解析为文本
try { try {
textData = data.toString('utf8'); textData = data.toString('utf8');
// console.log('收到TCP数据片段:', textData.length, '字节');
} catch (e) { } catch (e) {
// console.log('TCP数据无法解析为文本'); console.log('TCP数据无法解析为文本');
return; return;
} }
@ -94,7 +108,7 @@ function setupTcpProxy(conf) {
// 转发字符串数据到TCP服务器 // 转发字符串数据到TCP服务器
if (tcpClient.writable) { if (tcpClient.writable) {
console.log('准备发送数据到TCP服务器:', messageStr); console.log('发送数据到TCP服务器:', messageStr);
// 检查数据大小 // 检查数据大小
const dataSize = Buffer.byteLength(messageStr, 'utf8'); const dataSize = Buffer.byteLength(messageStr, 'utf8');
@ -150,14 +164,14 @@ function setupTcpProxy(conf) {
}); });
wss.on('listening', () => { wss.on('listening', () => {
// console.log(`TCP代理WebSocket服务器已启动在端口 ${wsPort},路径: /tcp-proxy`); // console.log(`TCP代理WebSocket服务器已启动在端口 ${wsPort},路径: /tcp-proxy`);
// console.log(`局域网连接地址: ws://[本机IP]:${wsPort}/tcp-proxy`); // console.log(`局域网连接地址: ws://[本机IP]:${wsPort}/tcp-proxy`);
// console.log(`本地连接地址: ws://localhost:${wsPort}/tcp-proxy`); // console.log(`本地连接地址: ws://localhost:${wsPort}/tcp-proxy`);
// console.log(`注意:请确保防火墙允许端口 ${wsPort} 的访问`); // console.log(`注意:请确保防火墙允许端口 ${wsPort} 的访问`);
}); });
wss.on('error', (err) => { wss.on('error', (err) => {
console.error(`[${new Date().toLocaleString()}] WebSocket服务器错误:`, err); console.error(`[${new Date().toLocaleString()}] WebSocket服务器错误:`, err);
}); });
return wss; return wss;

Loading…
Cancel
Save