From 0e95bb0c96813d9a372dc628211898bfbe7dc2df Mon Sep 17 00:00:00 2001 From: mmc <1556375442@qq.com> Date: Mon, 21 Sep 2026 13:24:06 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B5=8B=E8=AF=95=E4=BC=98=E5=8C=96=E5=AE=9E?= =?UTF-8?q?=E6=97=B6=E7=9B=91=E6=8E=A7=E4=B8=AD=E5=BF=83=EF=BC=8C=E6=8E=A5?= =?UTF-8?q?=E6=94=B6=E5=85=A8=E9=83=A8=E7=9A=84=E6=9C=BA=E5=99=A8=E4=BF=A1?= =?UTF-8?q?=E6=81=AF=20=20=EF=BC=8C=E4=BC=98=E5=8C=96=E6=98=BE=E7=A4=BA?= =?UTF-8?q?=E5=9B=BE=E6=A0=87=E7=9A=84=E9=97=AE=E9=A2=98=EF=BC=8C=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E4=B8=94=E4=BB=BB=E5=8A=A1=E7=9A=84=E6=97=B6=E5=80=99?= =?UTF-8?q?=E6=98=BE=E7=A4=BA=20=E8=A7=84=E5=88=92=E8=B7=AF=E5=BE=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/AgrtcRoomRobot.tsx | 6 +- src/components/Map.jsx | 5 +- src/components/devices/DeviceStatusPage.tsx | 2 +- src/components/devices/RobotTaskPage.tsx | 1 + src/components/devices/deviceController.js | 102 ++++++++- src/locales/en/index.ts | 2 +- src/pages/RealTimeMonitor.tsx | 229 ++++++++++---------- 7 files changed, 222 insertions(+), 125 deletions(-) diff --git a/src/components/AgrtcRoomRobot.tsx b/src/components/AgrtcRoomRobot.tsx index 2aa4c4a..626a6cf 100644 --- a/src/components/AgrtcRoomRobot.tsx +++ b/src/components/AgrtcRoomRobot.tsx @@ -5,7 +5,7 @@ import { Badge, message } from "antd"; AgoraRTC.setLogLevel(2); // 2=只输出warning/error,屏蔽debug日志 const APP_ID = "7887b4db6033410d9ac3264b40ca9c1a"; -const TOKEN = "007eJxTYDhxaLPL8617mtfHFi2tfJhQNSXh5vNJnqvu8b/3PenKKdumwGBuYWGeZJKSZGZgbGxiaJBimZhsbGRmkmRikJxomWyYuHHFiqyGQEaGB6+fMDEyQCCIz81gYqhbklGUmphrYMTAAAAyfSTX"; +const TOKEN = "007eJxTYPh6QSj59sUzD3mPNm64dK9n/n1PP64UzrhzdwUeqMUKv1BVYDC3sDBPMklJMjMwNjYxNEixTEw2NjIzSTIxSE60TDZMlOdYldUQyMiQXKbEwAiFID43g4mhbklGUWpiroExAwMAjGAhHQ=="; export default function AgrtcRoomRobot({ CHANNEL }: { CHANNEL: string }) { const [uid] = useState(() => Number(Math.floor(Math.random() * 100000))); @@ -22,7 +22,7 @@ export default function AgrtcRoomRobot({ CHANNEL }: { CHANNEL: string }) { useEffect(() => { const client = AgoraRTC.createClient({ - mode: "rtc", + mode: "live", codec: "h264", }); clientRef.current = client; @@ -145,7 +145,7 @@ export default function AgrtcRoomRobot({ CHANNEL }: { CHANNEL: string }) { const onUserUnpublished = (user: any, mediaType: "video" | "audio") => { if (isUnmounted) return; - console.log("user-unpublished", user.uid, mediaType); + console.log("user-unpublished", user.uid, mediaType, subscribedUidRef.current); if (mediaType === "video" && subscribedUidRef.current === user.uid) { if (unpublishTimer) clearTimeout(unpublishTimer); // 延迟2s清空,短闪断自动恢复不黑屏 diff --git a/src/components/Map.jsx b/src/components/Map.jsx index d2bf7b0..2d92643 100644 --- a/src/components/Map.jsx +++ b/src/components/Map.jsx @@ -795,7 +795,6 @@ const CesiumMap = React.forwardRef(({ ifClear, ifClearPlanLine, ifClearNavLine, }; function showCoordinateTooltip(tooltip, position, t, lon, lat, robot) { - console.log('showCoordinateTooltip', position, lon, lat, robot); tooltip.style.display = "block"; tooltip.style.left = position.x + 12 + "px"; tooltip.style.top = position.y + 12 + "px"; @@ -1309,7 +1308,7 @@ const CesiumMap = React.forwardRef(({ ifClear, ifClearPlanLine, ifClearNavLine, if (focusLocation.type === "plane" || focusLocation.type === "drone") { image = planeImg; } else if (focusLocation.type === "robot") { - image = droneInspectImg; + image = carImg; } else if (focusLocation.type === "mower") { image = carImg; } @@ -1739,7 +1738,6 @@ const CesiumMap = React.forwardRef(({ ifClear, ifClearPlanLine, ifClearNavLine, // ============================== // 光圈颜色:工作中蓝 / 空闲绿 / 离线红 - const haloColor = getDeviceHaloColor(device); let entity = entityMapRef.current.get(id); @@ -1781,6 +1779,7 @@ const CesiumMap = React.forwardRef(({ ifClear, ifClearPlanLine, ifClearNavLine, // 设备底部状态光晕:比机器稍大一点 // ============================== const haloId = `${id}_halo`; + const haloColor = getDeviceHaloColor(device); // 尺寸可根据设备类型微调:车辆类稍大,IOT/相机类稍小 diff --git a/src/components/devices/DeviceStatusPage.tsx b/src/components/devices/DeviceStatusPage.tsx index 988a970..c74d17c 100644 --- a/src/components/devices/DeviceStatusPage.tsx +++ b/src/components/devices/DeviceStatusPage.tsx @@ -1236,7 +1236,7 @@ export default function DeviceStatusPage() { ) : mowerView === 'front' ? (
{/**/} - +
) : ( diff --git a/src/components/devices/RobotTaskPage.tsx b/src/components/devices/RobotTaskPage.tsx index fd92911..5e02cf1 100644 --- a/src/components/devices/RobotTaskPage.tsx +++ b/src/components/devices/RobotTaskPage.tsx @@ -241,6 +241,7 @@ export default function RobotTaskPage() { const handlePoolTaskClick = (record) => { setRunningPoolTask(record); loadTaskPath(record.id); + fetchRouteDetail(record.id); // 查找对应的设备 const targetDevice = devices?.find(d => d.serialNumber === record.deviceId); diff --git a/src/components/devices/deviceController.js b/src/components/devices/deviceController.js index ad70d1f..00640f9 100644 --- a/src/components/devices/deviceController.js +++ b/src/components/devices/deviceController.js @@ -1,4 +1,5 @@ import throttle from 'lodash/throttle'; +import dayjs from 'dayjs'; import { wsManager } from '../WebSocketManager.ts'; import { getJurisdiction, @@ -9,9 +10,10 @@ import envConfig from '../../../env'; import i18n from '../../i18n'; export default class DeviceController { - constructor(userInfo, messageApi) { + constructor(userInfo, messageApi, siteId) { this.userInfo = userInfo; this.messageApi = messageApi; + this.siteId = siteId; this.wsUrl = envConfig.WS_URL || 'wss://ri.satabot.com/ws/'; @@ -44,6 +46,12 @@ export default class DeviceController { this.onIsFinished = null; this.handleArrivedPoint = null; + // 全量设备实时状态(mapPush)连接 + this.mapPushWs = null; + this.mapPushManualClose = false; + this.mapPushReconnectTimer = null; + this.onMapPush = null; + this.handleWsMessage = this.handleWsMessage.bind(this); this.handleWsStatus = this.handleWsStatus.bind(this); this.handleVisibilityChange = this.handleVisibilityChange.bind(this); @@ -78,6 +86,98 @@ export default class DeviceController { this.onControlStatusChange?.(false); } + // ============ 全量设备实时状态(mapPush)连接 ============ + // 进入实时监控页时连接一次,接收后端广播的所有设备状态;退出页面时调用 closeMapPush 断开。 + initMapPush(onMapPush) { + console.log('DeviceController initMapPush: 连接全量设备 WebSocket'); + this.onMapPush = onMapPush || null; + this.mapPushManualClose = false; + this.connectMapPush(); + } + + connectMapPush() { + if (this.mapPushWs && this.mapPushWs.readyState === WebSocket.OPEN) return; + try { + this.mapPushManualClose = false; + const ws = new WebSocket(this.wsUrl); + this.mapPushWs = ws; + + ws.onopen = () => { + ws.send(JSON.stringify({ + type: 'mapPush', + token: this.userInfo?.token, + siteId: this.siteId, + userName: this.userInfo?.username, + deviceId: '', + })); + }; + + ws.onmessage = (event) => { + let msg = null; + try { msg = JSON.parse(event.data); } catch (e) { return; } + + console.log('[DeviceController] 收到全量设备状态 初始状态', msg); + if (msg?.type !== 'mapPush') return; + const list = Array.isArray(msg?.data) && msg.data.length + ? msg.data + : []; + if (!list.length) return; + + const next = {}; + list.forEach((it) => { + const key = String(it?.id ?? ''); + if (!key) return; + next[key] = { + id: key, + lng: String(it?.lng ?? it?.lon ?? it?.longitude), + lat: String(it?.lat ?? it?.latitude), + yaw: it?.y, + fix: it?.f !== undefined ? String(it.f) : undefined, + fixStatus: it?.f !== undefined ? String(it.f) : undefined, + altitude: it?.a !== undefined ? String(it.a) : undefined, + status: it?.s ?? it?.status, + taskId: it?.t ?? it?.taskId ?? null, + battery: it?.b !== undefined ? Number(it.b) : (it?.batteryLevel !== undefined ? Number(it.batteryLevel) : undefined), + hasAlarm: !!it?.ha || !!it?.hasAlarm, + updateTime: dayjs().format('YYYY-MM-DD HH:mm:ss'), + }; + }); + console.log('[DeviceController] 收到全量设备状态', next); + this.onMapPush?.(next); + }; + + ws.onerror = () => { + console.error('[DeviceController] mapPush WebSocket 错误'); + }; + + ws.onclose = () => { + if (this.mapPushWs === ws) this.mapPushWs = null; + if (!this.mapPushManualClose) { + this.mapPushReconnectTimer = setTimeout(() => this.connectMapPush(), 5000); + } + }; + } catch (err) { + console.error('[DeviceController] 连接全量设备 WebSocket 失败', err); + this.mapPushReconnectTimer = setTimeout(() => this.connectMapPush(), 5000); + } + } + + closeMapPush() { + this.mapPushManualClose = true; + if (this.mapPushReconnectTimer) { + clearTimeout(this.mapPushReconnectTimer); + this.mapPushReconnectTimer = null; + } + if (this.mapPushWs) { + this.mapPushWs.onopen = null; + this.mapPushWs.onmessage = null; + this.mapPushWs.onerror = null; + this.mapPushWs.onclose = null; + try { this.mapPushWs.close(); } catch (e) { /* ignore */ } + this.mapPushWs = null; + } + } + handleVisibilityChange() { if (document.visibilityState === 'hidden') { console.log('Page hidden, release control'); diff --git a/src/locales/en/index.ts b/src/locales/en/index.ts index 2d51e15..21c80bc 100644 --- a/src/locales/en/index.ts +++ b/src/locales/en/index.ts @@ -1193,7 +1193,7 @@ const en = { s24: 'Recharging', - fixed2StopFixed2VideoFixed2: 'Fixed2 Stop Fixed2 Video Fixed2', + fixed2StopFixed2VideoFixed2: ' Stop Video ', s29: 'Emergency Stop', fixed2AiActions: 'AI Analysis and Actions Suggestions', diff --git a/src/pages/RealTimeMonitor.tsx b/src/pages/RealTimeMonitor.tsx index 5dff2dd..1713a45 100644 --- a/src/pages/RealTimeMonitor.tsx +++ b/src/pages/RealTimeMonitor.tsx @@ -21,7 +21,6 @@ import { NodeIndexOutlined, SaveOutlined, MaximizeOutlined, ReloadOutlined } from '@ant-design/icons'; import { Line, Bar, Pie } from '@ant-design/charts'; -import bgMain from '../assets/images/bgmain.jpg'; import { UniversalBarChart, UniversalLineChart, UniversalPieChart } from '../components/recharts'; import CesiumMap from '../components/Map'; import { useTranslation } from 'react-i18next'; @@ -106,7 +105,6 @@ const RealTimeMonitorPage = () => { const [filterStatus, setFilterStatus] = useState('all'); const [filterType, setFilterType] = useState('all'); const [searchName, setSearchName] = useState(''); - const [mapViewMode, setMapViewMode] = useState('all'); const cardStyle = { borderRadius: 12, border: '1px solid #f0f0f0', boxShadow: '0 2px 8px rgba(0,0,0,0.04)' }; @@ -122,28 +120,16 @@ const RealTimeMonitorPage = () => { const [listPage, setListPage] = useState(1); const [listPageSize, setListPageSize] = useState(8); - // ========= WebSocket + 地图交互相关 ========= - const controllerRef = useRef(null); - const isFirstConnect = useRef(true); + // ========= WebSocket(全量设备 mapPush)+ 地图交互相关 ========= const [focusLocation, setFocusLocation] = useState(null); - const [execute, setExecute] = useState(false); const [excuteStatus, setExcuteStatus] = useState('stop'); const [points, setPoints] = useState([]); const [polyPoints, setPolyPoints] = useState([]); const [routeMode, setRouteMode] = useState(''); const [clearNavLine, setClearNavLine] = useState(0); const [clearPlanLine, setClearPlanLine] = useState(0); - const [arrivedPoint, setArrivedPoint] = useState(null); - const [robotRealtimePosition, setRobotRealtimePosition] = useState(null); - const runningRealtimePosition = useMemo(() => { - return { - lon: robotRealtimePosition?.longitude || robotRealtimePosition?.lon || 0, - lat: robotRealtimePosition?.latitude || robotRealtimePosition?.lat || 0, - heading: robotRealtimePosition?.yaw || robotRealtimePosition?.heading || 0, - }; - }, [robotRealtimePosition]); + const [realtimeMap, setRealtimeMap] = useState({}); const [runningPoolTask, setRunningPoolTask] = useState(null); - const [proPoints, setProPoints] = useState({});//规划点 // ========= UI 状态 ========= const [selectedDevice, setSelectedDevice] = useState(null); @@ -168,62 +154,22 @@ const RealTimeMonitorPage = () => { // ============================================================ - // DeviceController 初始化(仅一次) + // 全量设备状态 WebSocket 连接(mapPush:进入页面自动连接,退出页面自动断开) + // 连接逻辑统一封装在 DeviceController 中 // ============================================================ + const controllerRef = useRef(null); + useEffect(() => { - if (!userInfo) return; - const controller = new DeviceController(userInfo, messageApi); - - controller.onLocationInfoUpdate = (pos) => { - setRobotRealtimePosition(pos); - }; - controller.onIsFinished = handleFinish; - - - controller.onRoutePost = (msg) => { - const { taskId, data } = msg; - //const matchedTask = taskPoolList.find(task => String(task.id) === String(taskId)); - //const sameAsSelected = runningPoolTask && String(runningPoolTask.id) === String(taskId); - //if ((!runningPoolTask && matchedTask) || sameAsSelected) { - // if (!runningPoolTask && matchedTask) setRunningPoolTask(matchedTask); - console.log('[RTMonitor] route_post:', data) - setProPoints(msg); - //} - }; - - controller.init(); - controllerRef.current = controller; - - return () => { - if (controllerRef.current) { - controllerRef.current.destroy?.(); - } - controllerRef.current = null; - }; - // eslint-disable-next-line - }, []); - useEffect(() => { - //console.log('runningPoolTask?.id, proPoints?.taskId', proPoints) - if (!runningPoolTask?.id || !proPoints?.taskId) return; - if (runningPoolTask?.id == proPoints?.taskId) { - console.log('1111111111', proPoints?.data); - setPoints(proPoints?.data); + if (!userInfo || !stationId) return; + if (!controllerRef.current) { + controllerRef.current = new DeviceController(userInfo, messageApi, stationId); } - - - }, [runningPoolTask, proPoints]); - const handleFinish = (data) => { - setExecute(false); - - utils.message.success(t('devices.fixed2TaskSuspended2Fixed2CompletedFixed2')); - setPoints([]); - setPolyPoints([]); - setRunningPoolTask(null); - setExcuteStatus('stop'); - }; - // ============================================================ - // WebSocket 连接 - + const controller = controllerRef.current; + controller.initMapPush((rtMap: any) => setRealtimeMap(rtMap || {})); + return () => { + controller.closeMapPush(); + }; + }, [userInfo, stationId]); // ============================================================ // 拉取基础数据 @@ -301,7 +247,6 @@ const RealTimeMonitorPage = () => { } }; - // 加载路线详情(预设路径点击执行时) // 加载路线详情(预设路径点击执行时) const fetchRouteDetailAndShow = async (routeId, routeData) => { try { @@ -403,21 +348,79 @@ const RealTimeMonitorPage = () => { return [...robots, ...planeDeviceRows, ...mocks]; }, [devices, planeDeviceRows, currentStation]); - const mapDevices = useMemo(() => { - const all = [...devices, ...planeDeviceRows]; - if (mapViewMode === 'selected' && (selectedDevice?.serialNumber || selectedDevice?.device_sn)) { - const sn = selectedDevice?.serialNumber || selectedDevice?.device_sn; - return all - .filter(d => (d.serialNumber == sn) || (d.device_sn == sn)) - + const pickRealtimeRecord = (rtMap: any, d: any) => { + const candidates = [d?.serialNumber, d?.device_sn, d?.sn, d?.id, d?.deviceId]; + for (const key of candidates) { + if (key == null || String(key) === '') continue; + const hit = rtMap[String(key)]; + if (hit) return hit; } - return all - .filter(d => (d?.lastRunningStatus?.latitude && d?.lastRunningStatus?.longitude) || (d?.locationMessage?.latitude && d?.locationMessage?.longitude) || (d?.longitude && d?.latitude)); + return null; + }; - }, [devices, planeDeviceRows, mapViewMode, selectedDevice]); + const realtimeMapList = useMemo(() => Object.values(realtimeMap || {}), [realtimeMap]); + + const deviceListWithRT = useMemo(() => { + return (devices || []).map((d: any) => { + const rt = pickRealtimeRecord(realtimeMap, d); + if (!rt) return d; + const lon = rt.lng; + const lat = rt.lat; + //if (!isFinite(lon) || !isFinite(lat) || (lon === 0 && lat === 0)) return d; + return { + ...d, + longitude: lon, + latitude: lat, + heading: rt.yaw, + batteryLevel: rt.battery, + feStatus: rt.status, + taskId: rt.taskId, + updateTime: rt.updateTime || d.updateTime, + raw: { ...(d.raw || d), heading: rt.yaw, batteryLevel: rt.b, longitude: lon, latitude: lat }, + }; + }); + }, [devices, realtimeMap]); + + const mapDevices = useMemo(() => { + const base = [...deviceListWithRT]; + const knownKeys = new Set(); + base.forEach((d: any) => { + const k = d?.serialNumber ?? d?.device_sn ?? d?.id; + if (k != null && k !== '') knownKeys.add(String(k)); + }); + const extras = realtimeMapList + .filter((rt: any) => isFinite(rt.lng) && isFinite(rt.lat) && !(rt.lng === 0 && rt.lat === 0) && !knownKeys.has(String(rt.id))) + .map((rt: any) => ({ + id: rt.id, + name: rt.name || rt.alias || `设备-${String(rt.id).slice(-4)}`, + serialNumber: rt.id, + deviceType: 'ROBOT', + type: 'ROBOT', + category: 'robot', + typeText: '机器人', + status: rt.status, + onlineStatus: rt.status, + longitude: rt.lng, + latitude: rt.lat, + heading: rt.yaw, + batteryLevel: rt.battery, + updateTime: rt.updateTime, + feStatus: rt.feStatus, + taskId: rt.t, + isRealtimeOnly: true, + raw: { ...rt }, + })); + const all = [...base, ...extras]; + + return all.filter((d: any) => + (d?.lastRunningStatus?.latitude && d?.lastRunningStatus?.longitude) || + (d?.locationMessage?.latitude && d?.locationMessage?.longitude) || + (d?.longitude && d?.latitude) + ); + }, [deviceListWithRT, planeDeviceRows, realtimeMapList, selectedDevice]); // ============================================================ - // 点击设备:WebSocket 连接 + 地图定位 + // 点击设备:仅打开侧边栏 + 地图定位(不再连接单台设备 WebSocket) // ============================================================ const handleDeviceClick = (robot) => { console.log('[RTMonitor] 点击设备:', robot); @@ -445,13 +448,11 @@ const RealTimeMonitorPage = () => { setMqttDrone(null); return; } - setExecute(true); setDeviceDrawerOpen(true); // ---- 无人机:先断开 WebSocket,再订阅该无人机 MQTT 实时数据 ---- if (isDroneDevice) { - controllerRef.current?.disconnect?.(); setMqttDrone(dev); if (activeDroneTask?.device_sn !== dev.device_sn) { setDroneReturningHome(false); @@ -465,22 +466,10 @@ const RealTimeMonitorPage = () => { return; } - // ---- 机器人:先取消 MQTT 订阅,再连接 WebSocket ---- + // ---- 机器人:仅打开侧边栏,不做实时 WebSocket 连接 ---- setMqttDrone(null); - if (dev.serialNumber && controllerRef.current) { - const isSameDevice = controllerRef.current.connectedDeviceSn === dev.serialNumber; - if (isSameDevice) { - console.log('[RTMonitor] 已连接相同设备,跳过'); - } else if (isFirstConnect.current) { - isFirstConnect.current = false; - controllerRef.current.connectDevice(dev); - } else { - controllerRef.current.switchDevice(dev); - } - setClearNavLine(c => c + 1); - } - // ---- 刷新该设备任务数据,并在地图显示该设备正在执行任务的路线和区域 ---- + // ---- 刷新该设备任务数据(仅更新侧边栏当前任务,路线点击时才绘制规划路径) ---- fetchTaskPoolList(dev.serialNumber).then(poolRows => { const rows = poolRows || []; const runningTask = rows.find(t => @@ -490,9 +479,9 @@ const RealTimeMonitorPage = () => { console.log('[RTMonitor] 设备正在执行的任务:', runningTask); if (runningTask) { setRunningPoolTask(runningTask); + setExcuteStatus(runningTask.taskStaus === 'EXECUTING' ? 'running' : 'pause'); loadTaskPath(runningTask.id); fetchDetailTaskPath(runningTask.id); - setExcuteStatus(runningTask.taskStaus === 'EXECUTING' ? 'running' : 'pause'); } else { setRunningPoolTask(null); setPoints([]); @@ -820,7 +809,6 @@ const RealTimeMonitorPage = () => { const newTask = [...rest].sort((a, b) => new Date(b.createTime) - new Date(a.createTime))[0]; if (newTask) { setRunningPoolTask(newTask); - await loadTaskPath(newTask.id); setExcuteStatus('running'); } } else { @@ -907,7 +895,7 @@ const RealTimeMonitorPage = () => { {t('page.deviceFixed2StatusAndGeneration')} · {currentStation?.siteName || t('common.loading')} - { { label: t('page.showAllDevices'), value: 'all' }, { label: t('page.showOnlySelected'), value: 'selected' } ]} - /> + />*/} @@ -971,11 +959,7 @@ const RealTimeMonitorPage = () => { onCameraClick={handleDeviceClick} points={points} focusLocation={focusLocation} - realtimePosition={runningRealtimePosition} polyPoints={polyPoints} - realtimeType="robot" - drawRealTime={execute} - finishPoint={arrivedPoint} ifClearNavLine={clearNavLine} ifClearPlanLine={clearPlanLine} routeMode={routeMode} @@ -988,6 +972,21 @@ const RealTimeMonitorPage = () => { {t('page.foundDevices', { count: mapDevices.length })} + {/*图例:工作中蓝 / 空闲绿 / 离线红 +
+ + + {t('devices.s59')} + + + + {t('devices.idle')} + + + + {t('devices.offline')} + +
*/} {runningPoolTask && (
@@ -999,15 +998,7 @@ const RealTimeMonitorPage = () => {
)} - {robotRealtimePosition && ( -
-
📡 {t('devices.realtimePosition')}
-
Lng: {Number(robotRealtimePosition.longitude || robotRealtimePosition.lng || 0).toFixed(5)}
-
Lat: {Number(robotRealtimePosition.latitude || robotRealtimePosition.lat || 0).toFixed(5)}
-
yaw: {Number(robotRealtimePosition.heading || 0).toFixed(5)}
-
- )} @@ -1298,7 +1289,7 @@ const RealTimeMonitorPage = () => { } extra={ - + e.stopPropagation()}>