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>

posted @ 2026-06-12 14:44  lambertlt  阅读(5)  评论(0)    收藏  举报