diff --git a/agv_app/templates/setting.js b/agv_app/templates/setting.js
index 217f9e8..e20f9de 100644
--- a/agv_app/templates/setting.js
+++ b/agv_app/templates/setting.js
@@ -8,12 +8,18 @@ createApp({
// 任务配置
missionConfig: { rows: 3, cols: 3, grid: [], machines: [] },
selectedMachine: null,
+ sequence: [],
+ poseForm: { name: '', photo_type: 'front', description: '' },
// 地图
mapForm: { map_dir: '/home/elephant/agv_pro_ros2/src/agv_pro_navigation2/map/', map_file: 'map.yaml' },
mapMsg: '',
mapLoaded: false,
mapImageUrl: '',
mapMeta: null,
+ mapRotation: 0,
+ mapVersion: 0,
+ navCurrentPos: null,
+ nav2Available: false,
// 点位
points: [],
newPointName: '',
@@ -24,7 +30,6 @@ createApp({
selectedModelId: null,
newModelName: '',
newModelSerial: '',
- poseForm: {},
// 机械臂
armConnected: false,
currentAngles: [],
@@ -45,6 +50,7 @@ createApp({
mounted() {
this.refresh()
this.refreshAngles()
+ this.nav2Timer = setInterval(this.refreshNavStatus, 3000)
},
watch: {
tab(val) {
@@ -63,6 +69,7 @@ createApp({
beforeUnmount() {
Object.values(this.jogIntervals).forEach(i => clearInterval(i))
if (this.agvCameraTimer) clearInterval(this.agvCameraTimer)
+ if (this.nav2Timer) clearInterval(this.nav2Timer)
},
methods: {
async refresh() {
@@ -73,7 +80,6 @@ createApp({
this.armConnected = data.arm_connected
this.cameraOpened = data.camera_opened
this.mapLoaded = data.map_loaded
- // 如果地图已加载,自动获取地图图像和元数据
if (data.map_loaded) {
this.mapImageUrl = API + '/api/map/image?t=' + Date.now()
try {
@@ -85,6 +91,8 @@ createApp({
} catch (e) {}
await this.loadAllPoints()
await this.loadAllModels()
+ await this.loadAllMachines()
+ await this.loadMissionConfig()
},
// === 地图 ===
async loadMap() {
@@ -108,6 +116,51 @@ createApp({
onMapError() {
this.mapMsg = '❌ 地图图像加载失败'
},
+ rotateMap(deg) {
+ this.mapRotation = (this.mapRotation + deg) % 360
+ },
+ resetMapView() {
+ this.mapRotation = 0
+ this.mapVersion++
+ },
+ async refreshNavStatus() {
+ try {
+ const res = await fetch(API + '/api/navigate/status')
+ if (res.ok) {
+ const data = await res.json()
+ this.nav2Available = data.nav2_available
+ if (data.current_pos) {
+ this.navCurrentPos = data.current_pos
+ }
+ }
+ } catch (e) {}
+ },
+ async onMapClick(e) {
+ if (!this.mapMeta || !this.agvConnected) return
+ const rect = e.target.getBoundingClientRect()
+ const px = (e.clientX - rect.left) / rect.width
+ const py = (e.clientY - rect.top) / rect.height
+ const { resolution, origin } = this.mapMeta
+ const wx = origin[0] + px * resolution * this.mapMeta.width
+ const wy = origin[1] + (1 - py) * resolution * this.mapMeta.height
+ try {
+ const res = await fetch(API + '/api/navigate/to', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ x: wx, y: wy })
+ })
+ const data = await res.json()
+ if (data.ok) {
+ this.mapMsg = '✅ 导航目标已发送'
+ this.mapVersion++
+ } else {
+ this.mapMsg = '❌ ' + (data.error || '导航失败')
+ }
+ } catch (e) {
+ this.mapMsg = '❌ 导航请求失败'
+ }
+ setTimeout(() => { this.mapMsg = '' }, 3000)
+ },
getMapX(coords) {
if (!coords || !this.mapMeta) return 50
const [x, y, yaw] = coords
@@ -177,7 +230,6 @@ createApp({
const res = await fetch(API + '/api/models/list')
const data = await res.json()
this.models = data.models || []
- // 初始化 poseForm
this.models.forEach(m => {
if (!this.poseForm[m.id]) {
this.poseForm[m.id] = { name: '', photo_type: 'front', description: '' }
@@ -234,61 +286,55 @@ createApp({
getModel(id) {
return this.models.find(m => m.id === id)
},
- // === 机械臂 ===
-
- clearSelection() { this.selectedMachine = null },
- async saveMachineCoords() {
- if (!this.selectedMachine) return
+ // === 任务配置 ===
+ async loadMissionConfig() {
try {
- const res = await fetch(API + '/api/mission/machines/' + this.selectedMachine.id, {
- method: 'PATCH',
+ const res = await fetch(API + '/api/mission/config')
+ const data = await res.json()
+ if (data.ok && data.config) {
+ this.missionConfig.rows = data.config.rows || 3
+ this.missionConfig.cols = data.config.cols || 3
+ this.missionConfig.grid = data.config.grid || []
+ }
+ } catch (e) { console.error('加载任务配置失败', e) }
+ },
+ async generateGrid() {
+ try {
+ const res = await fetch(API + '/api/mission/config', {
+ method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
- front: this.selectedMachine.front,
- back: this.selectedMachine.back
+ rows: this.missionConfig.rows,
+ cols: this.missionConfig.cols,
+ grid: []
})
})
- if (res.ok) {
- this.mapMsg = '✅ 机器坐标已保存'
- setTimeout(() => this.mapMsg = '', 2000)
+ const data = await res.json()
+ if (data.ok) {
+ this.missionConfig.grid = data.config.grid || []
+ alert('✅ 网格已生成 (' + this.missionConfig.rows + '×' + this.missionConfig.cols + ')')
} else {
- alert('保存失败: ' + res.status)
+ alert('❌ 网格生成失败')
+ }
+ } catch (e) { alert('请求失败: ' + e.message) }
+ },
+ async saveMissionConfig() {
+ try {
+ const res = await fetch(API + '/api/mission/config', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({
+ rows: this.missionConfig.rows,
+ cols: this.missionConfig.cols,
+ grid: this.missionConfig.grid
+ })
+ })
+ const data = await res.json()
+ if (data.ok) {
+ alert('✅ 网格配置已保存')
}
} catch (e) { alert('保存失败: ' + e.message) }
},
- selectMachine(machine) {
- // 确保 front/back 永远有 coords 数组,避免 v-model 赋值失败
- if (!machine.front) machine.front = { coords: [0, 0, 0], poses: [] }
- else if (!Array.isArray(machine.front.coords)) machine.front.coords = [0, 0, 0]
- if (!machine.back) machine.back = { coords: [0, 0, 0], poses: [] }
- else if (!Array.isArray(machine.back.coords)) machine.back.coords = [0, 0, 0]
- this.selectedMachine = machine
- console.log('selectedMachine:', machine)
- },
- onCellClick(ri, ci) {
- let m = this.getMachineAt(ri, ci)
- if (!m) {
- // 自动创建机器记录
- this.createMachine(ri, ci).then(() => {
- m = this.getMachineAt(ri, ci)
- if (m) this.selectMachine(m)
- })
- } else {
- this.selectMachine(m)
- }
- },
- async createMachine(ri, ci) {
- try {
- const res = await fetch(API + '/api/mission/machines', {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({ row: ri, col: ci, front: { coords: [0, 0, 0], poses: [] }, back: { coords: [0, 0, 0], poses: [] } })
- })
- await this.loadAllMachines()
- return res.ok
- } catch (e) { alert('创建机器失败: ' + e.message); return false }
- },
-
async loadAllMachines() {
try {
const res = await fetch(API + '/api/mission/machines')
@@ -308,6 +354,189 @@ createApp({
const prevMachine = this.getMachineAt(ri - 1, ci)
return prevMachine ? prevMachine.back : machine.front
},
+ onCellClick(ri, ci) {
+ const m = this.getMachineAt(ri, ci)
+ if (!m) {
+ // 无机器 → 创建机器记录并选中
+ this.createMachine(ri, ci).then(ok => {
+ if (ok) {
+ const created = this.getMachineAt(ri, ci)
+ if (created) this.selectMachine(created)
+ }
+ })
+ } else {
+ // 有机器 → 选中
+ this.selectMachine(m)
+ }
+ },
+ async createMachine(ri, ci) {
+ try {
+ const machineId = 'm_' + ri + '_' + ci
+ const res = await fetch(API + '/api/mission/machines/add', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({
+ id: machineId,
+ row: ri,
+ col: ci,
+ front: { coords: [0, 0, 0], poses: [] },
+ back: { coords: [0, 0, 0], poses: [] }
+ })
+ })
+ const data = await res.json()
+ if (!data.ok && data.error !== '该位置已有机器') {
+ alert('创建机器失败: ' + (data.error || '未知错误'))
+ return false
+ }
+ await this.loadAllMachines()
+ return true
+ } catch (e) { alert('创建机器失败: ' + e.message); return false }
+ },
+ selectMachine(machine) {
+ if (!machine.front) machine.front = { coords: [0, 0, 0], poses: [] }
+ else if (!Array.isArray(machine.front.coords)) machine.front.coords = [0, 0, 0]
+ if (!machine.back) machine.back = { coords: [0, 0, 0], poses: [] }
+ else if (!Array.isArray(machine.back.coords)) machine.back.coords = [0, 0, 0]
+ this.selectedMachine = machine
+ },
+ clearSelection() {
+ this.selectedMachine = null
+ },
+ async deleteMachine(machineId) {
+ if (!confirm('确定删除此机器?')) return
+ try {
+ await fetch(API + '/api/mission/machines/' + machineId, { method: 'DELETE' })
+ this.selectedMachine = null
+ await this.loadAllMachines()
+ } catch (e) { alert('删除失败: ' + e.message) }
+ },
+ async saveMachineCoords() {
+ if (!this.selectedMachine) return
+ try {
+ const res = await fetch(API + '/api/mission/machines/' + this.selectedMachine.id, {
+ method: 'PUT',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({
+ front: this.selectedMachine.front,
+ back: this.selectedMachine.back
+ })
+ })
+ if (res.ok) {
+ this.mapMsg = '✅ 机器坐标已保存'
+ setTimeout(() => this.mapMsg = '', 2000)
+ } else {
+ alert('保存失败: ' + res.status)
+ }
+ } catch (e) { alert('保存失败: ' + e.message) }
+ },
+ async readPosition(side) {
+ if (!this.agvConnected) { alert('AGV 未连接'); return }
+ try {
+ const res = await fetch(API + '/api/agv/position')
+ const data = await res.json()
+ if (data.ok && data.position) {
+ const [x, y, theta] = data.position
+ if (side === 'front') {
+ this.selectedMachine.front.coords = [x, y, theta]
+ } else {
+ this.selectedMachine.back.coords = [x, y, theta]
+ }
+ } else {
+ alert('读取位置失败: ' + (data.error || '未知错误'))
+ }
+ } catch (e) { alert('读取位置失败: ' + e.message) }
+ },
+ async addPoseToMachine(machineId, side) {
+ const name = this.poseForm.name || '姿态' + (((this.selectedMachine && this.selectedMachine[side] && this.selectedMachine[side].poses) || []).length + 1)
+ try {
+ const res = await fetch(API + '/api/mission/poses/' + machineId + '/' + side, {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({
+ name: name,
+ arm_angles: this.currentAngles.length === 6 ? this.currentAngles : [0, 0, 0, 0, 0, 0],
+ speed: 500,
+ description: ''
+ })
+ })
+ const data = await res.json()
+ if (data.ok) {
+ this.poseForm.name = ''
+ await this.loadAllMachines()
+ // 重新选中当前机器以刷新姿态列表
+ const updated = this.getMachineAt(this.selectedMachine.row, this.selectedMachine.col)
+ if (updated) this.selectMachine(updated)
+ } else {
+ alert('添加姿态失败: ' + (data.error || '未知错误'))
+ }
+ } catch (e) { alert('添加姿态失败: ' + e.message) }
+ },
+ async deletePose(machineId, side, poseId) {
+ if (!confirm('确定删除此姿态?')) return
+ try {
+ await fetch(API + '/api/mission/poses/' + machineId + '/' + side + '/' + poseId, { method: 'DELETE' })
+ await this.loadAllMachines()
+ if (this.selectedMachine) {
+ const updated = this.getMachineAt(this.selectedMachine.row, this.selectedMachine.col)
+ if (updated) this.selectMachine(updated)
+ }
+ } catch (e) { alert('删除姿态失败: ' + e.message) }
+ },
+ async capturePosition(ri, ci, side) {
+ if (!this.agvConnected) { alert('请先连接AGV'); return }
+ let machine = this.getMachineAt(ri, ci)
+ if (!machine) {
+ try {
+ const machineId = 'm_' + ri + '_' + ci
+ const res = await fetch(API + '/api/mission/machines/add', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({
+ id: machineId,
+ row: ri,
+ col: ci,
+ front: { coords: [0, 0, 0], poses: [] },
+ back: { coords: [0, 0, 0], poses: [] }
+ })
+ })
+ if (!res.ok) throw new Error('创建失败')
+ await this.loadAllMachines()
+ machine = this.getMachineAt(ri, ci)
+ } catch (e) { alert('创建机器失败: ' + e.message); return }
+ }
+ try {
+ const res = await fetch(API + '/api/agv/position')
+ const pos = await res.json()
+ let x = 0, y = 0, theta = 0
+ if (pos.ok && pos.position && Array.isArray(pos.position)) {
+ x = pos.position[0] || 0
+ y = pos.position[1] || 0
+ theta = pos.position[2] || 0
+ } else {
+ alert('读取位置失败: ' + (pos.error || '未知错误'))
+ return
+ }
+ if (!machine) { machine = this.getMachineAt(ri, ci) }
+ if (!machine) { alert('机器记录不存在'); return }
+ if (side === 'front') { machine.front.coords = [x, y, theta] } else { machine.back.coords = [x, y, theta] }
+ await fetch(API + '/api/mission/machines/' + machine.id, {
+ method: 'PUT',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify(machine)
+ })
+ alert((side === 'front' ? '正面' : '背面') + '点位已更新: (' + x.toFixed(2) + ',' + y.toFixed(2) + ',' + theta.toFixed(2) + ')')
+ } catch (e) { alert('读取位置失败: ' + e.message) }
+ },
+ async refreshSequence() {
+ try {
+ const res = await fetch(API + '/api/mission/generate_sequence')
+ const data = await res.json()
+ if (data.ok) {
+ this.sequence = data.sequence || []
+ }
+ } catch (e) { console.error('刷新序列失败', e) }
+ },
+ // === 机械臂 ===
async refreshAngles() {
if (!this.armConnected) return
try {
@@ -405,34 +634,5 @@ createApp({
alert('❌ 复位请求失败: ' + e.message)
}
},
- async capturePosition(ri, ci, side) {
- if (!this.agvConnected) { alert('请先连接AGV'); return }
- let machine = this.getMachineAt(ri, ci)
- if (!machine) {
- try {
- const res = await fetch(API + '/api/mission/machines', {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({ row: ri, col: ci, front: { coords: [0, 0, 0], poses: [] }, back: { coords: [0, 0, 0], poses: [] } })
- })
- if (!res.ok) throw new Error('创建失败')
- await this.loadAllMachines()
- machine = this.getMachineAt(ri, ci)
- } catch (e) { alert('创建机器失败: ' + e.message); return }
- }
- try {
- const res = await fetch(API + '/api/agv/position')
- const pos = await res.json()
- let x = 0, y = 0, theta = 0
- if (pos.position && Array.isArray(pos.position)) { x = pos.position[0]||0; y = pos.position[1]||0; theta = pos.position[2]||0 }
- if (!machine) { machine = this.getMachineAt(ri, ci) }
- if (!machine) { alert('机器记录不存在'); return }
- if (side === 'front') { machine.front.coords = [x, y, theta] } else { machine.back.coords = [x, y, theta] }
- await fetch(API + '/api/mission/machines/' + machine.id, {
- method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(machine)
- })
- alert((side==='front'?'正面':'背面')+'点位已更新: ('+x.toFixed(2)+','+y.toFixed(2)+','+theta.toFixed(2)+')')
- } catch (e) { alert('读取位置失败: '+e.message) }
- },
}
}).mount('#app')