pda 设备读取
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Weight Tracking & Trend Chart</title>
<style>
body { font-family: sans-serif; padding: 20px; background: #f9f9f9; }
.container { max-width: 600px; margin: 0 auto; background: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.05); }
h3 { margin-top: 0; color: #333; }
.controls { display: flex; gap: 10px; margin-bottom: 15px; align-items: center; }
button { padding: 8px 15px; border: none; border-radius: 4px; cursor: pointer; color: white; background-color: #007bff; }
button:disabled { background-color: #ccc; cursor: not-allowed; }
input[type="number"] { padding: 8px; border: 1px solid #ddd; border-radius: 4px; width: 100px; }
canvas { width: 100%; height: 250px; border: 1px solid #eee; border-radius: 4px; background: #fff; }
.status { font-size: 14px; color: #666; margin-top: 10px; }
.error { color: #dc3545; font-weight: bold; }
.config-panel { margin-bottom: 15px; padding: 10px; background: #f5f5f5; border-radius: 4px; }
.config-panel label { margin-right: 10px; font-size: 14px; }
.config-panel select { padding: 5px; border: 1px solid #ddd; border-radius: 4px; }
.debug-panel { margin-top: 15px; padding: 10px; background: #f5f5f5; border-radius: 4px; font-size: 12px; }
.debug-panel h4 { margin-top: 0; margin-bottom: 5px; }
.debug-panel pre { margin: 5px 0; padding: 5px; background: #fff; border: 1px solid #ddd; border-radius: 4px; overflow-x: auto; }
</style>
</head>
<body>
<div class="container">
<h3>Weight Trend Chart</h3>
<!-- 串口配置面板 -->
<div class="config-panel">
<label>Baud Rate:</label>
<select id="baudRateSelect">
<option value="300">300</option>
<option value="600" selected>600</option>
<option value="1200">1200</option>
<option value="2400">2400</option>
<option value="4800">4800</option>
<option value="9600">9600</option>
<option value="19200">19200</option>
<option value="38400">38400</option>
<option value="57600">57600</option>
<option value="115200">115200</option>
</select>
<label>Data Bits:</label>
<select id="dataBitsSelect">
<option value="7">7</option>
<option value="8" selected>8</option>
</select>
<label>Stop Bits:</label>
<select id="stopBitsSelect">
<option value="1" selected>1</option>
<option value="2">2</option>
</select>
<label>Parity:</label>
<select id="paritySelect">
<option value="none" selected>None</option>
<option value="even">Even</option>
<option value="odd">Odd</option>
</select>
</div>
<div class="controls">
<button id="connectBtn">Connect Device</button>
<span id="portStatus" style="font-size:14px;">Disconnected</span>
</div>
<div class="controls">
<input type="number" id="weightInput" min="40" max="150" step="0.1" placeholder="Weight(kg)">
<button id="addBtn">Add Record</button>
</div>
<canvas id="trendChart"></canvas>
<div class="status" id="chartInfo">No data available</div>
<!-- 调试信息面板 -->
<div class="debug-panel">
<h4>Debug Information</h4>
<pre id="debugLog">Browser: ${navigator.userAgent}
Web Serial Support: ${'serial' in navigator}
</pre>
</div>
</div>
<script>
const STORAGE_KEY = 'scale_weight_log';
let weightData = [];
let port = null;
let isReading = false;
let reader = null;
let debugLog = document.getElementById('debugLog');
// 初始化调试信息
debugLog.textContent = debugLog.textContent.replace('${navigator.userAgent}', navigator.userAgent);
debugLog.textContent = debugLog.textContent.replace('${\'serial\' in navigator}', 'serial' in navigator);
// 加载数据
function loadData() {
try {
const raw = localStorage.getItem(STORAGE_KEY);
if (!raw) return;
const parsed = JSON.parse(raw);
if (Array.isArray(parsed) && parsed.every(d => d.date && typeof d.value === 'number')) {
weightData = parsed;
}
} catch (e) { /* 静默处理错误 */ }
}
function saveData() {
localStorage.setItem(STORAGE_KEY, JSON.stringify(weightData));
drawChart();
}
// 添加调试日志
function addDebugLog(message) {
const timestamp = new Date().toLocaleTimeString();
debugLog.textContent += `[${timestamp}] ${message}\n`;
debugLog.scrollTop = debugLog.scrollHeight;
}
// 获取当前串口配置
function getSerialConfig() {
return {
baudRate: parseInt(document.getElementById('baudRateSelect').value),
dataBits: parseInt(document.getElementById('dataBitsSelect').value),
stopBits: parseInt(document.getElementById('stopBitsSelect').value),
parity: document.getElementById('paritySelect').value
};
}
// 连接设备逻辑
const connectBtn = document.getElementById('connectBtn');
const portStatus = document.getElementById('portStatus');
const weightInput = document.getElementById('weightInput');
connectBtn.addEventListener('click', async () => {
if (!('serial' in navigator)) {
alert('Web Serial API not supported. Please use Chrome or Edge version 89+.');
addDebugLog('Error: Web Serial API not supported');
return;
}
try {
if (!port) {
// 连接设备
portStatus.textContent = 'Connecting...';
portStatus.classList.add('error');
connectBtn.disabled = true;
addDebugLog('Attempting to connect to serial port...');
port = await navigator.serial.requestPort();
addDebugLog(`Port selected: ${port.getInfo().usbVendorId}:${port.getInfo().usbProductId}`);
// 获取当前配置并尝试连接
const config = getSerialConfig();
addDebugLog(`Connection configuration: ${JSON.stringify(config)}`);
// 尝试连接,带重试机制
let connected = false;
let attempts = 0;
const maxAttempts = 3;
while (!connected && attempts < maxAttempts) {
attempts++;
addDebugLog(`Connection attempt ${attempts} of ${maxAttempts}`);
try {
await port.open(config);
connected = true;
addDebugLog(`Connected successfully with config: ${JSON.stringify(config)}`);
break;
} catch (e) {
addDebugLog(`Attempt ${attempts} failed: ${e.message}`);
if (attempts < maxAttempts) {
addDebugLog(`Waiting 1 second before next attempt...`);
await new Promise(resolve => setTimeout(resolve, 1000)); // 等待1秒后重试
}
}
}
if (!connected) {
const errorMsg = `Failed to connect after ${maxAttempts} attempts`;
addDebugLog(errorMsg);
throw new Error(errorMsg);
}
portStatus.textContent = 'Connected';
portStatus.classList.remove('error');
connectBtn.textContent = 'Disconnect';
connectBtn.disabled = false;
// 启动读取循环
isReading = true;
await readLoop();
} else {
// 断开连接
await disconnectPort();
}
} catch (err) {
console.error('Connection error:', err);
portStatus.textContent = `Error: ${err.message}`;
portStatus.classList.add('error');
connectBtn.disabled = false;
// 重置状态
port = null;
isReading = false;
connectBtn.textContent = 'Connect Device';
addDebugLog(`Connection error: ${err.message}`);
}
});
// 断开连接的辅助函数
async function disconnectPort() {
addDebugLog('Attempting to disconnect...');
if (isReading && reader) {
try {
await reader.cancel(); // 取消读取操作
addDebugLog('Reader cancelled successfully');
} catch (e) {
addDebugLog('Error cancelling reader: ' + e.message);
}
}
if (port) {
try {
await port.close();
addDebugLog('Port closed successfully');
} catch (e) {
addDebugLog('Error closing port: ' + e.message);
}
}
port = null;
isReading = false;
reader = null;
portStatus.textContent = 'Disconnected';
connectBtn.textContent = 'Connect Device';
connectBtn.disabled = false;
}
// 读取串口数据
async function readLoop() {
if (!port || !port.readable) return;
reader = port.readable.getReader();
let buffer = '';
addDebugLog('Started reading data from port...');
try {
while (isReading) {
const { value, done } = await reader.read();
if (done) break;
buffer += new TextDecoder().decode(value);
const lines = buffer.split('\r\n');
buffer = lines.pop();
for (const line of lines) {
addDebugLog(`Received data: ${line}`);
const match = line.match(/(\d+\.?\d*)/);
if (match) {
const val = parseFloat(match[1]);
if (val >= 40 && val <= 150) {
weightInput.value = val.toFixed(1);
addDebugLog(`Weight detected: ${val}kg`);
}
}
}
}
} catch (err) {
addDebugLog('Reading error: ' + err.message);
if (err.name !== 'AbortError') { // 忽略主动取消的错误
portStatus.textContent = `Read Error: ${err.message}`;
portStatus.classList.add('error');
await disconnectPort();
}
} finally {
if (reader) {
reader.releaseLock();
reader = null;
}
isReading = false;
addDebugLog('Reading loop stopped');
}
}
// 添加记录
document.getElementById('addBtn').addEventListener('click', () => {
const rawVal = weightInput.value.trim();
if (!/^\d{2,3}(\.\d{1})?$/.test(rawVal)) {
alert('Please enter a valid weight value, e.g., 70.5');
return;
}
const val = parseFloat(parseFloat(rawVal).toFixed(1));
const today = new Date().toISOString().split('T')[0];
const existIndex = weightData.findIndex(d => d.date === today);
if (existIndex > -1) {
weightData[existIndex].value = val;
} else {
weightData.push({ date: today, value: val });
}
saveData();
});
// 绘制图表
function drawChart() {
const canvas = document.getElementById('trendChart');
const ctx = canvas.getContext('2d');
const info = document.getElementById('chartInfo');
canvas.width = canvas.offsetWidth * 2;
canvas.height = canvas.offsetHeight * 2;
ctx.scale(2, 2);
const W = canvas.offsetWidth;
const H = canvas.offsetHeight;
const padding = 40;
ctx.clearRect(0, 0, W, H);
if (weightData.length === 0) {
info.innerText = 'No data available';
return;
}
const values = weightData.map(d => d.value);
const minY = Math.min(...values) - 0.5;
const maxY = Math.max(...values) + 0.5;
ctx.strokeStyle = '#ddd';
ctx.beginPath();
ctx.moveTo(padding, padding);
ctx.lineTo(padding, H - padding);
ctx.lineTo(W - padding, H - padding);
ctx.stroke();
ctx.strokeStyle = '#007bff';
ctx.lineWidth = 2;
ctx.beginPath();
weightData.forEach((d, i) => {
const x = padding + (i / (weightData.length - 1 || 1)) * (W - 2 * padding);
const y = H - padding - ((d.value - minY) / (maxY - minY)) * (H - 2 * padding);
if (i === 0) ctx.moveTo(x, y);
else ctx.lineTo(x, y);
});
ctx.stroke();
ctx.fillStyle = '#666';
ctx.font = '12px sans-serif';
ctx.textAlign = 'center';
weightData.forEach((d, i) => {
const x = padding + (i / (weightData.length - 1 || 1)) * (W - 2 * padding);
ctx.fillText(new Date(d.date).toLocaleDateString('en-US'), x, H - padding + 15);
});
info.innerText = `Total ${weightData.length} records`;
}
// 页面卸载时清理
window.addEventListener('beforeunload', async () => {
await disconnectPort();
});
// 初始化
loadData();
drawChart();
</script>
</body>
</html>
有什么不同见解可以在评论区共同讨论

浙公网安备 33010602011771号