484 lines
19 KiB
C++
484 lines
19 KiB
C++
// -----------------------------------------------------------------------------
|
|
// Copyright (c) 2024 Mu Shibo
|
|
// Modify by Tommy
|
|
// -----------------------------------------------------------------------------
|
|
|
|
#include <WiFi.h>
|
|
|
|
/***********html&javascript网页构建**********/
|
|
const char basic_web[] PROGMEM = R"=====(
|
|
|
|
<html>
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<title>legged wheel robot web ctrl</title>
|
|
<style>
|
|
*{box-sizing:border-box;margin:0;padding:0}
|
|
body{display:flex;flex-direction:column;align-items:center;min-height:100vh;background-color:#fff;font-family:'Segoe UI',Arial,sans-serif;padding:15px;overflow:hidden;width:100vw}
|
|
.page-title{color:#333;margin-bottom:15px;text-align:center;font-weight:600;font-size:1.3rem;width:100%}
|
|
.connection-status{margin-bottom:15px;padding:6px 12px;border-radius:4px;font-weight:500;font-size:14px}
|
|
.status-connected{background-color:#e8f5e9;color:#2e7d32}
|
|
.status-disconnected{background-color:#ffebee;color:#c62828}
|
|
.btn{display:inline-block;font-size:15px;width:100px;height:60px;text-align:center;border-radius:12px;background-color:#fff;color:#6495ed;border:3px solid #6495ed;padding:0;transition:all .3s;margin:10px 0}
|
|
.row{display:flex;width:100%;justify-content:space-between;align-items:center;gap:10px}
|
|
.columnLateral{flex:1;min-width:0;text-align:center}
|
|
#joy1Div,#joy2Div{width:100%;max-width:200px;height:200px;margin:10px auto}
|
|
@media (max-width:360px){.page-title{font-size:1.1rem}
|
|
#joy1Div,#joy2Div{height:160px}
|
|
.btn{width:80px;height:50px;font-size:14px}
|
|
}
|
|
</style>
|
|
</head>
|
|
|
|
<!-- onload 事件在页面载入完成后立即触发 -->
|
|
<body onload="javascript:socket_init()">
|
|
<h1 class="page-title">WLROBOT WiFi遥控模式</h1>
|
|
|
|
<div class="connection-status status-disconnected" id="connectionStatus">未连接</div>
|
|
|
|
<center>
|
|
<div class="row">
|
|
<div class="columnLateral">
|
|
<div id="joy2Div" style="width:200px;height:200px;margin:10px"></div>
|
|
<div id="joy2Info">
|
|
<span>高度: <span id="joy2YValue">30</span></span>
|
|
<br>
|
|
<span>倾角: <span id="joy2XValue">0</span></span>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div class="columnLateral">
|
|
<div id="joy1Div" style="width:200px;height:200px;margin:10px"></div>
|
|
<div id="joy1Info">
|
|
<span>前后: <span id="joy1YValue">0</span></span>
|
|
<br>
|
|
<span>左右: <span id="joy1XValue">0</span></span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
|
|
<div class="buttons">
|
|
<button class="btn" id="jump">Jump</button>
|
|
</div>
|
|
</center>
|
|
|
|
<script>
|
|
var socket; // socket通信
|
|
var isConnected = false; // 连接状态
|
|
|
|
var g_roll = 0;
|
|
var g_h = 30;
|
|
var g_stable = 0;
|
|
var joyX = 0;
|
|
var joyY = 0;
|
|
|
|
// 更新连接状态显示
|
|
function updateConnectionStatus(connected) {
|
|
isConnected = connected;
|
|
const statusElement = document.getElementById('connectionStatus');
|
|
if (connected) {
|
|
statusElement.textContent = '已连接';
|
|
statusElement.className = 'connection-status status-connected';
|
|
} else {
|
|
statusElement.textContent = '未连接';
|
|
statusElement.className = 'connection-status status-disconnected';
|
|
}
|
|
}
|
|
|
|
function socket_init() {
|
|
try {
|
|
// 根据环境选择合适的连接方式
|
|
const wsProtocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
socket = new WebSocket(wsProtocol + '//' + window.location.hostname + ':81/');
|
|
|
|
// 连接成功事件
|
|
socket.onopen = function() {
|
|
// console.log('WebSocket连接已建立');
|
|
updateConnectionStatus(true);
|
|
};
|
|
|
|
// 接收消息事件
|
|
socket.onmessage = function(event) {
|
|
console.log('收到消息:', event.data);
|
|
// 可以在这里处理从服务器返回的消息
|
|
};
|
|
|
|
// 连接关闭事件
|
|
socket.onclose = function() {
|
|
console.log('WebSocket连接已关闭');
|
|
updateConnectionStatus(false);
|
|
// 尝试重连
|
|
setTimeout(socket_init, 3000);
|
|
};
|
|
|
|
// 错误事件
|
|
socket.onerror = function(error) {
|
|
console.error('WebSocket错误:', error);
|
|
updateConnectionStatus(false);
|
|
};
|
|
} catch (e) {
|
|
console.error('WebSocket初始化失败:', e);
|
|
updateConnectionStatus(false);
|
|
// 重试连接
|
|
setTimeout(socket_init, 3000);
|
|
}
|
|
}
|
|
|
|
function send_data() {
|
|
var data = {
|
|
'roll': g_roll,
|
|
'height': g_h,
|
|
'stable': g_stable,
|
|
'mode': 'basic',
|
|
'dir': "stop",
|
|
'joy_y': joyY,
|
|
'joy_x': joyX,
|
|
|
|
};
|
|
// console.log(data);
|
|
socket.send(JSON.stringify(data));
|
|
}
|
|
|
|
function is_stable() {
|
|
var obj = document.getElementById("stable");
|
|
if(obj.checked) {
|
|
// alert("is_stable checked");
|
|
g_stable = 1;
|
|
} else {
|
|
// alert("is_stable unchecked");
|
|
g_stable = 0;
|
|
}
|
|
send_data();
|
|
}
|
|
|
|
// 绑定按钮
|
|
var buttons = document.getElementsByClassName("btn");
|
|
for(i=0;i<buttons.length;i++) {
|
|
buttons[i].addEventListener("mousedown",btn_down,true);
|
|
buttons[i].addEventListener("mouseup",btn_up,true);
|
|
buttons[i].addEventListener("touchstart",btn_down,true);
|
|
buttons[i].addEventListener("touchend",btn_up,true);
|
|
}
|
|
|
|
function btn_down() {
|
|
this.style = "background-color: cornflowerblue; color: white;";
|
|
dir = (this.id == 'jump') ? 'jump' : 'stop';
|
|
var data = {
|
|
'dir': this.id,
|
|
'mode': 'basic',
|
|
'roll': g_roll,
|
|
'height': g_h,
|
|
'stable': g_stable,
|
|
'joy_x': joyX,
|
|
'joy_y': joyY,
|
|
};
|
|
// console.log(data);
|
|
socket.send(JSON.stringify(data));
|
|
|
|
}
|
|
|
|
function btn_up() {
|
|
this.style = "background-color: white; color: cornflowerblue;";
|
|
|
|
dir = (this.id == 'jump') ? 'stop' : 'jump';
|
|
var data = {
|
|
'dir': "stop",
|
|
'mode': 'basic',
|
|
'roll': g_roll,
|
|
'height': g_h,
|
|
'stable': g_stable,
|
|
'joy_x': joyX,
|
|
'joy_y': joyY,
|
|
};
|
|
// console.log(data); // 打印测试
|
|
socket.send(JSON.stringify(data));
|
|
|
|
}
|
|
|
|
/*摇杆内容*/
|
|
var JoyStick = (function(container, parameters) {
|
|
parameters = parameters || {};
|
|
var title = (typeof parameters.title === "undefined" ? "joystick" : parameters.title),
|
|
width = (typeof parameters.width === "undefined" ? 0 : parameters.width),
|
|
height = (typeof parameters.height === "undefined" ? 0 : parameters.height),
|
|
internalFillColor = (typeof parameters.internalFillColor === "undefined" ? "#4a90e2" : parameters.internalFillColor),
|
|
internalLineWidth = (typeof parameters.internalLineWidth === "undefined" ? 2 : parameters.internalLineWidth),
|
|
internalStrokeColor = (typeof parameters.internalStrokeColor === "undefined" ? "#4a90e2" : parameters.internalStrokeColor),
|
|
externalLineWidth = (typeof parameters.externalLineWidth === "undefined" ? 2 : parameters.externalLineWidth),
|
|
externalStrokeColor = (typeof parameters.externalStrokeColor === "undefined" ? "#4a90e2" : parameters.externalStrokeColor),
|
|
autoReturnToCenter = (typeof parameters.autoReturnToCenter === "undefined" ? true : parameters.autoReturnToCenter);
|
|
|
|
// Create Canvas element and add it in the Container object
|
|
var objContainer = document.getElementById(container);
|
|
var canvas = document.createElement("canvas");
|
|
canvas.id = title;
|
|
if(width === 0) { width = objContainer.clientWidth; }
|
|
if(height === 0) { height = objContainer.clientHeight; }
|
|
canvas.width = width;
|
|
canvas.height = height;
|
|
objContainer.appendChild(canvas);
|
|
var context = canvas.getContext("2d");
|
|
|
|
var isPressing = 0;
|
|
var isMoving = 0;
|
|
var isRelease = 0;
|
|
|
|
var circumference = 2 * Math.PI;
|
|
var internalRadius = (canvas.width - ((canvas.width / 2) + 10)) / 2;
|
|
var maxMoveStick = internalRadius + 5;
|
|
var externalRadius = internalRadius + 30;
|
|
var centerX = canvas.width / 2;
|
|
var centerY = canvas.height / 2;
|
|
var directionHorizontalLimitPos = canvas.width / 10;
|
|
var directionHorizontalLimitNeg = directionHorizontalLimitPos * -1;
|
|
var directionVerticalLimitPos = canvas.height / 10;
|
|
var directionVerticalLimitNeg = directionVerticalLimitPos * -1;
|
|
// Used to save current position of stick
|
|
var movedX = centerX;
|
|
var movedY = centerY;
|
|
|
|
// Check if the device support the touch or not
|
|
if("ontouchstart" in document.documentElement) {
|
|
canvas.addEventListener("touchstart", onTouchStart, true);
|
|
canvas.addEventListener("touchmove", onTouchMove, true);
|
|
document.addEventListener("touchend", onTouchEnd, true);
|
|
} else {
|
|
canvas.addEventListener("mousedown", onMouseDown, true);
|
|
canvas.addEventListener("mousemove", onMouseMove, true);
|
|
document.addEventListener("mouseup", onMouseUp, true);
|
|
}
|
|
// Draw the object
|
|
drawExternal();
|
|
drawInternal();
|
|
|
|
/**
|
|
* @desc Draw the external circle used as reference position
|
|
*/
|
|
function drawExternal() {
|
|
context.beginPath();
|
|
context.arc(centerX, centerY, externalRadius, 0, circumference, false);
|
|
context.lineWidth = externalLineWidth;
|
|
context.strokeStyle = externalStrokeColor;
|
|
context.stroke();
|
|
}
|
|
|
|
/**
|
|
* @desc Draw the internal stick in the current position the user have moved it
|
|
*/
|
|
function drawInternal() {
|
|
context.beginPath();
|
|
if(movedX < internalRadius) { movedX = maxMoveStick; }
|
|
if((movedX + internalRadius) > canvas.width) { movedX = canvas.width - (maxMoveStick); }
|
|
if(movedY < internalRadius) { movedY = maxMoveStick; }
|
|
if((movedY + internalRadius) > canvas.height) { movedY = canvas.height - (maxMoveStick); }
|
|
context.arc(movedX, movedY, internalRadius, 0, circumference, false);
|
|
// create radial gradient
|
|
var grd = context.createRadialGradient(centerX, centerY, 5, centerX, centerY, 200);
|
|
// Light color
|
|
grd.addColorStop(0, internalFillColor);
|
|
// Dark color
|
|
grd.addColorStop(1, internalStrokeColor);
|
|
context.fillStyle = grd;
|
|
context.fill();
|
|
context.lineWidth = internalLineWidth;
|
|
context.strokeStyle = internalStrokeColor;
|
|
context.stroke();
|
|
}
|
|
|
|
function postCoordinate(containerId) {
|
|
var xValue = (100 * ((movedX - centerX) / maxMoveStick)).toFixed();
|
|
var yValue = ((100 * ((movedY - centerY) / maxMoveStick)) * -1).toFixed();
|
|
|
|
if(containerId === 'joy1Div') {
|
|
joyX = xValue;
|
|
joyY = yValue;
|
|
// 更新 joy1 的显示信息
|
|
document.getElementById('joy1XValue').textContent = xValue;
|
|
document.getElementById('joy1YValue').textContent = yValue;
|
|
} else if(containerId === 'joy2Div') {
|
|
// 限定 x 轴 roll 取值范围
|
|
let rollValue = parseInt((xValue * 30 / 100).toFixed());
|
|
if (rollValue < -30) {
|
|
rollValue = -30;
|
|
} else if (rollValue > 30) {
|
|
rollValue = 30;
|
|
}
|
|
g_roll = rollValue;
|
|
|
|
// 限定 y 轴 小车高度 取值范围为 15 到 85
|
|
let heightValue = parseInt(yValue) ;
|
|
if (heightValue < 0) {
|
|
heightValue = 0;
|
|
} else if (heightValue > 60) {
|
|
heightValue = 60;
|
|
}
|
|
g_h = heightValue;
|
|
|
|
// 更新 joy2 的显示信息,显示实际限制后的值
|
|
document.getElementById('joy2XValue').textContent = g_roll;
|
|
document.getElementById('joy2YValue').textContent = g_h;
|
|
}
|
|
|
|
send_data();
|
|
}
|
|
|
|
function releaseControl() {
|
|
joyX = 0;
|
|
joyY = 0;
|
|
g_roll = 0;
|
|
g_h = 30;
|
|
// 重置显示信息
|
|
document.getElementById('joy1XValue').textContent = '0';
|
|
document.getElementById('joy1YValue').textContent = '0';
|
|
document.getElementById('joy2XValue').textContent = '0';
|
|
document.getElementById('joy2YValue').textContent = g_h;
|
|
|
|
send_data();
|
|
}
|
|
|
|
/**
|
|
* @desc Events for manage touch
|
|
*/
|
|
function noTouch(event) {
|
|
isPressing = 0;
|
|
isMoving = 0;
|
|
isRelease = 0;
|
|
}
|
|
|
|
function onTouchStart(event) {
|
|
isPressing = 1;
|
|
isMoving = 0;
|
|
isRelease = 0;
|
|
}
|
|
|
|
function onTouchMove(event) {
|
|
// Prevent the browser from doing its default thing (scroll, zoom)
|
|
event.preventDefault();
|
|
if(isPressing === 1 && event.targetTouches[0].target === canvas) {
|
|
isMoving = 1;
|
|
isRelease = 0;
|
|
|
|
movedX = event.targetTouches[0].pageX;
|
|
movedY = event.targetTouches[0].pageY;
|
|
// Manage offset
|
|
if(canvas.offsetParent.tagName.toUpperCase() === "BODY") {
|
|
movedX -= canvas.offsetLeft;
|
|
movedY -= canvas.offsetTop;
|
|
} else {
|
|
movedX -= canvas.offsetParent.offsetLeft;
|
|
movedY -= canvas.offsetParent.offsetTop;
|
|
}
|
|
// Delete canvas
|
|
context.clearRect(0, 0, canvas.width, canvas.height);
|
|
// Redraw object
|
|
drawExternal();
|
|
drawInternal();
|
|
|
|
postCoordinate(canvas.parentNode.id);
|
|
}
|
|
}
|
|
|
|
function onTouchEnd(event) {
|
|
//event.preventDefault();
|
|
isPressing = 0;
|
|
isMoving = 0;
|
|
isRelease = 1;
|
|
|
|
// If required reset position store variable
|
|
if(autoReturnToCenter) {
|
|
movedX = centerX;
|
|
movedY = centerY;
|
|
}
|
|
// Delete canvas
|
|
context.clearRect(0, 0, canvas.width, canvas.height);
|
|
// Redraw object
|
|
drawExternal();
|
|
drawInternal();
|
|
|
|
releaseControl();
|
|
}
|
|
|
|
/**
|
|
* @desc Events for manage mouse
|
|
*/
|
|
function noMouse(event) {
|
|
isPressing = 0;
|
|
isMoving = 0;
|
|
isRelease = 0;
|
|
}
|
|
|
|
function onMouseDown(event) {
|
|
isPressing = 1;
|
|
isMoving = 0;
|
|
isRelease = 0;
|
|
}
|
|
|
|
function onMouseMove(event) {
|
|
if(isPressing === 1) {
|
|
isMoving = 1;
|
|
isRelease = 0;
|
|
|
|
movedX = event.pageX;
|
|
movedY = event.pageY;
|
|
// Manage offset
|
|
if(canvas.offsetParent.tagName.toUpperCase() === "BODY") {
|
|
movedX -= canvas.offsetLeft;
|
|
movedY -= canvas.offsetTop;
|
|
} else {
|
|
movedX -= canvas.offsetParent.offsetLeft;
|
|
movedY -= canvas.offsetParent.offsetTop;
|
|
}
|
|
// Delete canvas
|
|
context.clearRect(0, 0, canvas.width, canvas.height);
|
|
// Redraw object
|
|
drawExternal();
|
|
drawInternal();
|
|
|
|
postCoordinate(canvas.parentNode.id);
|
|
}
|
|
}
|
|
|
|
function onMouseUp(event) {
|
|
isPressing = 0;
|
|
isMoving = 0;
|
|
isRelease = 1;
|
|
|
|
// If required reset position store variable
|
|
if(autoReturnToCenter) {
|
|
movedX = centerX;
|
|
movedY = centerY;
|
|
}
|
|
// Delete canvas
|
|
context.clearRect(0, 0, canvas.width, canvas.height);
|
|
// Redraw object
|
|
drawExternal();
|
|
drawInternal();
|
|
|
|
releaseControl();
|
|
}
|
|
|
|
this.GetX = function () {
|
|
return (100 * ((movedX - centerX) / maxMoveStick)).toFixed();
|
|
};
|
|
|
|
/**
|
|
* @desc Normalizzed value of Y move of stick
|
|
* @return Integer from -100 to +100
|
|
*/
|
|
this.GetY = function () {
|
|
return ((100 * ((movedY - centerY) / maxMoveStick)) * -1).toFixed();
|
|
};
|
|
});
|
|
|
|
var joy1Param = { "title": "1" };
|
|
var Joy1 = new JoyStick('joy1Div', joy1Param);
|
|
var joy2Param = { "title": "2" };
|
|
var Joy2 = new JoyStick('joy2Div', joy2Param);
|
|
</script>
|
|
</body>
|
|
</html>
|
|
|
|
)====="; |