2026-06-26 10:51:01 +08:00
|
|
|
// server/index.js
|
|
|
|
|
import express from 'express'
|
|
|
|
|
import cors from 'cors'
|
2026-06-29 17:29:36 +08:00
|
|
|
import path from 'path';
|
2026-06-26 10:51:01 +08:00
|
|
|
|
2026-07-02 16:21:38 +08:00
|
|
|
import 'dotenv/config';
|
|
|
|
|
import { createAGVGrpcClient, createARMGrpcClient, __dirname } from './grpcClient.js';
|
2026-07-22 13:33:59 +08:00
|
|
|
import { attachAudioWebSocket } from './audioWebSocket.js';
|
2026-06-26 10:51:01 +08:00
|
|
|
|
|
|
|
|
const app = express()
|
|
|
|
|
app.use(express.static(path.join(__dirname, '../dist')));
|
|
|
|
|
app.use(cors())
|
|
|
|
|
app.use(express.json())
|
|
|
|
|
|
2026-07-10 13:44:07 +08:00
|
|
|
// 获取agv运行时状态
|
|
|
|
|
app.post('/api/agv/getRuntimeState', async (req, res) => {
|
2026-06-26 10:51:01 +08:00
|
|
|
const request = {
|
|
|
|
|
header: {
|
2026-07-02 16:26:02 +08:00
|
|
|
device_id: req.body.deviceId || 'agv_src1100'
|
2026-06-26 10:51:01 +08:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2026-07-02 16:21:38 +08:00
|
|
|
const grpcClient = createAGVGrpcClient(req.body.ip)
|
2026-06-26 10:51:01 +08:00
|
|
|
|
|
|
|
|
const resp = await new Promise((resolve, reject) => {
|
2026-07-10 13:44:07 +08:00
|
|
|
grpcClient.getRuntimeState(request, (err, res) => {
|
2026-06-26 10:51:01 +08:00
|
|
|
if (err) {
|
|
|
|
|
resolve({
|
|
|
|
|
code: 500,
|
|
|
|
|
data: `grpc服务端错误`
|
|
|
|
|
});
|
|
|
|
|
} else {
|
|
|
|
|
resolve({
|
|
|
|
|
code: 200,
|
2026-07-10 13:44:07 +08:00
|
|
|
data: res.state
|
2026-06-26 10:51:01 +08:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
if (resp.code === 200) {
|
|
|
|
|
res.json(resp)
|
|
|
|
|
} else {
|
|
|
|
|
res.status(500).json({ error: 'grpc服务端错误' })
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
2026-07-10 13:44:07 +08:00
|
|
|
// 获取机器人当前地图
|
2026-06-26 10:51:01 +08:00
|
|
|
app.post('/api/agv/getCurrentMap', async (req, res) => {
|
2026-07-02 16:21:38 +08:00
|
|
|
const grpcClient = createAGVGrpcClient(req.body.ip)
|
2026-06-26 10:51:01 +08:00
|
|
|
const request = {
|
|
|
|
|
header: {
|
2026-07-02 16:26:02 +08:00
|
|
|
device_id: req.body.deviceId || 'agv_src1100'
|
2026-06-26 10:51:01 +08:00
|
|
|
},
|
2026-07-10 13:44:07 +08:00
|
|
|
map_name: req.body.mapName
|
2026-06-26 10:51:01 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const resp = await new Promise((resolve, reject) => {
|
2026-07-10 13:44:07 +08:00
|
|
|
grpcClient.downloadMap(request, (err, res) => {
|
2026-06-26 10:51:01 +08:00
|
|
|
if (err) {
|
|
|
|
|
resolve({
|
|
|
|
|
code: 500,
|
|
|
|
|
data: `grpc服务端错误`
|
|
|
|
|
});
|
|
|
|
|
} else {
|
|
|
|
|
resolve({
|
|
|
|
|
code: 200,
|
2026-07-10 13:44:07 +08:00
|
|
|
data: res.content
|
2026-06-26 10:51:01 +08:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
if (resp.code === 200) {
|
|
|
|
|
res.json(resp)
|
|
|
|
|
} else {
|
|
|
|
|
res.status(500).json({ error: 'grpc服务端错误' })
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
2026-07-02 16:21:38 +08:00
|
|
|
// 机器人运动控制
|
2026-06-26 10:51:01 +08:00
|
|
|
app.post('/api/agv/moveRobot', async (req, res) => {
|
2026-07-10 13:44:07 +08:00
|
|
|
const grpcClient = setVelocity(req.body.ip)
|
2026-06-26 10:51:01 +08:00
|
|
|
const request = {
|
|
|
|
|
header: {
|
2026-07-02 16:26:02 +08:00
|
|
|
device_id: req.body.deviceId || 'agv_src1100'
|
2026-06-26 10:51:01 +08:00
|
|
|
},
|
2026-07-10 13:44:07 +08:00
|
|
|
velocity: {
|
2026-06-26 10:51:01 +08:00
|
|
|
vx: req.body.vx,
|
|
|
|
|
vy: req.body.vy,
|
2026-07-10 13:44:07 +08:00
|
|
|
vz: req.body.vz
|
2026-06-26 10:51:01 +08:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const resp = await new Promise((resolve, reject) => {
|
2026-07-10 13:44:07 +08:00
|
|
|
grpcClient.setVelocity(request, (err, res) => {
|
2026-06-26 10:51:01 +08:00
|
|
|
if (err) {
|
|
|
|
|
resolve({
|
|
|
|
|
code: 500,
|
|
|
|
|
data: `grpc服务端错误`
|
|
|
|
|
});
|
|
|
|
|
} else {
|
|
|
|
|
resolve({
|
|
|
|
|
code: 200,
|
|
|
|
|
data: res.status
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
if (resp.code === 200) {
|
|
|
|
|
res.json({
|
|
|
|
|
code: 200,
|
|
|
|
|
data: resp
|
|
|
|
|
})
|
|
|
|
|
} else {
|
|
|
|
|
res.status(500).json({ error: 'grpc服务端错误' })
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
2026-07-02 16:21:38 +08:00
|
|
|
// 机器人停止运动
|
2026-06-26 10:51:01 +08:00
|
|
|
app.post('/api/agv/stopRobot', async (req, res) => {
|
2026-07-02 16:21:38 +08:00
|
|
|
const grpcClient = createAGVGrpcClient(req.body.ip)
|
2026-06-26 10:51:01 +08:00
|
|
|
const request = {
|
|
|
|
|
header: {
|
2026-07-02 16:26:02 +08:00
|
|
|
device_id: req.body.deviceId || 'agv_src1100'
|
2026-06-26 10:51:01 +08:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const resp = await new Promise((resolve, reject) => {
|
2026-07-10 13:44:07 +08:00
|
|
|
grpcClient.stopVelocityControl(request, (err, res) => {
|
2026-06-26 10:51:01 +08:00
|
|
|
if (err) {
|
|
|
|
|
resolve({
|
|
|
|
|
code: 500,
|
|
|
|
|
data: `grpc服务端错误`
|
|
|
|
|
});
|
|
|
|
|
} else {
|
|
|
|
|
resolve({
|
|
|
|
|
code: 200,
|
|
|
|
|
data: res.status
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
if (resp.code === 200) {
|
|
|
|
|
res.json({
|
|
|
|
|
code: 200,
|
|
|
|
|
data: resp
|
|
|
|
|
})
|
|
|
|
|
} else {
|
|
|
|
|
res.status(500).json({ error: 'grpc服务端错误' })
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
2026-07-10 13:44:07 +08:00
|
|
|
|
|
|
|
|
// ==================================================
|
|
|
|
|
|
2026-07-02 16:21:38 +08:00
|
|
|
// 获取机械臂末端位姿
|
|
|
|
|
app.post('/api/arm/getPose', async (req, res) => {
|
|
|
|
|
const request = {
|
|
|
|
|
header: {
|
|
|
|
|
device_id: req.body.deviceId || 'huayan_arm'
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const grpcClient = createARMGrpcClient(req.body.ip)
|
|
|
|
|
|
|
|
|
|
const resp = await new Promise((resolve, reject) => {
|
|
|
|
|
grpcClient.getPose(request, (err, res) => {
|
|
|
|
|
if (err) {
|
|
|
|
|
resolve({
|
|
|
|
|
code: 500,
|
|
|
|
|
data: `grpc服务端错误`
|
|
|
|
|
});
|
|
|
|
|
} else {
|
|
|
|
|
resolve({
|
|
|
|
|
code: 200,
|
|
|
|
|
data: res.pose
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
if (resp.code === 200) {
|
|
|
|
|
res.json(resp)
|
|
|
|
|
} else {
|
|
|
|
|
res.status(500).json({ error: 'grpc服务端错误' })
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
// 获取机械臂节点状态
|
|
|
|
|
app.post('/api/arm/getJointState', async (req, res) => {
|
|
|
|
|
const request = {
|
|
|
|
|
header: {
|
|
|
|
|
device_id: req.body.deviceId || 'huayan_arm'
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const grpcClient = createARMGrpcClient(req.body.ip)
|
|
|
|
|
|
|
|
|
|
const resp = await new Promise((resolve, reject) => {
|
|
|
|
|
grpcClient.getJointState(request, (err, res) => {
|
|
|
|
|
if (err) {
|
|
|
|
|
resolve({
|
|
|
|
|
code: 500,
|
|
|
|
|
data: `grpc服务端错误`
|
|
|
|
|
});
|
|
|
|
|
} else {
|
|
|
|
|
resolve({
|
|
|
|
|
code: 200,
|
|
|
|
|
data: res.state
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
if (resp.code === 200) {
|
|
|
|
|
res.json(resp)
|
|
|
|
|
} else {
|
|
|
|
|
res.status(500).json({ error: 'grpc服务端错误' })
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
// 机械臂关节运动
|
|
|
|
|
app.post('/api/arm/speedJ', async (req, res) => {
|
|
|
|
|
const request = {
|
|
|
|
|
header: {
|
|
|
|
|
device_id: req.body.deviceId || 'huayan_arm'
|
|
|
|
|
},
|
|
|
|
|
velocity: {
|
|
|
|
|
velocity: req.body.velocities
|
|
|
|
|
},
|
|
|
|
|
acceleration: req.body.acceleration,
|
|
|
|
|
duration: req.body.duration
|
|
|
|
|
};
|
|
|
|
|
const grpcClient = createARMGrpcClient(req.body.ip)
|
|
|
|
|
|
|
|
|
|
const resp = await new Promise((resolve, reject) => {
|
|
|
|
|
grpcClient.speedJ(request, (err, res) => {
|
|
|
|
|
if (err) {
|
|
|
|
|
resolve({
|
|
|
|
|
code: 500,
|
|
|
|
|
data: `grpc服务端错误`
|
|
|
|
|
});
|
|
|
|
|
} else {
|
|
|
|
|
resolve({
|
|
|
|
|
code: 200,
|
|
|
|
|
data: res
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
if (resp.code === 200) {
|
|
|
|
|
res.json(resp)
|
|
|
|
|
} else {
|
|
|
|
|
res.status(500).json({ error: 'grpc服务端错误' })
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
app.post('/api/arm/speedL', async (req, res) => {
|
|
|
|
|
const request = {
|
|
|
|
|
header: {
|
|
|
|
|
device_id: req.body.deviceId || 'huayan_arm'
|
|
|
|
|
},
|
|
|
|
|
velocity: req.body.velocity,
|
|
|
|
|
acceleration: req.body.acceleration,
|
|
|
|
|
duration: req.body.duration
|
|
|
|
|
};
|
|
|
|
|
const grpcClient = createARMGrpcClient(req.body.ip)
|
|
|
|
|
|
|
|
|
|
const resp = await new Promise((resolve, reject) => {
|
|
|
|
|
grpcClient.speedL(request, (err, res) => {
|
|
|
|
|
if (err) {
|
|
|
|
|
resolve({
|
|
|
|
|
code: 500,
|
|
|
|
|
data: `grpc服务端错误`
|
|
|
|
|
});
|
|
|
|
|
} else {
|
|
|
|
|
resolve({
|
|
|
|
|
code: 200,
|
|
|
|
|
data: res
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
if (resp.code === 200) {
|
|
|
|
|
res.json(resp)
|
|
|
|
|
} else {
|
|
|
|
|
res.status(500).json({ error: 'grpc服务端错误' })
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
// 机械臂停止运动
|
|
|
|
|
app.post('/api/arm/stopMotion', async (req, res) => {
|
|
|
|
|
const request = {
|
|
|
|
|
// header: {
|
|
|
|
|
device_id: req.body.deviceId || 'huayan_arm',
|
|
|
|
|
// },
|
|
|
|
|
};
|
|
|
|
|
const grpcClient = createARMGrpcClient(req.body.ip)
|
|
|
|
|
|
|
|
|
|
const resp = await new Promise((resolve, reject) => {
|
|
|
|
|
grpcClient.stopMotion(request, (err, res) => {
|
|
|
|
|
if (err) {
|
|
|
|
|
resolve({
|
|
|
|
|
code: 500,
|
|
|
|
|
data: `grpc服务端错误`
|
|
|
|
|
});
|
|
|
|
|
} else {
|
|
|
|
|
resolve({
|
|
|
|
|
code: 200,
|
|
|
|
|
data: res.success
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
if (resp.code === 200) {
|
|
|
|
|
res.json(resp)
|
|
|
|
|
} else {
|
|
|
|
|
res.status(500).json({ error: 'grpc服务端错误' })
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
2026-07-06 10:32:35 +08:00
|
|
|
app.post('/api/arm/torqueOn', async (req, res) => {
|
|
|
|
|
const request = {
|
|
|
|
|
device_id: req.body.deviceId || 'huayan_arm',
|
|
|
|
|
};
|
|
|
|
|
const grpcClient = createARMGrpcClient(req.body.ip)
|
|
|
|
|
|
|
|
|
|
const resp = await new Promise((resolve, reject) => {
|
|
|
|
|
grpcClient.torqueOn(request, (err, res) => {
|
|
|
|
|
if (err) {
|
|
|
|
|
resolve({
|
|
|
|
|
code: 500,
|
|
|
|
|
data: `grpc服务端错误`
|
|
|
|
|
});
|
|
|
|
|
} else {
|
|
|
|
|
resolve({
|
|
|
|
|
code: 200,
|
|
|
|
|
data: res
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
if (resp.code === 200) {
|
|
|
|
|
res.json(resp)
|
|
|
|
|
} else {
|
|
|
|
|
res.status(500).json({ error: 'grpc服务端错误' })
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
2026-06-29 11:17:08 +08:00
|
|
|
const isProd = process.env.NODE_ENV === 'production';
|
|
|
|
|
const PORT = process.env.VITE_SERVICE_PORT || 3000;
|
|
|
|
|
|
|
|
|
|
// ============ 生产环境:托管 Vite 构建产物 ============
|
|
|
|
|
if (isProd) {
|
2026-06-29 17:29:36 +08:00
|
|
|
const distPath = path.resolve(__dirname, '../dist');
|
2026-06-29 11:17:08 +08:00
|
|
|
|
|
|
|
|
// 托管静态文件
|
|
|
|
|
app.use(express.static(distPath));
|
|
|
|
|
|
|
|
|
|
// SPA 回退:所有非 API 请求返回 index.html
|
2026-06-29 17:33:18 +08:00
|
|
|
app.get('/{*path}', (req, res) => {
|
2026-06-29 17:29:36 +08:00
|
|
|
res.sendFile(path.resolve(distPath, 'index.html'));
|
2026-06-29 11:17:08 +08:00
|
|
|
});
|
|
|
|
|
|
2026-06-29 17:33:18 +08:00
|
|
|
app.post('/{*path}', (req, res) => {
|
2026-06-29 17:29:36 +08:00
|
|
|
res.sendFile(path.resolve(distPath, 'index.html'));
|
2026-06-29 11:17:08 +08:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ========== 启动 =========
|
2026-06-26 10:51:01 +08:00
|
|
|
|
2026-07-22 13:33:59 +08:00
|
|
|
const httpServer = app.listen(PORT, () => {
|
2026-06-26 10:51:01 +08:00
|
|
|
console.log(`gRPC 桥接服务已启动: http://localhost:${PORT}`)
|
2026-07-22 13:33:59 +08:00
|
|
|
})
|
|
|
|
|
|
|
|
|
|
attachAudioWebSocket(httpServer)
|