web-map/README.md

407 lines
29 KiB
Markdown
Raw Normal View History

2025-05-06 17:29:20 +08:00
# 场景接口
2025-04-20 00:49:14 +08:00
2025-05-06 17:29:20 +08:00
## 获取场景
2025-04-20 00:49:14 +08:00
2025-05-06 17:29:20 +08:00
POST /scene/getById
请求体:
{ id: string }
响应体:
SceneDetail
示例:
```json
{
"code": 200,
"success": true,
"data": {
"id": "mock-scene-1",
"label": "模拟场景A",
2025-05-11 18:46:59 +08:00
"json": "{\"x\":0,\"y\":0,\"scale\":1,\"pens\":[{\"id\":\"1417882e\",\"name\":\"area\",\"tags\":[\"area\",\"area-11\"],\"label\":\"测试互斥区\",\"x\":275,\"y\":54,\"width\":178,\"height\":107,\"lineWidth\":1,\"area\":{\"type\":11,\"points\":[\"083f10\",\"3a350276\"],\"routes\":[\"4781a31f\",\"fc86102\",\"6f33b86\"]},\"locked\":4,\"fontSize\":14,\"lineHeight\":1.5,\"anchors\":[],\"rotate\":0},{\"id\":\"f236e4b\",\"name\":\"area\",\"tags\":[\"area\",\"area-1\"],\"label\":\"测试库区\",\"x\":281,\"y\":246,\"width\":292,\"height\":152,\"lineWidth\":1,\"area\":{\"type\":1,\"points\":[\"343a2f0\"],\"routes\":[]},\"locked\":4,\"fontSize\":14,\"lineHeight\":1.5,\"anchors\":[],\"rotate\":0},{\"id\":\"de62ae3\",\"name\":\"area\",\"tags\":[\"area\",\"area-12\"],\"label\":\"测试非互斥区\",\"x\":63,\"y\":67,\"width\":203,\"height\":325,\"lineWidth\":1,\"area\":{\"type\":12,\"points\":[\"776fa1bf\",\"ea84ca\",\"51b95527\",\"200e2898\"],\"routes\":[]},\"locked\":4,\"fontSize\":14,\"lineHeight\":1.5,\"anchors\":[],\"rotate\":0},{\"x\":100,\"y\":100,\"width\":24,\"height\":24,\"lineWidth\":2,\"iconSize\":4,\"image\":\"\",\"canvasLayer\":3,\"id\":\"776fa1bf\",\"name\":\"point\",\"tags\":[\"point\"],\"label\":\"测试普通点\",\"point\":{\"type\":1},\"fontSize\":14,\"lineHeight\":1.5,\"anchors\":[{\"penId\":\"776fa1bf\",\"id\":\"t\",\"x\":0.5,\"y\":0},{\"penId\":\"776fa1bf\",\"id\":\"b\",\"x\":0.5,\"y\":1},{\"penId\":\"776fa1bf\",\"id\":\"l\",\"x\":0,\"y\":0.5},{\"penId\":\"776fa1bf\",\"id\":\"r\",\"x\":1,\"y\":0.5}],\"rotate\":0,\"connectedLines\":[{\"lineId\":\"3de9971\",\"lineAnchor\":\"42f48978\",\"anchor\":\"r\"},{\"lineId\":\"142dbab5\",\"lineAnchor\":\"70b09d5\",\"anchor\":\"b\"}]},{\"x\":200,\"y\":100,\"width\":24,\"height\":24,\"lineWidth\":2,\"iconSize\":4,\"image\":\"\",\"canvasLayer\":3,\"id\":\"ea84ca\",\"name\":\"point\",\"tags\":[\"point\"],\"label\":\"测试等待点\",\"point\":{\"type\":2,\"actions\":[\"343a2f0\"]},\"fontSize\":14,\"lineHeight\":1.5,\"anchors\":[{\"penId\":\"ea84ca\",\"id\":\"t\",\"x\":0.5,\"y\":0},{\"penId\":\"ea84ca\",\"id\":\"b\",\"x\":0.5,\"y\":1},{\"penId\":\"ea84ca\",\"id\":\"l\",\"x\":0,\"y\":0.5},{\"penId\":\"ea84ca\",\"id\":\"r\",\"x\":1,\"y\":0.5}],\"rotate\":0,\"connectedLines\":[{\"lineId\":\"3de9971\",\"lineAnchor\":\"60110d7\",\"anchor\":\"l\"},{\"lineId\":\"4781a31f\",\"lineAnchor\":\"73e75ced\",\"anchor\":\"r\"}]},{\"x\":300,\"y\":100,\"width\":24,\"height\":24,\"lineWidth\":2,\"iconSize\":4,\"image\":\"\",\"canvasLayer\":3,\"id\":\"083f10\",\"name\":\"point\",\"tags\":[\"point\"],\"label\":\"测试避让点\",\"point\":{\"type\":3},\"fontSize\":14,\"lineHeight\":1.5,\"anchors\":[{\"penId\":\"083f10\",\"id\":\"t\",\"x\":0.5,\"y\":0},{\"penId\":\"083f10\",\"id\":\"b\",\"x\":0.5,\"y\":1},{\"penId\":\"083f10\",\"id\":\"l\",\"x\":0,\"y\":0.5},{\"penId\":\"083f10\",\"id\":\"r\",\"x\":1,\"y\":0.5}],\"rotate\":0,\"connectedLines\":[{\"lineId\":\"4781a31f\",\"lineAnchor\":\"4c98bb6\",\"anchor\":\"l\"},{\"lineId\":\"fc86102\",\"lineAnchor\":\"19ec7bcb\",\"anchor\":\"r\"}]},{\"x\":400,\"y\":100,\"width\":24,\"height\":24,\"lineWidth\":2,\"iconSize\":4,\"image\":\"\",\"canvasLayer\":3,\"id\":\"3a350276\",\"name\":\"point\",\"tags\":[\"point\"],\"label\":\"测试临时避让点\",\"point\":{\"type\":4},\"fontSize\":14,\"lineHeight\":1.5,\"anchors\":[{\"penId\":\"3a350276\",\"id\":\"t\",\"x\":0.5,\"y\":0},{\"penId\":\"3a350276\",\"id\":\"b\",\"x\":0.5,\"y\":1},{\"penId\":\"3a350276\",\"id\":\"l\",\"x\":0,\"y\":0.5},{\"penId\":\"3a350276\",\"id\":\"r\",\"x\":1,\"y\":0.5}],\"rotate\":0,\"connectedLines\":[{\"lineId\":\"fc86102\",\"lineAnchor\":\"ef8cad\",\"anchor\":\"l\"},{\"lineId\":\"6f33b86\",\"lineAnchor\":\"2a73e4b\",\"anchor\":\"r\"}]},{\"x\":100,\"y\":300,\"width\":48,\"height\":60,\"lineWidth\":3,\"iconSize\":10,\"image\":\"/point/11-dark.png\",\"canvasLayer\":3,\"id\":\"51b95527\",\"name\":\"point\",\"tags\":[\"point\"],\"label\":\"测试电梯点\",\"point\":{\"type\":11},\"fontSize\":14,\"lineHeight\":1.5,\"anchors\":[{\"penId\":\"51b95527\",\"id\":\"t\",\"x\":0.5,\"y\":0}
2025-05-06 17:29:20 +08:00
},
"message": "模拟提示"
}
```
2025-05-11 18:46:59 +08:00
## 保存场景
POST /scene/saveById
请求体:
{ id: string; json: string }
响应体:
## 获取组场景
POST /scene/getByGroupId
请求体:
{ id: string; sid: string }
响应体:
GroupSceneDetail
示例:
```json
{
"code": 200,
"success": true,
"data": {
"id": "mock-group-scene-1",
"label": "模拟组场景A",
"group": {
"sid": "mock-scene-1",
"id": "mock-robot-group",
"label": "模拟机器人组",
"robots": ["mock-robot-1", "mock-robot-2"]
},
"robots": [
{
"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
},
{
"id": "mock-robot-2",
"label": "模拟机器人B",
"brand": "模拟品牌A",
"type": 2,
"ip": "127.0.1.2"
}
]
},
"message": "模拟提示"
}
```
## 保存组场景
POST /scene/saveByGroupId
请求体:
{ id: string; sid: string; json: string }
响应体:
# 机器人接口
## 获取所有机器人
POST /robot/getAll
请求体:
响应体:
Array<RobotInfo>
示例:
```json
{
"code": 200,
"success": true,
"data": [
{
"id": "mock-robot-1",
"label": "模拟机器人A",
"brand": "模拟品牌A",
"type": 1,
"ip": "127.0.1.1"
},
{
"id": "mock-robot-2",
"label": "模拟机器人B",
"brand": "模拟品牌A",
"type": 2,
"ip": "127.0.1.2"
},
{
"id": "mock-robot-3",
"label": "模拟机器人C",
"brand": "模拟品牌A",
"type": 3,
"ip": "127.0.1.3"
},
{
"id": "mock-robot-4",
"label": "模拟机器人D",
"brand": "模拟品牌B",
"type": 1,
"ip": "127.0.2.1"
},
{
"id": "mock-robot-5",
"label": "模拟机器人E",
"brand": "模拟品牌B",
"type": 2,
"ip": "127.0.2.2"
},
{
"id": "mock-robot-6",
"label": "模拟机器人F",
"brand": "模拟品牌B",
"type": 3,
"ip": "127.0.2.3"
}
],
"message": "模拟提示"
}
```
## 注册机器人
POST /robot/register
请求体:
RobotDetail
示例
```json
{
"isSimulative": 1,
"gid": "26b26411",
"label": "测试机器人",
"brand": 1,
"type": 2,
"ip": "127.0.0.1",
"minBattery": 0,
"chargeBattery": 40,
"taskBattery": 60,
"swapBattery": 20,
"maxBattery": 100
}
```
响应体:
RobotInfo
示例
```json
{
"code": 200,
"success": true,
"data": {
"id": "mock-robot-0",
"label": "模拟机器人-注册",
"brand": "模拟品牌A",
"type": 1,
"ip": "127.0.0.0"
},
"message": "模拟提示"
}
```
## 批量抢占控制权
POST /robot/seizeByIds
请求体:
{ ids: string[] }
响应体:
string[]
示例
```json
{
"code": 200,
"success": true,
"data": ["mock-robot-1", "mock-robot-2"],
"message": "模拟提示"
}
```
## 同步组文件
POST /robot/syncByGroupId
请求体:
{ id: string; sid: string }
响应体:
2025-05-06 17:29:20 +08:00
# 数据结构
1. 场景相关
```typescript
interface SceneDetail {
id: string; // 场景id
label: string; // 场景名称
json?: string; // 场景JSON
}
2025-05-11 18:46:59 +08:00
interface GroupSceneDetail {
id: string; // 组场景id
label: string; // 组场景名称
json?: string; // 组场景JSON
group: RobotGroup; // 机器人组
robots?: Array<RobotInfo>; // 组场景机器人
}
2025-05-06 17:29:20 +08:00
```
2. 机器人相关
```typescript
interface RobotGroup {
2025-05-11 18:46:59 +08:00
sid?: string; // 场景id
2025-05-06 17:29:20 +08:00
id: string; // 机器人组id
label: string; // 机器人组名称
robots?: Array<string>; // 机器人列表
}
interface RobotInfo {
gid?: string; // 机器人组id
id: string; // 机器人id
label: string; // 机器人名称
brand: RobotBrand; // 机器人品牌
type: RobotType; // 机器人类型
ip: string; // 机器人ip
battery?: number; // 机器人电量
isConnected?: boolean; // 机器人连接状态
state?: RobotState; // 机器人状态
canOrder?: boolean; // 接单状态
canStop?: boolean; // 急停状态
canControl?: boolean; // 控制状态
}
2025-05-11 18:46:59 +08:00
export interface RobotDetail extends RobotInfo {
isSimulative?: 0 | 1; // 是否仿真机器人
minBattery?: number; // 最小电量
maxBattery?: number; // 最大电量
chargeBattery?: number; // 充电电量
taskBattery?: number; // 任务电量
swapBattery?: number; // 交换电量
}
2025-05-06 17:29:20 +08:00
enum RobotBrand {
'先工' = 1,
}
enum RobotType {
叉车机器人 = 1,
AMR机器人,
料箱机器人,
}
enum RobotState {
任务执行中 = 1,
充电中,
停靠中,
空闲中,
}
```
3. 地图相关
```typescript
interface MapPen {
id: string; // 唯一标识
name: string; // 唯一名称(用于识别图元,包含点位-point、线路-line、区域-area
x: number; // 横坐标
y: number; // 纵坐标
width: number; // 宽度
height: number; // 高度
label?: string; // 展示名称
desc?: string; // 描述
point?: MapPointInfo; // 点位信息
route?: MapRouteInfo; // 线路信息
area?: MapAreaInfo; // 区域信息
attrs?: Record<string, unknown>; // 额外属性
activeAttrs?: Array<string>; // 已激活的额外属性
}
interface MapPointInfo {
type: MapPointType; // 点位类型
robots?: Array<RobotInfo['id']>; // 绑定机器人id集合
2025-05-06 23:48:21 +08:00
actions?: Array<string>; // 绑定动作点id集合
isBlock?: boolean; // 是否禁行
isForbidAvoid?: boolean; // 是否禁止避让
2025-05-06 17:29:20 +08:00
}
interface MapRouteInfo {
type: MapRouteType; // 线路类型
2025-05-06 23:48:21 +08:00
direction?: -1 | 1; // 方向
pass?: MapRoutePassType; // 可通行类型
2025-05-11 18:46:59 +08:00
c1?: Point; // 控制点A
c2?: Point; // 控制点B
2025-05-06 17:29:20 +08:00
}
interface MapAreaInfo {
type: MapAreaType; // 区域类型
points?: Array<string>; // 绑定点位id集合
routes?: Array<string>; // 绑定线路id集合
}
2025-05-11 18:46:59 +08:00
export type Point = Record<'x' | 'y', number>;
export type Rect = Record<'x' | 'y' | 'width' | 'height', number>;
export type AnchorPosition = 't' | 'b' | 'l' | 'r';
2025-05-06 17:29:20 +08:00
enum MapPointType {
普通点 = 1,
等待点,
避让点,
临时避让点,
电梯点 = 11,
自动门点,
充电点,
停靠点,
动作点,
禁行点,
障碍点 = 99, // 待优化,后续将单独抽离
}
2025-05-06 23:48:21 +08:00
2025-05-06 17:29:20 +08:00
enum MapRouteType {
直线 = 'line',
三阶贝塞尔曲线 = 'bezier3',
}
2025-05-06 23:48:21 +08:00
enum MapRoutePassType {
无,
仅空载可通行,
仅载货可通行,
禁行 = 10,
}
2025-05-06 17:29:20 +08:00
enum MapAreaType {
库区 = 1,
互斥区 = 11,
非互斥区,
}
```
# 场景文件格式
1. 场景文件格式为json格式包含以下字段
template: string; // 模板id只读
locked: number; // 锁定状态(后端无需处理)
version: string; // 版本号(后端无需处理)
x: number; // 地图在画布中的横坐标
y: number; // 地图在画布中的纵坐标
scale: number; // 地图缩放比例
origin: { x: number; y: number; }; // 地图原点坐标
center: { x: number; y: number; }; // 地图中心点坐标
pens: Array<MapPen>; // 地图元素(包含点位、线路、区域)
robots: Array<RobotInfo>; // 场景中的机器人
robotGroups: Array<RobotGroup>; // 场景中的机器人组