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

View File

@@ -1,5 +1,6 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="zh-CN"> <html lang="zh-CN">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
@@ -12,6 +13,7 @@
background-color: #f0f0f0; background-color: #f0f0f0;
min-height: 100vh; min-height: 100vh;
} }
.container { .container {
display: grid; display: grid;
grid-template-columns: repeat(3, 1fr); grid-template-columns: repeat(3, 1fr);
@@ -21,6 +23,7 @@
margin: 0 auto; margin: 0 auto;
height: calc(100vh - 40px); height: calc(100vh - 40px);
} }
.camera-frame { .camera-frame {
border: 1px solid #ccc; border: 1px solid #ccc;
border-radius: 8px; border-radius: 8px;
@@ -30,6 +33,7 @@
transition: all 0.3s ease; transition: all 0.3s ease;
position: relative; position: relative;
} }
.camera-frame.main-view { .camera-frame.main-view {
grid-column: 1 / span 2; grid-column: 1 / span 2;
grid-row: 1 / span 2; grid-row: 1 / span 2;
@@ -37,26 +41,32 @@
border: 3px solid #4a90e2; 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 { .camera-frame.main-view .camera-title {
background-color: #2c5aa0; background-color: #2c5aa0;
font-size: 1.2em; font-size: 1.2em;
} }
.camera-frame.front-view { .camera-frame.front-view {
grid-column: 3 / span 1; grid-column: 3 / span 1;
grid-row: 1 / span 1; grid-row: 1 / span 1;
} }
.camera-frame.back-view { .camera-frame.back-view {
grid-column: 3 / span 1; grid-column: 3 / span 1;
grid-row: 2 / span 1; grid-row: 2 / span 1;
} }
.camera-frame.left-view { .camera-frame.left-view {
grid-column: 1 / span 1; grid-column: 1 / span 1;
grid-row: 3 / span 1; grid-row: 3 / span 1;
} }
.camera-frame.right-view { .camera-frame.right-view {
grid-column: 2 / span 1; grid-column: 2 / span 1;
grid-row: 3 / span 1; grid-row: 3 / span 1;
} }
.joystick-container { .joystick-container {
grid-column: 3 / span 1; grid-column: 3 / span 1;
grid-row: 3 / span 1; grid-row: 3 / span 1;
@@ -67,6 +77,7 @@
border-radius: 8px; 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 { .camera-title {
background-color: #4a90e2; background-color: #4a90e2;
color: white; color: white;
@@ -76,14 +87,17 @@
cursor: pointer; cursor: pointer;
user-select: none; user-select: none;
} }
.camera-title:hover { .camera-title:hover {
background-color: #3a7bc8; background-color: #3a7bc8;
} }
iframe { iframe {
width: 100%; width: 100%;
height: 100%; height: 100%;
border: none; border: none;
} }
.status-indicator { .status-indicator {
position: absolute; position: absolute;
top: 5px; top: 5px;
@@ -93,20 +107,25 @@
border-radius: 50%; border-radius: 50%;
z-index: 5; z-index: 5;
} }
virtual-joystick { virtual-joystick {
--radius: 100px; --radius: 100px;
} }
virtual-joystick::part(joystick) { virtual-joystick::part(joystick) {
background-color: rgba(255, 255, 255, 0.2); background-color: rgba(255, 255, 255, 0.2);
border: 2px solid rgba(74, 144, 226, 0.5); border: 2px solid rgba(74, 144, 226, 0.5);
} }
virtual-joystick::part(joystick):before { virtual-joystick::part(joystick):before {
background-color: rgba(74, 144, 226, 0.2); background-color: rgba(74, 144, 226, 0.2);
} }
virtual-joystick::part(joystick):after { virtual-joystick::part(joystick):after {
background-color: #4a90e2; background-color: #4a90e2;
border: 1px solid #2c5aa0; border: 1px solid #2c5aa0;
} }
#connection-status { #connection-status {
position: fixed; position: fixed;
top: 10px; top: 10px;
@@ -116,16 +135,42 @@
color: white; color: white;
font-weight: bold; font-weight: bold;
} }
.connected { .connected {
background-color: #28a745; background-color: #28a745;
} }
.disconnected { .disconnected {
background-color: #dc3545; background-color: #dc3545;
} }
</style> </style>
</head> </head>
<body> <body>
<div id="connection-status" class="disconnected">未连接</div> <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="container">
<div class="camera-frame main-view" data-camera="cam_gimbal"> <div class="camera-frame main-view" data-camera="cam_gimbal">
@@ -181,13 +226,36 @@
connectionStatus.className = 'disconnected'; connectionStatus.className = 'disconnected';
}); });
socket.on('connection_response', function(data) { // socket.on('connection_response', function (data) {
console.log('服务器响应:', 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) { // button_gimbal_pip.addEventListener('click', function () {
console.log('数据确认:', data); // 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'); const joystick = document.querySelector('virtual-joystick');
@@ -233,4 +301,5 @@
}, 30000); // 每 30 秒发送一次心跳 }, 30000); // 每 30 秒发送一次心跳
</script> </script>
</body> </body>
</html> </html>