feat: 增加云台功能控制

This commit is contained in:
2025-11-01 15:02:00 +08:00
parent b3b34b5d91
commit 9b6b0263c7
3 changed files with 151 additions and 59 deletions

67
app.py
View File

@@ -11,7 +11,8 @@ logger = logging.getLogger(__name__)
# Flask 应用初始化
app = Flask(__name__)
app.config['SECRET_KEY'] = 'h2Ms4pw9GzvwiFHyNPhH' # 请更换为安全的密钥
socketio = SocketIO(app, cors_allowed_origins="*")
# socketio = SocketIO(app, cors_allowed_origins="*")
socketio = SocketIO(app)
# 全局变量存储摇杆数据
joystick_data = {}
@@ -38,7 +39,7 @@ def handle_joystick_data(data):
# print(f"force: {force} x: {x}, y: {y}")
ptz.set_pitch_speed(int(y * 100))
ptz.set_yaw_speed(int(x * 100))
ptz.set_yaw_speed(int(x * -100))
# direction = data.get('direction', '')
# logger.info(f"收到摇杆数据:{direction}")
@@ -85,27 +86,49 @@ def handle_ping():
"""处理心跳检测"""
emit('pong')
@app.route('/get_joystick_data')
def get_joystick_data():
"""API 端点:获取当前摇杆数据"""
from flask import jsonify
return jsonify(joystick_data)
@socketio.on('gimbal_center')
def handle_gimbal_center():
"""处理云台中心"""
ptz.center()
logger.info(f"云台回中")
@app.route('/control/<action>')
def robot_control(action):
"""
示例控制端点
可用于直接控制机器人动作
"""
from flask import jsonify
valid_actions = ['forward', 'backward', 'left', 'right', 'stop']
@socketio.on('gimbal_pip')
def handle_gimbal_pip(data):
"""处理云台PIP"""
mode = int(data)
if mode >= 0 and mode <= 3:
ptz.set_pip_mode(mode)
logger.info(f"云台PIP {mode}")
@socketio.on('gimbal_ir_mode')
def handle_gimbal_ir_mode(data):
"""处理云台IR"""
mode = int(data)
if mode >= 0 and mode <= 9:
ptz.set_ir_mode(mode)
logger.info(f"云台IR {mode}")
# @app.route('/get_joystick_data')
# def get_joystick_data():
# """API 端点:获取当前摇杆数据"""
# from flask import jsonify
# return jsonify(joystick_data)
# @app.route('/control/<action>')
# def robot_control(action):
# """
# 示例控制端点
# 可用于直接控制机器人动作
# """
# from flask import jsonify
# valid_actions = ['forward', 'backward', 'left', 'right', 'stop']
if action in valid_actions:
logger.info(f"执行机器人控制:{action}")
# 在这里添加实际的机器人控制逻辑
return jsonify({'status': 'success', 'action': action})
else:
return jsonify({'status': 'error', 'message': '无效的动作'}), 400
# if action in valid_actions:
# logger.info(f"执行机器人控制:{action}")
# # 在这里添加实际的机器人控制逻辑
# return jsonify({'status': 'success', 'action': action})
# else:
# return jsonify({'status': 'error', 'message': '无效的动作'}), 400
if __name__ == '__main__':
socketio.run(app, host='0.0.0.0', port=5000, debug=True)
socketio.run(app, host='10.21.31.250', port=5000, debug=True)

View File

@@ -185,20 +185,20 @@ if __name__ == "__main__":
ptz.center()
time.sleep(1)
ptz.set_pitch_speed(99)
time.sleep(1)
ptz.set_pitch_speed(0)
time.sleep(1)
# ptz.set_pitch_speed(99)
# time.sleep(1)
# ptz.set_pitch_speed(0)
# time.sleep(1)
ptz.set_yaw_speed(-99)
time.sleep(1)
ptz.set_yaw_speed(0)
time.sleep(1)
# ptz.set_yaw_speed(-99)
# time.sleep(1)
# ptz.set_yaw_speed(0)
# time.sleep(1)
ptz.set_roll_speed(-99)
time.sleep(1)
ptz.set_roll_speed(0)
time.sleep(1)
# ptz.set_roll_speed(-99)
# time.sleep(1)
# ptz.set_roll_speed(0)
# time.sleep(1)
print("All commands sent.")

View File

