From 224b7e3cbf15e5b2b517a878649a3d833a93f88a Mon Sep 17 00:00:00 2001 From: chndfang Date: Sun, 25 May 2025 16:45:45 +0800 Subject: [PATCH] feat: monitor robot moving --- README.md | 45 +++++++++++++++++- src/components/pen-groups.vue | 6 +-- src/pages/group-editor.vue | 5 ++ src/pages/movement-supervision.vue | 19 ++++---- src/pages/scene-editor.vue | 5 ++ src/services/editor.service.ts | 74 +++++++++++++++--------------- 6 files changed, 99 insertions(+), 55 deletions(-) diff --git a/README.md b/README.md index e976a4c..bd2a45d 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,11 @@ # 页面路由 1. 场景编辑 - /scene-editor/:id 【id为场景id】 + /scene-editor/:id [id为场景id] 2. 组编辑 - /group-editor/:sid/:id 【sid为场景id,id为机器人组id】 + /group-editor/:sid/:id [sid为场景id,id为机器人组id] +3. 运行监控 + /movement-supervision/:sid [sid为场景id] # 场景接口 @@ -113,6 +115,38 @@ POST /scene/saveByGroupId 响应体: 无 +## 实时监控场景 + +WebSocket /scene/monitor/:id [id为场景id] + +实时数据: +RobotRealtimeInfo [JSON] + +示例: + +```json +{ + "id": "mock-robot-1", + "label": "模拟机器人A", + "brand": "模拟品牌A", + "type": 1, + "ip": "127.0.1.1", + "isConnected": true, + "state": 4, + "canOrder": true, + "canStop": true, + "canControl": true, + "x": 800, + "y": 500, + "active": true, + "angle": -90, + "path": [ + [600, 500], + [100, 400] + ] +} +``` + # 机器人接口 ## 获取所有机器人 @@ -354,6 +388,13 @@ export interface RobotDetail extends RobotInfo { taskBattery?: number; // 任务电量 swapBattery?: number; // 交换电量 } +export interface RobotRealtimeInfo extends RobotInfo { + x: number; // 坐标x + y: number; // 坐标y + active?: boolean; // 是否运行 + angle?: number; // 旋转角度 + path?: Array<[number, number]>; // 规划路径 +} enum RobotBrand { '先工' = 1, diff --git a/src/components/pen-groups.vue b/src/components/pen-groups.vue index 43c8b1e..16b5f90 100644 --- a/src/components/pen-groups.vue +++ b/src/components/pen-groups.vue @@ -21,11 +21,7 @@ const points = computed(() => //#region 线路列表 const routes = computed(() => - editor.value.routes.value.filter(({ label }) => { - console.log(label); - - return label?.includes(keyword.value); - }), + editor.value.routes.value.filter(({ label }) => label?.includes(keyword.value)), ); //#endregion diff --git a/src/pages/group-editor.vue b/src/pages/group-editor.vue index 826d5de..c6f83f6 100644 --- a/src/pages/group-editor.vue +++ b/src/pages/group-editor.vue @@ -195,5 +195,10 @@ const selectRobot = (id: string) => { width: 320px; height: calc(100% - 96px); overflow: visible; + pointer-events: none; + + & > * { + pointer-events: all; + } } diff --git a/src/pages/movement-supervision.vue b/src/pages/movement-supervision.vue index e27f039..9dadc35 100644 --- a/src/pages/movement-supervision.vue +++ b/src/pages/movement-supervision.vue @@ -57,10 +57,10 @@ onMounted(async () => { let x = 800; let y = 500; const active = true; - const angle = 0; + const angle = -90; const path = <[number, number][]>[ [600, 500], - // [100, 400], + [100, 400], ]; editor.value?.refreshRobot(id, { x, y, active, angle, path }); const test = () => @@ -69,7 +69,7 @@ onMounted(async () => { editor.value?.refreshRobot(id, { x, y }); test(); }); - // test(); + test(); }); onUnmounted(() => { client.value?.close(); @@ -146,14 +146,6 @@ const selectRobot = (id: string) => { background-color: transparent !important; } -.toolbar-container { - position: fixed; - bottom: 40px; - left: 50%; - z-index: 100; - transform: translateX(-50%); -} - .card-container { position: fixed; top: 80px; @@ -162,5 +154,10 @@ const selectRobot = (id: string) => { width: 320px; height: calc(100% - 96px); overflow: visible; + pointer-events: none; + + & > * { + pointer-events: all; + } } diff --git a/src/pages/scene-editor.vue b/src/pages/scene-editor.vue index 2daf68b..acef211 100644 --- a/src/pages/scene-editor.vue +++ b/src/pages/scene-editor.vue @@ -195,5 +195,10 @@ const selectRobot = (id: string) => { width: 320px; height: calc(100% - 96px); overflow: visible; + pointer-events: none; + + & > * { + pointer-events: all; + } } diff --git a/src/services/editor.service.ts b/src/services/editor.service.ts index ed591c0..b8fe48d 100644 --- a/src/services/editor.service.ts +++ b/src/services/editor.service.ts @@ -374,12 +374,16 @@ export class EditorService extends Meta2d { } public refreshRobot(id: RobotInfo['id'], info: Partial): void { - const { rotate: or, robot } = this.getPenById(id) ?? {}; + const pen = this.getPenById(id); + const { rotate: or, robot } = pen ?? {}; if (!robot?.type) return; - const { x: ex = 37, y: ey = 37, active, angle, path } = info; - const x = ex - 37; - const y = ey - 37; + const { x: ox, y: oy } = this.getPenRect(pen!); + const { x: cx = 37, y: cy = 37, active, angle, path: points } = info; + const x = cx - 37; + const y = cy - 37; const rotate = angle ?? or; + const path = + points?.map(([px, py]) => [px - cx, py - cy]) ?? robot.path?.map(([ex, ey]) => [ex + ox! - x, ey + oy! - y]); const o = { ...robot, ...omitBy({ active, path }, isNil) }; if (isNil(active)) { this.setValue({ id, x, y, rotate, robot: o, visible: true }, { render: true, history: false, doEvent: false }); @@ -850,41 +854,37 @@ function drawRobot(ctx: CanvasRenderingContext2D, pen: MapPen): void { const { x = 0, y = 0, width: w = 0, height: h = 0, rotate: deg = 0 } = pen.calculative?.worldRect ?? {}; const { active, path } = pen.robot ?? {}; + if (!active) return; + const ox = x + w / 2; + const oy = y + h / 2; ctx.save(); - if (active) { - const ox = x + w / 2; - const oy = y + h / 2; - console.log(ox, oy); - - ctx.ellipse(ox, oy, w / 2, h / 2, 0, 0, Math.PI * 2); - ctx.fillStyle = get(theme, 'robot.fill') ?? ''; - ctx.fill(); - ctx.strokeStyle = get(theme, 'robot.stroke') ?? ''; + ctx.ellipse(ox, oy, w / 2, h / 2, 0, 0, Math.PI * 2); + ctx.fillStyle = get(theme, 'robot.fill') ?? ''; + ctx.fill(); + ctx.strokeStyle = get(theme, 'robot.stroke') ?? ''; + ctx.stroke(); + if (path?.length) { + ctx.strokeStyle = get(theme, 'robot.line') ?? ''; + ctx.lineCap = 'round'; + ctx.lineWidth = s * 4; + ctx.setLineDash([s * 5, s * 10]); + ctx.translate(ox, oy); + ctx.rotate((-deg * Math.PI) / 180); + ctx.beginPath(); + ctx.moveTo(0, 0); + path.forEach(([ex, ey]) => ctx.lineTo(ex * s, ey * s)); ctx.stroke(); - if (path?.length) { - ctx.strokeStyle = get(theme, 'robot.line') ?? ''; - ctx.lineCap = 'round'; - ctx.lineWidth = s * 4; - ctx.setLineDash([s * 5, s * 10]); - ctx.translate(ox, oy); - ctx.rotate((-deg * Math.PI) / 180); - ctx.beginPath(); - ctx.moveTo(0, 0); - // todo 缩放时坐标偏移 - path.forEach(([ex, ey]) => ctx.lineTo((ex - ox) * s, (ey - oy) * s)); - ctx.stroke(); - const [x1 = 0, y1 = 0] = nth(path, -1) ?? []; - const [x2 = ox, y2 = oy] = nth(path, -2) ?? []; - const r = Math.atan2(y1 - y2, x1 - x2) + Math.PI; - ctx.setLineDash([0]); - ctx.translate((x1 - ox) * s, (y1 - oy) * s); - ctx.beginPath(); - ctx.moveTo(Math.cos(r + Math.PI / 4) * s * 10, Math.sin(r + Math.PI / 4) * s * 10); - ctx.lineTo(0, 0); - ctx.lineTo(Math.cos(r - Math.PI / 4) * s * 10, Math.sin(r - Math.PI / 4) * s * 10); - ctx.stroke(); - ctx.setTransform(1, 0, 0, 1, 0, 0); - } + const [ex1 = 0, ey1 = 0] = nth(path, -1) ?? []; + const [ex2 = 0, ey2 = 0] = nth(path, -2) ?? []; + const r = Math.atan2(ey1 - ey2, ex1 - ex2) + Math.PI; + ctx.setLineDash([0]); + ctx.translate(ex1 * s, ey1 * s); + ctx.beginPath(); + ctx.moveTo(Math.cos(r + Math.PI / 4) * s * 10, Math.sin(r + Math.PI / 4) * s * 10); + ctx.lineTo(0, 0); + ctx.lineTo(Math.cos(r - Math.PI / 4) * s * 10, Math.sin(r - Math.PI / 4) * s * 10); + ctx.stroke(); + ctx.setTransform(1, 0, 0, 1, 0, 0); } ctx.restore(); }