PID 配置改为通过 HTTP API 存取,移除 rosbridge 推送
- 页面初始化时 GET /api/pid/config 加载已保存的配置 - 点击保存通过 POST /api/pid/config 持久化到后端 - 新增「加载配置」按钮支持手动从后端重新拉取 - 移除 PID_CONFIG_TOPIC 及 rosbridge 发布逻辑
This commit is contained in:
@@ -9,8 +9,6 @@ export const MJPEG_MANUAL_URL = 'http://192.168.0.11/stream?topic=/vision/result
|
||||
// ROS 话题
|
||||
export const CMD_WHEEL_TOPIC = '/cmd_wheel'
|
||||
export const CMD_GIMBAL_TOPIC = '/cmd_gimbal'
|
||||
export const PID_CONFIG_TOPIC = '/pid_config'
|
||||
|
||||
// 键盘控制参数
|
||||
export const MAX_LINEAR = 0.5 // m/s
|
||||
export const MAX_ANGULAR = 1.0 // rad/s
|
||||
|
||||
+81
-16
@@ -44,27 +44,62 @@
|
||||
<button class="btn-laser" @click="toggleLaser">
|
||||
激光: {{ laserOn ? 'ON' : 'OFF' }}
|
||||
</button>
|
||||
<button class="btn-publish" @click="publishParams">发布参数</button>
|
||||
<button class="btn-load" :disabled="loading" @click="loadConfig">
|
||||
{{ loading ? '加载中...' : '加载配置' }}
|
||||
</button>
|
||||
<button class="btn-publish" :disabled="saving" @click="saveConfig">
|
||||
{{ saving ? '保存中...' : '保存配置' }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { reactive, ref } from 'vue'
|
||||
import { HTTP_API_BASE, ROSBRIDGE_URL, PID_CONFIG_TOPIC } from '../config.js'
|
||||
import { reactive, ref, onMounted } from 'vue'
|
||||
import { HTTP_API_BASE, ROSBRIDGE_URL } from '../config.js'
|
||||
import { useRosbridge } from '../composables/useRosbridge.js'
|
||||
|
||||
const { connected, connect, publish } = useRosbridge()
|
||||
const { connected, connect } = useRosbridge()
|
||||
connect(ROSBRIDGE_URL)
|
||||
|
||||
const laserOn = ref(false)
|
||||
const loading = ref(false)
|
||||
const saving = ref(false)
|
||||
|
||||
const params = reactive({
|
||||
const defaults = {
|
||||
visualYaw: { kp: 0, ki: 0, kd: 0 },
|
||||
visualPitch: { kp: 0, ki: 0, kd: 0 },
|
||||
angleYaw: { kp: 0, ki: 0, kd: 0 },
|
||||
anglePitch: { kp: 0, ki: 0, kd: 0 },
|
||||
})
|
||||
}
|
||||
|
||||
const params = reactive(structuredClone(defaults))
|
||||
|
||||
function snakeToCamel(key) {
|
||||
return key.replace(/_([a-z])/g, (_, c) => c.toUpperCase())
|
||||
}
|
||||
|
||||
async function loadConfig() {
|
||||
loading.value = true
|
||||
try {
|
||||
const res = await fetch(`${HTTP_API_BASE}/api/pid/config`)
|
||||
if (!res.ok) throw new Error(`HTTP ${res.status}`)
|
||||
const data = await res.json()
|
||||
for (const [snakeKey, val] of Object.entries(data)) {
|
||||
const camelKey = snakeToCamel(snakeKey)
|
||||
if (params[camelKey]) {
|
||||
params[camelKey].kp = val.kp ?? 0
|
||||
params[camelKey].ki = val.ki ?? 0
|
||||
params[camelKey].kd = val.kd ?? 0
|
||||
}
|
||||
}
|
||||
console.log('PID 配置已加载:', data)
|
||||
} catch (e) {
|
||||
console.error('加载 PID 配置失败:', e)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function switchMode(mode) {
|
||||
try {
|
||||
@@ -79,18 +114,37 @@ async function switchMode(mode) {
|
||||
}
|
||||
}
|
||||
|
||||
function publishParams() {
|
||||
publish(PID_CONFIG_TOPIC, 'std_msgs/String', {
|
||||
data: JSON.stringify({
|
||||
visual_yaw: params.visualYaw,
|
||||
visual_pitch: params.visualPitch,
|
||||
angle_yaw: params.angleYaw,
|
||||
angle_pitch: params.anglePitch,
|
||||
}),
|
||||
})
|
||||
console.log('PID 参数已发布:', params)
|
||||
async function saveConfig() {
|
||||
const payload = {
|
||||
visual_yaw: params.visualYaw,
|
||||
visual_pitch: params.visualPitch,
|
||||
angle_yaw: params.angleYaw,
|
||||
angle_pitch: params.anglePitch,
|
||||
}
|
||||
|
||||
saving.value = true
|
||||
try {
|
||||
const res = await fetch(`${HTTP_API_BASE}/api/pid/config`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(payload),
|
||||
})
|
||||
if (!res.ok) {
|
||||
const err = await res.json().catch(() => ({}))
|
||||
throw new Error(err.message || `HTTP ${res.status}`)
|
||||
}
|
||||
console.log('PID 配置已保存')
|
||||
} catch (e) {
|
||||
console.error('保存 PID 配置失败:', e)
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadConfig()
|
||||
})
|
||||
|
||||
async function toggleLaser() {
|
||||
try {
|
||||
const action = laserOn.value ? 'off' : 'on'
|
||||
@@ -146,6 +200,16 @@ async function toggleLaser() {
|
||||
.conn-status { font-size: 13px; color: #e94560; }
|
||||
.conn-status.ok { color: #4ecca3; }
|
||||
|
||||
.btn-load {
|
||||
padding: 10px 28px;
|
||||
border: 1px solid #0f3460; border-radius: 4px;
|
||||
background: #16213e; color: #a0a0b0;
|
||||
font-size: 15px; font-weight: 600; cursor: pointer;
|
||||
transition: background 0.2s, color 0.2s;
|
||||
}
|
||||
.btn-load:hover { background: #1a1a3e; color: #e0e0e0; }
|
||||
.btn-load:disabled { opacity: 0.5; cursor: not-allowed; }
|
||||
|
||||
.btn-publish {
|
||||
padding: 10px 28px;
|
||||
border: none; border-radius: 4px;
|
||||
@@ -154,6 +218,7 @@ async function toggleLaser() {
|
||||
transition: background 0.2s;
|
||||
}
|
||||
.btn-publish:hover { background: #3bb08a; }
|
||||
.btn-publish:disabled { opacity: 0.5; cursor: not-allowed; }
|
||||
|
||||
.btn-laser {
|
||||
padding: 10px 28px;
|
||||
|
||||
Reference in New Issue
Block a user