mirror of
https://github.com/whowechina/aic_pico.git
synced 2026-10-07 06:07:55 +03:00
955 lines
36 KiB
HTML
955 lines
36 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>AIC Pico Configuration</title>
|
|
<style>
|
|
/* 内联CSS以避免外部文件问题 */
|
|
* {
|
|
margin: 0;
|
|
padding: 0;
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
body {
|
|
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
min-height: 100vh;
|
|
color: #333;
|
|
}
|
|
|
|
.container {
|
|
max-width: 1200px;
|
|
margin: 0 auto;
|
|
padding: 20px;
|
|
}
|
|
|
|
header {
|
|
background: rgba(255, 255, 255, 0.95);
|
|
padding: 20px;
|
|
border-radius: 10px;
|
|
margin-bottom: 20px;
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
|
}
|
|
|
|
h1 {
|
|
color: #2c3e50;
|
|
font-size: 2em;
|
|
}
|
|
|
|
.connection-panel {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 15px;
|
|
}
|
|
|
|
.btn {
|
|
padding: 10px 20px;
|
|
border: none;
|
|
border-radius: 5px;
|
|
cursor: pointer;
|
|
font-size: 14px;
|
|
font-weight: 500;
|
|
transition: all 0.3s ease;
|
|
text-transform: uppercase;
|
|
}
|
|
|
|
.btn-primary {
|
|
background: #3498db;
|
|
color: white;
|
|
}
|
|
|
|
.btn-primary:hover {
|
|
background: #2980b9;
|
|
}
|
|
|
|
.btn-secondary {
|
|
background: #95a5a6;
|
|
color: white;
|
|
}
|
|
|
|
.btn-secondary:disabled,
|
|
.btn-secondary.saved {
|
|
background: #bdc3c7;
|
|
cursor: not-allowed;
|
|
opacity: 0.7;
|
|
}
|
|
|
|
.btn-success {
|
|
background: #27ae60;
|
|
color: white;
|
|
}
|
|
|
|
.btn-danger {
|
|
background: #e74c3c;
|
|
color: white;
|
|
}
|
|
|
|
.status {
|
|
padding: 5px 10px;
|
|
border-radius: 20px;
|
|
font-size: 12px;
|
|
font-weight: bold;
|
|
text-transform: uppercase;
|
|
}
|
|
|
|
.status.connected {
|
|
background: #d5f4e6;
|
|
color: #27ae60;
|
|
}
|
|
|
|
.status.disconnected {
|
|
background: #fadbd8;
|
|
color: #e74c3c;
|
|
}
|
|
|
|
.config-panel {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
|
|
gap: 20px;
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
.hidden {
|
|
display: none !important;
|
|
}
|
|
|
|
.card {
|
|
background: rgba(255, 255, 255, 0.95);
|
|
padding: 25px;
|
|
border-radius: 10px;
|
|
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
|
}
|
|
|
|
.card h2 {
|
|
color: #2c3e50;
|
|
margin-bottom: 20px;
|
|
border-bottom: 2px solid #3498db;
|
|
padding-bottom: 10px;
|
|
}
|
|
|
|
.setting-group {
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
.setting-group label {
|
|
display: block;
|
|
margin-bottom: 8px;
|
|
font-weight: 500;
|
|
color: #34495e;
|
|
}
|
|
|
|
.radio-group {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 15px;
|
|
}
|
|
|
|
.radio-group label {
|
|
display: flex;
|
|
align-items: center;
|
|
margin-bottom: 0;
|
|
cursor: pointer;
|
|
gap: 4px;
|
|
}
|
|
|
|
.radio-group input[type="radio"] {
|
|
margin: 0;
|
|
}
|
|
|
|
select, input[type="checkbox"] {
|
|
padding: 8px;
|
|
border: 2px solid #bdc3c7;
|
|
border-radius: 5px;
|
|
font-size: 14px;
|
|
}
|
|
|
|
.slider-group {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 15px;
|
|
}
|
|
|
|
.slider-item {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 8px;
|
|
}
|
|
|
|
.slider-item label {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
}
|
|
|
|
input[type="range"] {
|
|
width: 100%;
|
|
height: 8px;
|
|
border-radius: 5px;
|
|
background: #ddd;
|
|
outline: none;
|
|
-webkit-appearance: none;
|
|
}
|
|
|
|
input[type="range"]::-webkit-slider-thumb {
|
|
-webkit-appearance: none;
|
|
appearance: none;
|
|
width: 20px;
|
|
height: 20px;
|
|
border-radius: 50%;
|
|
background: #3498db;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.info-display {
|
|
background: #ecf0f1;
|
|
padding: 10px;
|
|
border-radius: 5px;
|
|
border-left: 4px solid #3498db;
|
|
font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
|
|
}
|
|
|
|
.button-group {
|
|
display: flex;
|
|
gap: 10px;
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
.debug-console {
|
|
background: rgba(255, 255, 255, 0.95);
|
|
padding: 20px;
|
|
border-radius: 10px;
|
|
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
|
}
|
|
|
|
.debug-output {
|
|
background: #2c3e50;
|
|
color: #ecf0f1;
|
|
padding: 15px;
|
|
border-radius: 5px;
|
|
height: 200px;
|
|
overflow-y: auto;
|
|
font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
|
|
font-size: 12px;
|
|
margin-bottom: 10px;
|
|
white-space: pre-wrap;
|
|
}
|
|
|
|
.debug-input {
|
|
display: flex;
|
|
gap: 10px;
|
|
}
|
|
|
|
.debug-input input {
|
|
flex: 1;
|
|
padding: 10px;
|
|
border: 2px solid #bdc3c7;
|
|
border-radius: 5px;
|
|
}
|
|
|
|
.error-message {
|
|
background: #fadbd8;
|
|
color: #e74c3c;
|
|
padding: 20px;
|
|
border-radius: 10px;
|
|
margin: 20px;
|
|
text-align: center;
|
|
}
|
|
|
|
.checkbox-group {
|
|
display: flex;
|
|
gap: 15px;
|
|
}
|
|
|
|
.checkbox-group label {
|
|
display: flex;
|
|
align-items: center;
|
|
margin-bottom: 0;
|
|
cursor: pointer;
|
|
gap: 8px;
|
|
}
|
|
|
|
.checkbox-group input[type="checkbox"] {
|
|
margin: 0;
|
|
}
|
|
|
|
/* 新增样式:禁用状态 */
|
|
.card.disabled {
|
|
opacity: 0.6;
|
|
pointer-events: none;
|
|
background: rgba(200, 200, 200, 0.95);
|
|
}
|
|
|
|
.card.disabled h2 {
|
|
color: #7f8c8d;
|
|
border-bottom-color: #bdc3c7;
|
|
}
|
|
|
|
.card.disabled input,
|
|
.card.disabled select,
|
|
.card.disabled button {
|
|
opacity: 0.7;
|
|
cursor: not-allowed;
|
|
}
|
|
|
|
/* 新增样式:加载指示器 */
|
|
.loading-indicator {
|
|
color: #95a5a6;
|
|
font-style: italic;
|
|
margin-top: 10px;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div id="app">
|
|
<!-- Web Serial API 支持检查 -->
|
|
<div id="unsupported" class="error-message" style="display: none;">
|
|
<h2>Web Serial API Not Supported</h2>
|
|
<p>This browser doesn't support Web Serial API.</p>
|
|
<p>Please use Chrome 89+ or Edge 89+ and access via HTTPS or localhost.</p>
|
|
</div>
|
|
|
|
<!-- 主界面 -->
|
|
<div id="mainApp" class="container">
|
|
<header>
|
|
<h1>AIC Pico Configuration</h1>
|
|
<div class="connection-panel">
|
|
<button id="connectBtn" class="btn btn-primary">CONNECT</button>
|
|
<span id="connectionStatus" class="status disconnected">Disconnected</span>
|
|
</div>
|
|
</header>
|
|
|
|
<main id="configPanel" class="config-panel hidden">
|
|
<!-- Light Settings -->
|
|
<section class="card">
|
|
<h2>Light Settings</h2>
|
|
<div class="setting-group">
|
|
<label>Light Mode:</label>
|
|
<div class="checkbox-group">
|
|
<label><input type="checkbox" id="rgbEnabled"> RGB</label>
|
|
<label><input type="checkbox" id="ledEnabled"> LED</label>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="setting-group">
|
|
<label>RGB Order:</label>
|
|
<select id="rgbOrder">
|
|
<option value="grb">GRB</option>
|
|
<option value="brg">BRG</option>
|
|
<option value="rgb">RGB</option>
|
|
<option value="rbg">RBG</option>
|
|
</select>
|
|
</div>
|
|
|
|
<div class="setting-group">
|
|
<label>Brightness Levels:</label>
|
|
<div class="slider-group">
|
|
<div class="slider-item">
|
|
<label>Idle: <span id="idleValue">24</span></label>
|
|
<input type="range" id="idleSlider" min="0" max="255" value="24">
|
|
</div>
|
|
<div class="slider-item">
|
|
<label>Active: <span id="activeValue">100</span></label>
|
|
<input type="range" id="activeSlider" min="0" max="255" value="100">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- Reader Settings -->
|
|
<section class="card">
|
|
<h2>Reader Settings</h2>
|
|
<div class="setting-group">
|
|
<label>
|
|
<input type="checkbox" id="virtualAic"> Virtual AIC
|
|
</label>
|
|
</div>
|
|
|
|
<div class="setting-group">
|
|
<label>Reader Mode:</label>
|
|
<select id="readerMode">
|
|
<option value="auto">Auto</option>
|
|
<option value="aime0">Aime 0</option>
|
|
<option value="aime1">Aime 1</option>
|
|
<option value="bana">Bandai Namco</option>
|
|
</select>
|
|
</div>
|
|
|
|
<div class="setting-group">
|
|
<label>Detected Mode:</label>
|
|
<span id="detectedMode" class="info-display">None</span>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- LCD Settings -->
|
|
<section class="card">
|
|
<h2>LCD Settings</h2>
|
|
<div class="setting-group">
|
|
<label>Backlight: <span id="lcdValue">200</span></label>
|
|
<input type="range" id="lcdSlider" min="0" max="255" value="200">
|
|
</div>
|
|
</section>
|
|
|
|
<!-- NFC Module Info -->
|
|
<section class="card">
|
|
<h2>NFC Module</h2>
|
|
<div id="nfcInfo" class="info-display">Unknown ()</div>
|
|
</section>
|
|
|
|
<!-- Action Buttons -->
|
|
<section class="card">
|
|
<h2>Actions</h2>
|
|
<div class="button-group">
|
|
<button id="refreshBtn" class="btn btn-success">Refresh</button>
|
|
<button id="saveBtn" class="btn btn-success">Save to Flash</button>
|
|
</div>
|
|
<div class="button-group" style="margin-top: 10px;">
|
|
<button id="factoryResetBtn" class="btn btn-danger">Factory Reset</button>
|
|
<button id="updateBtn" class="btn btn-danger">Update Firmware</button>
|
|
</div>
|
|
</section>
|
|
</main>
|
|
|
|
<!-- Debug Console -->
|
|
<section class="debug-console">
|
|
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px;">
|
|
<h3>Debug Console</h3>
|
|
<button id="clearBtn" class="btn btn-secondary">Clear</button>
|
|
</div>
|
|
<div id="debugOutput" class="debug-output"></div>
|
|
<div class="debug-input">
|
|
<input type="text" id="commandInput" placeholder="Enter command...">
|
|
<button id="sendBtn" class="btn btn-secondary">Send</button>
|
|
</div>
|
|
</section>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
// 检查Web Serial API支持
|
|
if (!('serial' in navigator)) {
|
|
document.getElementById('unsupported').style.display = 'block';
|
|
document.getElementById('mainApp').style.display = 'none';
|
|
} else {
|
|
// 主控制器类
|
|
class AICPicoController {
|
|
constructor() {
|
|
this.port = null;
|
|
this.reader = null;
|
|
this.writer = null;
|
|
this.connected = false;
|
|
this.hasUnsavedChanges = false;
|
|
this.configParseRetries = 0;
|
|
this.maxConfigRetries = 3;
|
|
this.configLoaded = false;
|
|
|
|
this.initializeElements();
|
|
this.attachEventListeners();
|
|
}
|
|
|
|
initializeElements() {
|
|
// Connection elements
|
|
this.connectBtn = document.getElementById('connectBtn');
|
|
this.connectionStatus = document.getElementById('connectionStatus');
|
|
this.configPanel = document.getElementById('configPanel');
|
|
|
|
// Control elements
|
|
this.lightModeRadios = document.querySelectorAll('input[name="lightMode"]');
|
|
this.rgbOrderSelect = document.getElementById('rgbOrder');
|
|
this.idleSlider = document.getElementById('idleSlider');
|
|
this.activeSlider = document.getElementById('activeSlider');
|
|
this.idleValue = document.getElementById('idleValue');
|
|
this.activeValue = document.getElementById('activeValue');
|
|
this.lcdSlider = document.getElementById('lcdSlider');
|
|
this.lcdValue = document.getElementById('lcdValue');
|
|
this.virtualAicCheck = document.getElementById('virtualAic');
|
|
this.readerModeSelect = document.getElementById('readerMode');
|
|
|
|
// Info displays
|
|
this.nfcInfo = document.getElementById('nfcInfo');
|
|
this.detectedMode = document.getElementById('detectedMode');
|
|
|
|
// Action buttons
|
|
this.refreshBtn = document.getElementById('refreshBtn');
|
|
this.saveBtn = document.getElementById('saveBtn');
|
|
this.updateBtn = document.getElementById('updateBtn');
|
|
this.factoryResetBtn = document.getElementById('factoryResetBtn');
|
|
|
|
// Debug console
|
|
this.debugOutput = document.getElementById('debugOutput');
|
|
this.commandInput = document.getElementById('commandInput');
|
|
this.sendBtn = document.getElementById('sendBtn');
|
|
this.clearBtn = document.getElementById('clearBtn');
|
|
|
|
// Light mode checkboxes
|
|
this.rgbEnabled = document.getElementById('rgbEnabled');
|
|
this.ledEnabled = document.getElementById('ledEnabled');
|
|
}
|
|
|
|
attachEventListeners() {
|
|
// Connection
|
|
this.connectBtn.addEventListener('click', () => this.toggleConnection());
|
|
|
|
// Slider updates
|
|
this.idleSlider.addEventListener('input', (e) => {
|
|
this.idleValue.textContent = e.target.value;
|
|
this.updateLightLevel();
|
|
});
|
|
|
|
this.activeSlider.addEventListener('input', (e) => {
|
|
this.activeValue.textContent = e.target.value;
|
|
this.updateLightLevel();
|
|
});
|
|
|
|
this.lcdSlider.addEventListener('input', (e) => {
|
|
this.lcdValue.textContent = e.target.value;
|
|
this.updateLcdBacklight();
|
|
});
|
|
|
|
// Light mode
|
|
this.lightModeRadios.forEach(radio => {
|
|
radio.addEventListener('change', () => this.updateLightMode());
|
|
});
|
|
|
|
// RGB order
|
|
this.rgbOrderSelect.addEventListener('change', () => this.updateRgbOrder());
|
|
|
|
// Virtual AIC
|
|
this.virtualAicCheck.addEventListener('change', () => this.updateVirtualAic());
|
|
|
|
// Reader mode
|
|
this.readerModeSelect.addEventListener('change', () => this.updateReaderMode());
|
|
|
|
// Action buttons
|
|
this.refreshBtn.addEventListener('click', () => this.refreshSettings());
|
|
this.saveBtn.addEventListener('click', () => this.saveSettings());
|
|
this.updateBtn.addEventListener('click', () => this.enterUpdateMode());
|
|
this.factoryResetBtn.addEventListener('click', () => this.factoryReset());
|
|
|
|
// Debug console
|
|
this.sendBtn.addEventListener('click', () => this.sendCommand());
|
|
this.clearBtn.addEventListener('click', () => this.clearConsole());
|
|
this.commandInput.addEventListener('keypress', (e) => {
|
|
if (e.key === 'Enter') this.sendCommand();
|
|
});
|
|
|
|
// Light mode checkboxes
|
|
this.rgbEnabled.addEventListener('change', () => this.updateLightMode());
|
|
this.ledEnabled.addEventListener('change', () => this.updateLightMode());
|
|
}
|
|
|
|
async toggleConnection() {
|
|
if (this.connected) {
|
|
await this.disconnect();
|
|
} else {
|
|
await this.connect();
|
|
}
|
|
}
|
|
|
|
async connect() {
|
|
try {
|
|
// Request serial port
|
|
this.port = await navigator.serial.requestPort();
|
|
await this.port.open({ baudRate: 115200 });
|
|
|
|
this.reader = this.port.readable.getReader();
|
|
this.writer = this.port.writable.getWriter();
|
|
|
|
this.connected = true;
|
|
this.updateConnectionUI();
|
|
|
|
// Start reading responses
|
|
this.startReading();
|
|
|
|
// 增加延迟并确保配置获取成功
|
|
setTimeout(async () => {
|
|
await this.refreshSettings();
|
|
// 如果2秒后仍未正确解析配置,再次尝试
|
|
setTimeout(() => {
|
|
this.retryConfigIfNeeded();
|
|
}, 2000);
|
|
}, 1000);
|
|
|
|
} catch (error) {
|
|
this.log(`Connection failed: ${error.message}`);
|
|
}
|
|
}
|
|
|
|
async disconnect() {
|
|
try {
|
|
if (this.reader) {
|
|
await this.reader.cancel();
|
|
await this.reader.releaseLock();
|
|
}
|
|
if (this.writer) {
|
|
await this.writer.releaseLock();
|
|
}
|
|
if (this.port) {
|
|
await this.port.close();
|
|
}
|
|
|
|
this.connected = false;
|
|
this.updateConnectionUI();
|
|
|
|
} catch (error) {
|
|
this.log(`Disconnect failed: ${error.message}`);
|
|
}
|
|
}
|
|
|
|
updateConnectionUI() {
|
|
if (this.connected) {
|
|
this.connectBtn.textContent = 'DISCONNECT';
|
|
this.connectionStatus.textContent = 'Connected';
|
|
this.connectionStatus.className = 'status connected';
|
|
this.configPanel.classList.remove('hidden');
|
|
|
|
// 连接后先禁用所有面板
|
|
this.setConfigPanelsState(false);
|
|
this.updateSaveButtonState();
|
|
} else {
|
|
this.connectBtn.textContent = 'CONNECT';
|
|
this.connectionStatus.textContent = 'Disconnected';
|
|
this.connectionStatus.className = 'status disconnected';
|
|
this.configPanel.classList.add('hidden');
|
|
this.configLoaded = false;
|
|
}
|
|
}
|
|
|
|
setConfigPanelsState(enabled) {
|
|
const cards = document.querySelectorAll('.card');
|
|
cards.forEach(card => {
|
|
if (enabled) {
|
|
card.classList.remove('disabled');
|
|
} else {
|
|
card.classList.add('disabled');
|
|
}
|
|
});
|
|
|
|
if (!enabled && this.connected) {
|
|
this.addLoadingIndicator();
|
|
} else {
|
|
this.removeLoadingIndicator();
|
|
}
|
|
}
|
|
|
|
addLoadingIndicator() {
|
|
const cards = document.querySelectorAll('.card h2');
|
|
cards.forEach(h2 => {
|
|
if (!h2.querySelector('.loading-indicator')) {
|
|
const indicator = document.createElement('span');
|
|
indicator.className = 'loading-indicator';
|
|
indicator.textContent = ' (Loading...)';
|
|
h2.appendChild(indicator);
|
|
}
|
|
});
|
|
}
|
|
|
|
removeLoadingIndicator() {
|
|
const indicators = document.querySelectorAll('.loading-indicator');
|
|
indicators.forEach(indicator => indicator.remove());
|
|
}
|
|
|
|
markConfigChanged() {
|
|
this.hasUnsavedChanges = true;
|
|
this.updateSaveButtonState();
|
|
}
|
|
|
|
markConfigSaved() {
|
|
this.hasUnsavedChanges = false;
|
|
this.updateSaveButtonState();
|
|
}
|
|
|
|
updateSaveButtonState() {
|
|
if (this.hasUnsavedChanges) {
|
|
this.saveBtn.textContent = 'Save to Flash';
|
|
this.saveBtn.className = 'btn btn-success';
|
|
this.saveBtn.disabled = false;
|
|
} else {
|
|
this.saveBtn.textContent = 'Saved';
|
|
this.saveBtn.className = 'btn btn-secondary saved';
|
|
this.saveBtn.disabled = true;
|
|
}
|
|
}
|
|
|
|
async startReading() {
|
|
try {
|
|
while (this.connected) {
|
|
const { value, done } = await this.reader.read();
|
|
if (done) break;
|
|
|
|
const text = new TextDecoder().decode(value);
|
|
this.log(text, 'response');
|
|
}
|
|
} catch (error) {
|
|
if (this.connected) {
|
|
this.log(`Read error: ${error.message}`);
|
|
}
|
|
}
|
|
}
|
|
|
|
async sendCommand(command = null) {
|
|
if (!this.connected) return;
|
|
|
|
const cmd = command || this.commandInput.value.trim();
|
|
if (!cmd) return;
|
|
|
|
try {
|
|
const data = new TextEncoder().encode(cmd + '\n');
|
|
await this.writer.write(data);
|
|
|
|
if (!command) {
|
|
this.commandInput.value = '';
|
|
}
|
|
} catch (error) {
|
|
this.log(`Send error: ${error.message}`);
|
|
}
|
|
}
|
|
|
|
parseAndUpdateConfig(message) {
|
|
let configParsed = false;
|
|
|
|
if (message.includes('RGB-')) {
|
|
const rgbMatch = message.match(/RGB-(\w+) \((\w+)\), LED-(\w+)/);
|
|
if (rgbMatch) {
|
|
this.rgbEnabled.checked = rgbMatch[1] === 'ON';
|
|
this.rgbOrderSelect.value = rgbMatch[2].toLowerCase();
|
|
this.ledEnabled.checked = rgbMatch[3] === 'ON';
|
|
configParsed = true;
|
|
}
|
|
}
|
|
|
|
if (message.includes('Level:')) {
|
|
const levelMatch = message.match(/Level: Idle-(\d+), Active-(\d+)/);
|
|
if (levelMatch) {
|
|
this.idleSlider.value = levelMatch[1];
|
|
this.idleValue.textContent = levelMatch[1];
|
|
this.activeSlider.value = levelMatch[2];
|
|
this.activeValue.textContent = levelMatch[2];
|
|
}
|
|
}
|
|
|
|
if (message.includes('Virtual AIC:')) {
|
|
const virtualMatch = message.match(/Virtual AIC: (ON|OFF)/);
|
|
if (virtualMatch) {
|
|
this.virtualAicCheck.checked = virtualMatch[1] === 'ON';
|
|
configParsed = true;
|
|
}
|
|
}
|
|
|
|
if (message.includes('Mode:')) {
|
|
const modeMatch = message.match(/Mode: (\w+)/);
|
|
if (modeMatch) {
|
|
const mode = modeMatch[1].toLowerCase();
|
|
if (['auto', 'aime0', 'aime1', 'bana'].includes(mode)) {
|
|
this.readerModeSelect.value = mode;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (message.includes('Detected:')) {
|
|
const detectedMatch = message.match(/Detected: (\w+)/);
|
|
if (detectedMatch) {
|
|
this.detectedMode.textContent = detectedMatch[1];
|
|
}
|
|
}
|
|
|
|
if (message.includes('Backlight:')) {
|
|
const backlightMatch = message.match(/Backlight: (\d+)/);
|
|
if (backlightMatch) {
|
|
this.lcdSlider.value = backlightMatch[1];
|
|
this.lcdValue.textContent = backlightMatch[1];
|
|
configParsed = true;
|
|
}
|
|
}
|
|
|
|
if (configParsed && !this.configLoaded) {
|
|
this.configLoaded = true;
|
|
this.setConfigPanelsState(true);
|
|
}
|
|
}
|
|
|
|
log(message, type = 'info') {
|
|
// 检测保存完成消息
|
|
if (message.includes('Program Flash')) {
|
|
this.markConfigSaved();
|
|
}
|
|
|
|
// 简化配置解析触发条件 - 严格检测4个空格开头的配置行
|
|
if (message.includes(' RGB-') || message.includes(' Virtual AIC:') ||
|
|
message.includes(' Mode:') || message.includes(' Level:') ||
|
|
message.includes(' Backlight:') || message.includes(' Detected:') ||
|
|
message.includes('PN532') || message.includes('PN5180')) {
|
|
this.parseAndUpdateConfig(message);
|
|
}
|
|
|
|
// 对消息进行色彩处理
|
|
const coloredMessage = this.colorizeMessage(message);
|
|
this.debugOutput.innerHTML += coloredMessage;
|
|
this.debugOutput.scrollTop = this.debugOutput.scrollHeight;
|
|
}
|
|
|
|
colorizeMessage(message) {
|
|
// 转义HTML特殊字符,但保留原始格式
|
|
let escaped = message
|
|
.replace(/&/g, '&')
|
|
.replace(/</g, '<')
|
|
.replace(/>/g, '>');
|
|
|
|
// 添加色彩高亮
|
|
let colored = escaped
|
|
// [Section] 标题用黄色
|
|
.replace(/(\[.*?\])/g, '<span style="color: #f39c12;">$1</span>')
|
|
// 数字用青色
|
|
.replace(/\b(\d+)\b/g, '<span style="color: #3498db;">$1</span>')
|
|
// ON/OFF 状态用绿色/红色
|
|
.replace(/\b(ON)\b/g, '<span style="color: #27ae60;">$1</span>')
|
|
.replace(/\b(OFF)\b/g, '<span style="color: #e74c3c;">$1</span>')
|
|
// 冒号后的值用浅蓝色
|
|
.replace(/:\s*([A-Za-z0-9-]+)/g, ': <span style="color: #85c1e9;">$1</span>');
|
|
|
|
// 将换行符转换为<br>标签
|
|
return colored.replace(/\r?\n/g, '<br>');
|
|
}
|
|
|
|
// Settings update methods
|
|
async updateLightLevel() {
|
|
const idle = this.idleSlider.value;
|
|
const active = this.activeSlider.value;
|
|
await this.sendCommand(`level ${idle} ${active}`);
|
|
this.markConfigChanged();
|
|
}
|
|
|
|
async updateLcdBacklight() {
|
|
const backlight = this.lcdSlider.value;
|
|
await this.sendCommand(`lcd ${backlight}`);
|
|
this.markConfigChanged();
|
|
}
|
|
|
|
async updateLightMode() {
|
|
const rgbOn = this.rgbEnabled.checked;
|
|
const ledOn = this.ledEnabled.checked;
|
|
|
|
let mode;
|
|
if (rgbOn && ledOn) {
|
|
mode = 'both';
|
|
} else if (rgbOn) {
|
|
mode = 'rgb';
|
|
} else if (ledOn) {
|
|
mode = 'led';
|
|
} else {
|
|
mode = 'off';
|
|
}
|
|
|
|
await this.sendCommand(`light ${mode}`);
|
|
this.markConfigChanged();
|
|
}
|
|
|
|
async updateRgbOrder() {
|
|
const order = this.rgbOrderSelect.value;
|
|
await this.sendCommand(`rgb-order ${order}`);
|
|
this.markConfigChanged();
|
|
}
|
|
|
|
async updateVirtualAic() {
|
|
const enabled = this.virtualAicCheck.checked ? 'on' : 'off';
|
|
await this.sendCommand(`virtual ${enabled}`);
|
|
this.markConfigChanged();
|
|
}
|
|
|
|
async updateReaderMode() {
|
|
const mode = this.readerModeSelect.value;
|
|
await this.sendCommand(`mode ${mode}`);
|
|
this.markConfigChanged();
|
|
}
|
|
|
|
async refreshSettings() {
|
|
this.configParseRetries = 0;
|
|
this.configLoaded = false; // 重置配置加载状态
|
|
this.setConfigPanelsState(false); // 禁用面板
|
|
|
|
await this.sendCommand('display');
|
|
// 初始加载配置时标记为已保存状态
|
|
setTimeout(() => {
|
|
this.markConfigSaved();
|
|
}, 1000);
|
|
}
|
|
|
|
retryConfigIfNeeded() {
|
|
// 检查关键配置是否已加载
|
|
const hasLightConfig = this.rgbEnabled.checked || this.ledEnabled.checked ||
|
|
this.idleSlider.value !== '24' || this.activeSlider.value !== '100';
|
|
const hasReaderConfig = this.virtualAicCheck.checked || this.readerModeSelect.value !== 'auto';
|
|
const hasLcdConfig = this.lcdSlider.value !== '200';
|
|
|
|
// 如果配置看起来还是默认值,重新获取
|
|
if (!hasLightConfig && !hasReaderConfig && !hasLcdConfig) {
|
|
this.log('Configuration not loaded, retrying...');
|
|
this.refreshSettings();
|
|
}
|
|
}
|
|
|
|
async saveSettings() {
|
|
await this.sendCommand('save');
|
|
// 不要立即标记为已保存,等待设备确认
|
|
}
|
|
|
|
async enterUpdateMode() {
|
|
const confirmMessage = 'This will disconnect the device and enter update mode.\n\n' +
|
|
'You will need to manually copy the firmware file (.uf2) to the RPI-RP2 drive that appears.\n\n' +
|
|
'Do you want to proceed?';
|
|
|
|
if (confirm(confirmMessage)) {
|
|
try {
|
|
await this.sendCommand('update');
|
|
// 设备会自动断开连接进入bootloader模式
|
|
setTimeout(() => {
|
|
this.log('Device entered update mode. Look for RPI-RP2 drive and copy firmware file.');
|
|
}, 1000);
|
|
} catch (error) {
|
|
this.log(`Update mode failed: ${error.message}`);
|
|
}
|
|
}
|
|
}
|
|
|
|
clearConsole() {
|
|
this.debugOutput.innerHTML = '';
|
|
}
|
|
|
|
async factoryReset() {
|
|
const confirmMessage = 'This will reset all settings to factory defaults.\n\n' +
|
|
'All your custom configurations will be lost permanently.\n\n' +
|
|
'Are you sure you want to proceed?';
|
|
|
|
if (confirm(confirmMessage)) {
|
|
try {
|
|
await this.sendCommand('factory');
|
|
this.log('Factory reset initiated. Device will restart with default settings.');
|
|
// 重置后标记配置已保存(因为会恢复到默认值)
|
|
this.markConfigSaved();
|
|
|
|
// 等待设备重启后自动刷新配置显示
|
|
setTimeout(() => {
|
|
this.refreshSettings();
|
|
this.log('Refreshing settings after factory reset...');
|
|
}, 2000); // 等待2秒让设备完成重启
|
|
|
|
} catch (error) {
|
|
this.log(`Factory reset failed: ${error.message}`);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// 初始化控制器
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
window.aicController = new AICPicoController();
|
|
});
|
|
}
|
|
</script>
|
|
</body>
|
|
</html>
|