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-07-22 15:03:56 +08:00
|
|
|
|
import fs from 'fs';
|
|
|
|
|
|
import http from 'http';
|
|
|
|
|
|
import https from 'https';
|
2026-06-26 10:51:01 +08:00
|
|
|
|
|
2026-07-02 16:21:38 +08:00
|
|
|
|
import 'dotenv/config';
|
2026-07-22 13:53:44 +08:00
|
|
|
|
import {
|
|
|
|
|
|
createAGVGrpcClient,
|
|
|
|
|
|
createARMGrpcClient,
|
|
|
|
|
|
createMicrophoneGrpcClient,
|
2026-07-22 14:01:38 +08:00
|
|
|
|
createSpeakerGrpcClient,
|
2026-07-22 13:53:44 +08:00
|
|
|
|
__dirname
|
|
|
|
|
|
} from './grpcClient.js';
|
2026-07-22 13:33:59 +08:00
|
|
|
|
import { attachAudioWebSocket } from './audioWebSocket.js';
|
2026-07-31 10:44:15 +08:00
|
|
|
|
import { streamCameraVideo } from './cameraVideoStream.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-31 10:44:15 +08:00
|
|
|
|
// 将相机逐帧 gRPC 数据转码为浏览器可直接播放的 fragmented MP4。
|
|
|
|
|
|
// 示例:/api/camera/video?ip=192.168.0.28%3A50052&deviceId=camera1
|
|
|
|
|
|
app.get('/api/camera/video', streamCameraVideo)
|
|
|
|
|
|
|
2026-07-22 13:53:44 +08:00
|
|
|
|
// 获取机器人麦克风音量(0-100)。
|
|
|
|
|
|
app.post('/api/audio/microphone/volume/get', (req, res) => {
|
|
|
|
|
|
const { ip, deviceId } = req.body
|
|
|
|
|
|
if (!ip || !deviceId) {
|
|
|
|
|
|
res.status(400).json({ code: 400, message: '机器人地址和麦克风设备 ID 不能为空' })
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const grpcClient = createMicrophoneGrpcClient(ip)
|
|
|
|
|
|
grpcClient.getVolume(
|
|
|
|
|
|
{ header: { device_id: deviceId } },
|
|
|
|
|
|
{ deadline: Date.now() + 5000 },
|
|
|
|
|
|
(error, feedback) => {
|
|
|
|
|
|
grpcClient.close()
|
|
|
|
|
|
if (error) {
|
|
|
|
|
|
console.error('[audio] get microphone volume failed:', error)
|
|
|
|
|
|
res.status(502).json({ code: 502, message: error.details || error.message || '获取机器人音量失败' })
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
if (feedback?.header?.success === false) {
|
|
|
|
|
|
res.status(502).json({
|
|
|
|
|
|
code: 502,
|
|
|
|
|
|
message: feedback.header.error_message || '机器人拒绝获取音量'
|
|
|
|
|
|
})
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
res.json({ code: 200, data: { volume: feedback?.volume ?? 0 } })
|
|
|
|
|
|
}
|
|
|
|
|
|
)
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
// 设置机器人麦克风音量(0-100)。
|
|
|
|
|
|
app.post('/api/audio/microphone/volume/set', (req, res) => {
|
|
|
|
|
|
const { ip, deviceId } = req.body
|
|
|
|
|
|
const volume = Number(req.body.volume)
|
|
|
|
|
|
if (!ip || !deviceId) {
|
|
|
|
|
|
res.status(400).json({ code: 400, message: '机器人地址和麦克风设备 ID 不能为空' })
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!Number.isInteger(volume) || volume < 0 || volume > 100) {
|
|
|
|
|
|
res.status(400).json({ code: 400, message: '音量必须是 0 到 100 的整数' })
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const grpcClient = createMicrophoneGrpcClient(ip)
|
|
|
|
|
|
grpcClient.setVolume({
|
|
|
|
|
|
header: { device_id: deviceId },
|
|
|
|
|
|
volume
|
|
|
|
|
|
}, { deadline: Date.now() + 5000 }, (error, feedback) => {
|
|
|
|
|
|
grpcClient.close()
|
|
|
|
|
|
if (error) {
|
|
|
|
|
|
console.error('[audio] set microphone volume failed:', error)
|
|
|
|
|
|
res.status(502).json({ code: 502, message: error.details || error.message || '设置机器人音量失败' })
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
if (feedback?.header?.success === false) {
|
|
|
|
|
|
res.status(502).json({
|
|
|
|
|
|
code: 502,
|
|
|
|
|
|
message: feedback.header.error_message || '机器人拒绝设置音量'
|
|
|
|
|
|
})
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
res.json({ code: 200, data: { volume } })
|
|
|
|
|
|
})
|
|
|
|
|
|
})
|
|
|
|
|
|
|
2026-07-22 14:01:38 +08:00
|
|
|
|
// 获取机器人扬声器音量(0-100)。
|
|
|
|
|
|
app.post('/api/audio/speaker/volume/get', (req, res) => {
|
|
|
|
|
|
const { ip, deviceId } = req.body
|
|
|
|
|
|
if (!ip || !deviceId) {
|
|
|
|
|
|
res.status(400).json({ code: 400, message: '机器人地址和扬声器设备 ID 不能为空' })
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const grpcClient = createSpeakerGrpcClient(ip)
|
|
|
|
|
|
grpcClient.getVolume(
|
|
|
|
|
|
{ header: { device_id: deviceId } },
|
|
|
|
|
|
{ deadline: Date.now() + 5000 },
|
|
|
|
|
|
(error, feedback) => {
|
|
|
|
|
|
grpcClient.close()
|
|
|
|
|
|
if (error) {
|
|
|
|
|
|
console.error('[audio] get speaker volume failed:', error)
|
|
|
|
|
|
res.status(502).json({ code: 502, message: error.details || error.message || '获取扬声器音量失败' })
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
if (feedback?.header?.success === false) {
|
|
|
|
|
|
res.status(502).json({
|
|
|
|
|
|
code: 502,
|
|
|
|
|
|
message: feedback.header.error_message || '机器人拒绝获取扬声器音量'
|
|
|
|
|
|
})
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
res.json({ code: 200, data: { volume: feedback?.volume ?? 0 } })
|
|
|
|
|
|
}
|
|
|
|
|
|
)
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
// 设置机器人扬声器音量(0-100)。
|
|
|
|
|
|
app.post('/api/audio/speaker/volume/set', (req, res) => {
|
|
|
|
|
|
const { ip, deviceId } = req.body
|
|
|
|
|
|
const volume = Number(req.body.volume)
|
|
|
|
|
|
if (!ip || !deviceId) {
|
|
|
|
|
|
res.status(400).json({ code: 400, message: '机器人地址和扬声器设备 ID 不能为空' })
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!Number.isInteger(volume) || volume < 0 || volume > 100) {
|
|
|
|
|
|
res.status(400).json({ code: 400, message: '音量必须是 0 到 100 的整数' })
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const grpcClient = createSpeakerGrpcClient(ip)
|
|
|
|
|
|
grpcClient.setVolume({
|
|
|
|
|
|
header: { device_id: deviceId },
|
|
|
|
|
|
volume
|
|
|
|
|
|
}, { deadline: Date.now() + 5000 }, (error, feedback) => {
|
|
|
|
|
|
grpcClient.close()
|
|
|
|
|
|
if (error) {
|
|
|
|
|
|
console.error('[audio] set speaker volume failed:', error)
|
|
|
|
|
|
res.status(502).json({ code: 502, message: error.details || error.message || '设置扬声器音量失败' })
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
if (feedback?.header?.success === false) {
|
|
|
|
|
|
res.status(502).json({
|
|
|
|
|
|
code: 502,
|
|
|
|
|
|
message: feedback.header.error_message || '机器人拒绝设置扬声器音量'
|
|
|
|
|
|
})
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
res.json({ code: 200, data: { volume } })
|
|
|
|
|
|
})
|
|
|
|
|
|
})
|
|
|
|
|
|
|
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-08-03 16:52:27 +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
|
|
|
|
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';
|
2026-07-22 15:03:56 +08:00
|
|
|
|
const PORT = Number(process.env.VITE_SERVICE_PORT || 3000);
|
|
|
|
|
|
const httpsEnabled = process.env.HTTPS_ENABLED === 'true';
|
|
|
|
|
|
|
|
|
|
|
|
if (!Number.isInteger(PORT) || PORT < 1 || PORT > 65535) {
|
|
|
|
|
|
throw new Error(`VITE_SERVICE_PORT 必须是 1-65535 之间的整数,当前值为: ${process.env.VITE_SERVICE_PORT}`);
|
|
|
|
|
|
}
|
2026-06-29 11:17:08 +08:00
|
|
|
|
|
|
|
|
|
|
// ============ 生产环境:托管 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 15:03:56 +08:00
|
|
|
|
let httpServer;
|
|
|
|
|
|
|
|
|
|
|
|
if (httpsEnabled) {
|
|
|
|
|
|
const certFile = process.env.HTTPS_CERT_FILE;
|
|
|
|
|
|
const keyFile = process.env.HTTPS_KEY_FILE;
|
|
|
|
|
|
|
|
|
|
|
|
if (!certFile || !keyFile) {
|
|
|
|
|
|
throw new Error('启用 HTTPS 时必须设置 HTTPS_CERT_FILE 和 HTTPS_KEY_FILE');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
httpServer = https.createServer({
|
|
|
|
|
|
cert: fs.readFileSync(certFile),
|
|
|
|
|
|
key: fs.readFileSync(keyFile)
|
|
|
|
|
|
}, app);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
httpServer = http.createServer(app);
|
|
|
|
|
|
}
|
2026-07-22 13:33:59 +08:00
|
|
|
|
|
|
|
|
|
|
attachAudioWebSocket(httpServer)
|
2026-07-22 15:03:56 +08:00
|
|
|
|
|
|
|
|
|
|
httpServer.listen(PORT, '0.0.0.0', () => {
|
|
|
|
|
|
const protocol = httpsEnabled ? 'https' : 'http';
|
|
|
|
|
|
console.log(`gRPC 桥接服务已启动: ${protocol}://0.0.0.0:${PORT}`)
|
|
|
|
|
|
})
|