@@ -1,5 +1,6 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
@@ -12,6 +13,7 @@
background-color: #f0f0f0;
min-height: 100vh;
}
.container {
display: grid;
grid-template-columns: repeat(3, 1fr);
@@ -21,42 +23,50 @@
margin: 0 auto;
height: calc(100vh - 40px);
}
.camera-frame {
border: 1px solid #ccc;
border-radius: 8px;
overflow: hidden;
background-color: #fff;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
transition: all 0.3s ease;
position: relative;
}
.camera-frame.main-view {
grid-column: 1 / span 2;
grid-row: 1 / span 2;
z-index: 10;
border: 3px solid #4a90e2;
box-shadow: 0 8px 16px rgba(0,0,0,0.2);
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.2);
}
.camera-frame.main-view .camera-title {
background-color: #2c5aa0;
font-size: 1.2em;
}
.camera-frame.front-view {
grid-column: 3 / span 1;
grid-row: 1 / span 1;
}
.camera-frame.back-view {
grid-column: 3 / span 1;
grid-row: 2 / span 1;
}
.camera-frame.left-view {
grid-column: 1 / span 1;
grid-row: 3 / span 1;
}
.camera-frame.right-view {
grid-column: 2 / span 1;
grid-row: 3 / span 1;
}
.joystick-container {
grid-column: 3 / span 1;
grid-row: 3 / span 1;
@@ -65,8 +75,9 @@
align-items: center;
background-color: #f8f9fa;
border-radius: 8px;
box-shadow: inset 0 2px 4px rgba(0,0,0,0.1);
box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.1);
}
.camera-title {
background-color: #4a90e2;
color: white;
@@ -76,14 +87,17 @@
cursor: pointer;
user-select: none;
}
.camera-title:hover {
background-color: #3a7bc8;
}
iframe {
width: 100%;
height: 100%;
border: none;
}
.status-indicator {
position: absolute;
top: 5px;
@@ -93,20 +107,25 @@
border-radius: 50%;
z-index: 5;
}
virtual-joystick {
--radius: 100px;
}
virtual-joystick::part(joystick) {
background-color: rgba(255, 255, 255, 0.2);
border: 2px solid rgba(74, 144, 226, 0.5);
}
virtual-joystick::part(joystick):before {
background-color: rgba(74, 144, 226, 0.2);
}
virtual-joystick::part(joystick):after {
background-color: #4a90e2;
border: 1px solid #2c5aa0;
}
#connection-status {
position: fixed;
top: 10px;
@@ -116,17 +135,43 @@
color: white;
font-weight: bold;
}
.connected {
background-color: #28a745;
}
.disconnected {
background-color: #dc3545;
}
</style>
</head>
<body>
<div id="connection-status" class="disconnected">未连接</div>
<div class="gimbal">
<button id="gimbal_center">云台回中</button>
<!-- <button id="gimbal_pip">画中画</button> -->
<!-- 下拉选框 -->
<select id="gimbal_pip">
<option value="0">RGB主</option>
<option value="1">仅RGB</option>
<option value="2">仅IR</option>
<option value="3">IR主</option>
</select>
<select id="ir_mode">
<option value="0">0</option>
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
<option value="4">4</option>
<option value="5">5</option>
<option value="6">6</option>
<option value="7">7</option>
<option value="8">8</option>
<option value="9">9</option>
</select>
</div>
<div class="container">
<div class="camera-frame main-view" data-camera="cam_gimbal">
<div class="camera-title">云台</div>
@@ -161,39 +206,62 @@
<!-- Socket.IO 客户端库 -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.7.2/socket.io.js"></script>
<script src="{{ url_for('static', filename='js/virtual-joystick.js') }}"></script>
<script>
// 初始化 Socket.IO 连接
const socket = io();
// 连接状态管理
const connectionStatus = document.getElementById('connection-status');
socket.on('connect', function() {
socket.on('connect', function () {
console.log('WebSocket 连接已建立');
connectionStatus.textContent = '已连接';
connectionStatus.className = 'connected';
});
socket.on('disconnect', function() {
socket.on('disconnect', function () {
console.log('WebSocket 连接已断开');
connectionStatus.textContent = '已断开';
connectionStatus.className = 'disconnected';
});
socket.on('connection_response', function(data) {
console.log('服务器响应:', data);
// socket.on('connection_response', function (data) {
// console.log('服务器响应:', data);
// });
// socket.on('data_received', function (data) {
// console.log('数据确认:', data);
// });
const button_gimbal_center = document.getElementById('gimbal_center');
// const button_gimbal_pip = document.getElementById('gimbal_pip');
const select_gimbal_pip = document.getElementById('gimbal_pip');
const select_ir_mode = document.getElementById('ir_mode');
button_gimbal_center.addEventListener('click', function () {
socket.emit('gimbal_center');
});
socket.on('data_received', function(data) {
console.log('数据确认:', data);
});
// button_gimbal_pip.addEventListener('click', function () {
// socket.emit('gimbal_pip');
// });
select_gimbal_pip.addEventListener('change', function () {
const selectedValue = select_gimbal_pip.value;
socket.emit('gimbal_pip', selectedValue);
})
select_ir_mode.addEventListener('change', function () {
const selectedValue = select_ir_mode.value;
socket.emit('gimbal_ir_mode', selectedValue);
})
// 获取摇杆元素
const joystick = document.querySelector('virtual-joystick');
// 监听摇杆移动事件
joystick.addEventListener('joystickmove', function(e) {
joystick.addEventListener('joystickmove', function (e) {
const data = {
x: parseFloat(joystick.dataset.x),
y: parseFloat(joystick.dataset.y),
@@ -203,13 +271,13 @@
hypot: parseFloat(joystick.dataset.hypot) || 0,
timestamp: Date.now()
};
// 发送数据到服务器
socket.emit('joystick_data', data);
});
// 监听摇杆抬起事件
joystick.addEventListener('joystickup', function(e) {
joystick.addEventListener('joystickup', function (e) {
// 发送停止信号
const stopData = {
x: 0,
@@ -221,10 +289,10 @@
timestamp: Date.now(),
action: 'stop'
};
socket.emit('joystick_data', stopData);
});
// 心跳检测
setInterval(() => {
if (socket.connected) {
@@ -233,4 +301,5 @@
}, 30000); // 每 30 秒发送一次心跳
</script>
</body>
</html>