window.onload = function () { function getStatus() { return new Promise((resolve, reject) => { fetch("/get_status") .then((response) => { if (!response.ok) { reject(new Error("Network response was not ok")); } return response.json(); }) .then((data) => resolve(data)) // 如果成功,返回数据 .catch((error) => { console.error("There was a problem with the fetch operation:", error); reject(error); // 如果发生错误,返回错误 }); }); } // 虚拟模式 const developBtn = document.getElementById("develop-btn"); const modeRealText = document.getElementById("mode-real-text"); const changePower = document.getElementById("change-power"); const changePowerText = document.getElementById("change-power-text"); const loadBtn = document.getElementById("load-btn"); const loadText = document.getElementById("load-text"); // 虚拟模式显示 modeRealText.innerText = parseInt(localStorage.getItem("modeReal")) === 0 ? "开" : "关"; // 力控方式按钮显示 changePower.style.display = parseInt(localStorage.getItem("modeReal")) === 0 ? "flex" : "none"; loadText.innerText = parseInt(localStorage.getItem("loadMode")) === 1 ? "开" : "关"; developBtn.addEventListener("click", function () { getStatus() .then(async (data) => { if (data.is_massaging || data.is_pause) { let stopPleaseText = await getPopupText("stopPleaseText"); showPopup(stopPleaseText, { confirm: true, cancel: false }); return; } else { if (parseInt(localStorage.getItem("loadMode")) === 1) { let noToggleText = await getPopupText("noToggleText"); showPopup(noToggleText); return; } else { const modeReal = parseInt(localStorage.getItem("modeReal")) === 0 ? 0 : 1; if (modeReal === 1) { localStorage.setItem("modeReal", 0); modeRealText.innerText = "开"; changePower.style.display = "flex"; changePowerText.innerText = localStorage.getItem("powerControl") ? localStorage.getItem("powerControl") : "导纳"; } else { localStorage.setItem("modeReal", 1); modeRealText.innerText = "关"; changePower.style.display = "none"; } } } }) .catch((error) => { console.error("Error:", error); showPopup(error, { confirm: true, cancel: false }); }); }); loadBtn.addEventListener("click", function () { getStatus() .then((data) => { if (data.is_massaging || data.is_pause) { showPopup("请先停止按摩...", { confirm: true, cancel: false }); return; } else { if (parseInt(localStorage.getItem("modeReal")) === 0) { showPopup("循环模式下和虚拟模式无法同时开启", { confirm: true, cancel: false }); return; } else { const loadMode = parseInt(localStorage.getItem("loadMode")) === 1 ? 1 : 0; if (loadMode === 0) { localStorage.setItem("loadMode", 1); loadText.innerText = "开"; } else { localStorage.setItem("loadMode", 0); loadText.innerText = "关"; } } } }) .catch((error) => { console.error("Error:", error); showPopup(error, { confirm: true, cancel: false }); }); }); const changePowerBtn = document.getElementById("change-power-btn"); changePowerText.innerText = localStorage.getItem("powerControl") ? localStorage.getItem("powerControl") : "导纳"; changePowerBtn.addEventListener("click", function () { let powerControl = localStorage.getItem("powerControl"); if (powerControl === "导纳" || !powerControl) { localStorage.setItem("powerControl", "力位混合"); changePowerText.innerText = "力位混合"; } else { localStorage.setItem("powerControl", "导纳"); changePowerText.innerText = "导纳"; } }); // 机械臂打包按钮 const robotPackBtn = document.getElementById("robot-pack-btn"); robotPackBtn.addEventListener("click", function () { getStatus() .then((data) => { if (data.is_massaging || data.is_pause) { showPopup("请先停止按摩...", { confirm: true, cancel: false }); return; } else { showPopup("准备运动到打包位置,请确定是否已经取下按摩头!").then( (confirm) => { if (confirm) { fetch("/pack_mode", { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded", // 表单数据类型 }, }); localStorage.setItem("modeReal", 1); modeRealText.innerText = "关"; changePower.style.display = "none"; localStorage.setItem("loadMode", 0); loadText.innerText = "关"; } } ); } }) .catch((error) => { console.error("Error:", error); showPopup(error, { confirm: true, cancel: false }); }); }); let jumpModeInterval = null; const massageLog = document.getElementById("massage-log-btn"); const uiLog = document.getElementById("ui-log-btn"); const langLog = document.getElementById("language-log-btn"); massageLog.addEventListener("click", function () { loadLogData("show", "massage"); // showLogModal(); }) uiLog.addEventListener("click", function () { loadLogData("show", "ui"); // showLogModal(); }) langLog.addEventListener("click", function () { loadLogData("show", "language"); // showLogModal(); }) const logModal = document.getElementById("log-modal"); const logBg = document.getElementById("log-bg"); const logInput = document.getElementById("log-input"); const logSearch = document.getElementById("log-search"); const logRefresh = document.getElementById("log-refresh"); const logText = document.getElementById("log-text"); logBg.addEventListener("click", function () { logModal.style.display = "none"; logInput.value = ""; logText.innerHTML = ""; }); // 自定义日志高亮规则 Prism.languages.log = { 'info': /INFO/, 'error': /ERROR/, 'warning': /WARNING/, 'timestamp': /\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}/ }; let currentPage = 1; // 当前页码 let totalPages = 1; // 总页数 let currentLogType = ""; // 当前日志类型 let currentKeyword = ""; // 当前搜索关键字 let isLoading = false; // 是否正在加载数据 // 加载日志数据 function loadLogData(clickType, type, keyword = "", page = 1) { if (isLoading) return; // 如果正在加载,则退出 isLoading = true; currentLogType = type; currentKeyword = keyword; currentPage = page; // 显示加载指示器 document.getElementById("loading-indicator").style.display = "block"; getLog(type, keyword, page) .then((data) => { if (clickType === 'show') { showLogModal(); } // 如果是第一页,清空内容 if (page === 1) { logText.innerHTML = `
${data.data.join("\n")}
`; } else { // 追加新内容 const existingContent = logText.querySelector("code").textContent; logText.innerHTML = `
${existingContent}\n${data.data.join("\n")}
`; } // 调用 Prism.js 高亮 Prism.highlightAll(); // 更新总页数 totalPages = Math.ceil(data.total / data.pageSize); // 隐藏加载指示器 document.getElementById("loading-indicator").style.display = "none"; isLoading = false; }) .catch((error) => { console.error("加载日志失败:", error); document.getElementById("loading-indicator").style.display = "none"; isLoading = false; }); } logText.addEventListener("scroll", () => { if ( logText.scrollTop + logText.clientHeight >= logText.scrollHeight - 10 // 接近底部时触发 ) { if (currentPage < totalPages) { loadLogData('scroll', currentLogType, currentKeyword, currentPage + 1); } } }); logSearch.addEventListener("click", () => { const keyword = logInput.value.trim(); loadLogData('search', currentLogType, keyword, 1); // 搜索时重置到第一页 }); logRefresh.addEventListener("click", () => { logInput.value = currentKeyword; loadLogData('refresh', currentLogType, currentKeyword, currentPage); }); logInput.addEventListener("keyup", (event) => { if (event.keyCode === 13) { const keyword = logInput.value.trim(); loadLogData('search', currentLogType, keyword, 1); // 按下回车时搜索 } }); // 显示日志弹窗 function showLogModal() { logModal.style.display = "block"; } function getLog(type, keyword = "", page = 1, pageSize = 400) { return new Promise((resolve, reject) => { fetch(`/get_log?type=${type}&keyword=${keyword}&page=${page}&pageSize=${pageSize}`) .then((response) => response.json()) .then((data) => { if (data.error) { showPopup(data.error, { confirm: true, cancel: false }); reject(data.error); // 如果有错误,拒绝 Promise } else { resolve(data); // 如果成功,返回数据 } }) .catch((error) => { console.error(error); showPopup(error, { confirm: true, cancel: false }); reject(error); // 捕获错误并拒绝 Promise }); }); } // 获取按摩头配置 function loadMassageHeads() { fetch('/get_massage_heads') .then(response => response.json()) .then(data => { if (data.status === 'success') { updateHeadCheckboxes(data.data); } else { showPopup('获取按摩头配置失败: ' + data.message, {confirm: true, cancel: false}); } }) .catch(error => { showPopup('请求出错: ' + error, {confirm: true, cancel: false}); }); } const saveBtn = document.getElementById('save-heads-btn'); document.querySelectorAll('input[name="head-option"]').forEach(checkbox => { checkbox.addEventListener('change', updateSaveButtonState); }); // 更新保存按钮状态 function updateSaveButtonState() { const hasSelected = Array.from(document.querySelectorAll('input[name="head-option"]:checked')).length > 0; if (!hasSelected) { saveBtn.disabled = true; saveBtn.style.opacity = '0.4'; showPopup('请至少选择一个按摩头', { confirm: true, cancel: false }) } else { saveBtn.disabled = false; saveBtn.style.opacity = '1'; } } // 保存按摩头配置 function saveMassageHeads() { if(saveBtn.style.opacity == '0.4') { return; } getStatus() .then((data) => { if (data.is_massaging || data.is_pause) { showPopup("按摩中无法设置", { confirm: true, cancel: false }); return; } else { const headsConfig = {}; document.querySelectorAll('input[name="head-option"]').forEach(checkbox => { const headId = checkbox.value; headsConfig[headId] = { display: checkbox.checked, name: document.querySelector(`label[for="head-${headId}"]`).textContent }; }); fetch('/set_massage_heads', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ heads_config: headsConfig }) }) .then(response => response.json()) .then(data => { if (data.status === 'success') { showPopup('按摩头设置已保存', {confirm: true, cancel: false}); } else { showPopup('保存失败: ' + data.message, {confirm: true, cancel: false}); } }) .catch(error => { showPopup('保存出错: ' + error, {confirm: true, cancel: false}); }); } }) .catch((error) => { console.error("Error:", error); showPopup(error, { confirm: true, cancel: false }); }); } async function getJumpMode() { try { const response = await fetch('/get_jump_mode'); const { data } = await response.json(); document.getElementById('jump-text').textContent = data.enabled ? '已开启' : '已关闭'; jumpModeInterval = data.enabled; } catch (error) { console.error('获取跳跃模式失败:', error); showPopup('获取跳跃模式失败'); } } // 切换跳跃模式 document.getElementById('jump-toggle-btn').addEventListener('click', async () => { getStatus() .then(async (data) => { if (data.is_massaging || data.is_pause) { showPopup("请先停止按摩...", { confirm: true, cancel: false }); return; } else { if(jumpModeInterval === null || jumpModeInterval === undefined) { try { const current = await fetch('/get_jump_mode').then(res => res.json()); const newState = !current.data.enabled; toggleJumpMode(newState); } catch (error) { console.error('切换跳跃模式失败:', error); showPopup('切换跳跃模式失败'); } } else { const newState = !jumpModeInterval; toggleJumpMode(newState); } } }) .catch((error) => { console.error("Error:", error); showPopup(error, { confirm: true, cancel: false }); }); }); async function toggleJumpMode(newState) { // 设置新状态 const response = await fetch('/set_jump_mode', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ enabled: newState }) }); const result = await response.json(); if (result.status === 'success') { document.getElementById('jump-text').textContent = newState ? '已开启' : '已关闭'; jumpModeInterval = newState; showPopup(`跳跃模式已${newState ? '开启' : '关闭'}`); } else { showPopup('操作失败: ' + result.message); } } // 更新复选框状态 function updateHeadCheckboxes(headsConfig) { for (const [headId, config] of Object.entries(headsConfig)) { const checkbox = document.getElementById(`head-${headId}`); if (checkbox) { checkbox.checked = config.display; } } } // 绑定保存按钮事件 saveBtn.addEventListener('click', saveMassageHeads); loadMassageHeads(); getJumpMode(); }; const wifiModal = document.getElementById("wifi-modal"); function getWifiStatus() { fetch("/is_connected") .then((response) => response.json()) .then((data) => { const myWifi = document.getElementById("my-wifi"); if (data.is_connected) { myWifi.style.display = "flex"; getWifiInfo(); } else { myWifi.style.display = "none"; showPopup("星耀按摩机器人未连接网络,请检查网络连接后重试。", {confirm: true, cancel: false}); } }); } const wifiName = document.getElementById("wifi-name"); function getWifiInfo() { fetch("/get_current_connection") .then((response) => response.json()) .then((data) => { const wifiTxt = document.getElementById("wifi-txt"); if (data.connection_info[0].name) { wifiTxt.innerText = data.connection_info[0].name; wifiName.innerText = data.connection_info[0].name; } else { wifiTxt.innerText = "未知网络或未连接"; wifiTxt.style.color = "rgba(145, 66, 197, 1)"; } }); } let isAllowClick = true; let selectedSsid = ""; // 全局变量,用于存储当前选择的 SSID let selectedWifiImg = ""; // 全局变量,用于存储当前选择的 Wi-Fi 信号强度图片 let selectedWifiImgElement = null; function scanWifi() { const wifiList = document.getElementById("wifi-list"); wifiList.innerHTML = `
`; fetch("/scan_wifi") .then((response) => response.json()) .then((data) => { // 先清空列表 wifiList.innerHTML = ""; if (data.wifi_networks.length === 0) { wifiList.innerHTML = `扫描不到wifi`; } else { // 再添加扫描到的wifi data.wifi_networks.forEach((network) => { if (network.SSID !== wifiName.innerText) { let wifiImg = network.Signal <= 33 ? "/static/images/setting/wifi-small.png" : network.Signal <= 66 ? "/static/images/setting/wifi-medium.png" : "/static/images/setting/wifi.png"; wifiList.innerHTML += `
${network.SSID}
`; } }); // 为每个 wifi-item 添加点击事件监听器 const wifiItems = document.querySelectorAll(".wifi-item"); wifiItems.forEach((item) => { item.addEventListener("click", function () { if (isAllowClick === true) { selectedSsid = this.getAttribute("data-ssid"); // 存储当前选中的 SSID console.log("点击了 Wi-Fi 网络:", selectedSsid); // 获取当前点击项中的图片元素,并保存它 selectedWifiImgElement = this.querySelector(".wifi-signal-img"); selectedWifiImg = selectedWifiImgElement.src; // 显示密码框 showPwdModal().then((confirm) => { if (confirm) { wifiPwdCheck(); } }); } else { return; } }); }); } }); } const pwdModal = document.getElementById("pwd-modal"); const pwdInput = document.getElementById("pwd-input"); let pwdResolve; function showPwdModal() { pwdModal.style.display = "flex"; pwdInput.value = ""; pwdInput.focus(); // 返回Promise,等待用户选择 return new Promise((resolve) => { pwdResolve = resolve; // 保存 resolve 函数 }); } // 绑定 keydown 事件 pwdInput.addEventListener('keydown', function (event) { // 检查按下的键是否是 Enter 键 if (event.key === 'Enter' || event.keyCode === 13) { // 回车键按下时的逻辑 pwdConfirm() } }); function pwdConfirm() { pwdModal.style.display = "none"; pwdResolve(true); } function pwdCancel() { pwdModal.style.display = "none"; pwdResolve(false); } function wifiPwdCheck() { selectedWifiImgElement.src = "/static/images/setting/loading.gif"; // 更换为加载中图片 isAllowClick = false; if (pwdInput.value === "") { showPopup("密码不能为空。", {confirm: true, cancel: false}); isAllowClick = true; // 恢复之前选择的 Wi-Fi 图标 if (selectedWifiImgElement) { selectedWifiImgElement.src = selectedWifiImg; // 恢复为原始图标 } return; } else if (pwdInput.value.length < 8) { showPopup("密码长度必须大于等于8位。", {confirm: true, cancel: false}); isAllowClick = true; // 恢复之前选择的 Wi-Fi 图标 if (selectedWifiImgElement) { selectedWifiImgElement.src = selectedWifiImg; // 恢复为原始图标 } return; } else { let timeout = false; // 是否超时的标志 // 设置 20 秒超时计时器 const timer = setTimeout(() => { timeout = true; // 标记超时 showPopup( `连接成功,请将平板的WI-FI连接到与按摩机器人同一网络:${selectedSsid},再手动重启APP。`, { confirm: false, cancel: false } ); }, 20000); // 20秒后触发超时逻辑 fetch("/connect_wifi", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ ssid: selectedSsid, password: pwdInput.value, }), }) .then((response) => response.json()) .then((data) => { let num = 20; let interval = setInterval(() => { if (timeout) { clearInterval(interval); // 如果超时,则停止轮询 return; } if (data.message.status === "error") { showPopup(data.message.message, { confirm: true }); isAllowClick = true; // 恢复之前选择的 Wi-Fi 图标 if (selectedWifiImgElement) { selectedWifiImgElement.src = selectedWifiImg; // 恢复为原始图标 } clearInterval(interval); // 错误时停止轮询 clearTimeout(timer); // 停止超时计时器 } if (num === 0) { clearInterval(interval); // 如果 20 秒后还没有返回错误,则显示成功信息 showPopup( `连接成功,请将平板的WI-FI连接到与按摩机器人同一网络:${selectedSsid},再手动重启APP。`, { confirm: false, cancel: false } ); clearTimeout(timer); // 结束超时计时器 } num--; }, 1000); // 每秒检查一次 }) .catch(() => { isAllowClick = true; clearTimeout(timer); // 如果请求出错,取消超时计时器 }); } } getWifiStatus(); scanWifi(); document.getElementById("scan-btn").addEventListener("click", function () { wifiModal.style.display = "flex"; scanWifi(); }); document.getElementById("rescan-btn").addEventListener("click", function () { scanWifi(); }); document .getElementById("modal-level") .addEventListener("click", function (e) { if (isAllowClick === true) { wifiModal.style.display = "none"; } else { return; } }); const showBoardBtn = document.getElementById("show-board-btn"); const boardImg = document.getElementById("board-img"); const calibrationControls = document.getElementById("calibration-controls"); const startCalibrationBtn = document.getElementById("start-calibration-btn"); const calibrationResultModal = document.getElementById("calibration-result-modal"); const rotationMatrixResult = document.getElementById("rotation-matrix-result"); const translationVectorResult = document.getElementById("translation-vector-result"); const intrinsicsResult = document.getElementById("intrinsics-result"); // 存储最新的标定结果 let currentCalibrationResults = null; // 使用已存在的socket连接 if (typeof socket === 'undefined') { console.warn('Socket.IO connection not found, creating new one'); window.socket = io(); } // 格式化矩阵显示 function formatMatrix(matrix) { // 处理内参字典格式 if (typeof matrix === 'object' && matrix !== null && !Array.isArray(matrix) && 'fx' in matrix) { let result = ''; // 添加主要参数 result += `fx: ${matrix.fx.toFixed(6)}\n`; result += `fy: ${matrix.fy.toFixed(6)}\n`; result += `cx: ${matrix.cx.toFixed(6)}\n`; result += `cy: ${matrix.cy.toFixed(6)}\n`; // 添加畸变系数 if (matrix.distortion_coeffs) { result += `畸变系数:\n`; const distCoeffs = matrix.distortion_coeffs; result += ` k1: ${distCoeffs.k1.toFixed(6)}\n`; result += ` k2: ${distCoeffs.k2.toFixed(6)}\n`; result += ` p1: ${distCoeffs.p1.toFixed(6)}\n`; result += ` p2: ${distCoeffs.p2.toFixed(6)}\n`; result += ` k3: ${distCoeffs.k3.toFixed(6)}`; } return result; } else if (!Array.isArray(matrix)) { return JSON.stringify(matrix); } return matrix.map(row => Array.isArray(row) ? row.map(v => v.toFixed(6)).join(' ') : row.toFixed(6) // 直接对非数组的 row 使用 toFixed(6) ).join('\n'); } // 监听标定状态更新 socket.on('calibration_status', function (data) { console.log('Received calibration status:', data); switch (data.status) { case 'running': startCalibrationBtn.disabled = true; startCalibrationBtn.textContent = '标定中...'; break; case 'collecting': startCalibrationBtn.textContent = '收集数据...'; break; case 'calibrating': startCalibrationBtn.textContent = '计算参数...'; break; case 'completed': startCalibrationBtn.disabled = false; startCalibrationBtn.textContent = '开始标定'; // 保存当前标定结果 currentCalibrationResults = data.results; // 格式化并显示标定结果 console.log(data.results.rotation_matrix) console.log(data.results.translation_vector) console.log(data.results.intrinsics) rotationMatrixResult.textContent = formatMatrix(data.results.rotation_matrix); translationVectorResult.textContent = formatMatrix(data.results.translation_vector); intrinsicsResult.textContent = formatMatrix(data.results.intrinsics); calibrationResultModal.style.display = "block"; break; case 'error': startCalibrationBtn.disabled = false; startCalibrationBtn.textContent = '开始标定'; showPopup('标定失败:' + data.message); break; } }); showBoardBtn.addEventListener("click", function () { console.log('Showing board image'); boardImg.style.display = "block"; calibrationControls.style.display = "block"; }); boardImg.addEventListener("dblclick", function () { console.log('Hiding board image'); boardImg.style.display = "none"; calibrationControls.style.display = "none"; calibrationResultModal.style.display = "none"; }); startCalibrationBtn.addEventListener("click", function () { console.log('Starting calibration'); fetch('/start_calibration', { method: 'POST' }) .then(response => response.json()) .then(data => { if (data.status === 'error') { showPopup(data.message); } }) .catch(error => { console.error('Error:', error); showPopup('启动标定失败:' + error.message); }); }); function saveCalibrationResult() { if (!currentCalibrationResults) { showPopup('没有可保存的标定结果'); return; } fetch('/save_calibration', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ rotation_matrix: currentCalibrationResults.rotation_matrix, translation_vector: currentCalibrationResults.translation_vector, intrinsics: currentCalibrationResults.intrinsics }) }) .then(response => response.json()) .then(data => { if (data.status === 'success') { showPopup('标定结果保存成功'); calibrationResultModal.style.display = "none"; boardImg.style.display = "none"; calibrationControls.style.display = "none"; } else { showPopup('保存失败:' + data.message); } }) .catch(error => { console.error('Error:', error); showPopup('保存失败:' + error.message); }); } function cancelCalibrationResult() { calibrationResultModal.style.display = "none"; currentCalibrationResults = null; }