diff --git a/Configurator/index.html b/Configurator/index.html index cd411bd..61b949c 100644 --- a/Configurator/index.html +++ b/Configurator/index.html @@ -72,6 +72,13 @@ color: white; } + .btn-secondary:disabled, + .btn-secondary.saved { + background: #bdc3c7; + cursor: not-allowed; + opacity: 0.7; + } + .btn-success { background: #27ae60; color: white; @@ -252,6 +259,49 @@ 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; + } @@ -279,11 +329,9 @@

Light Settings

-
- - - - +
+ +
@@ -356,9 +404,12 @@

Actions

- + +
+
+
@@ -391,6 +442,10 @@ 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(); @@ -421,13 +476,18 @@ // 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'); // 添加这行 + this.clearBtn = document.getElementById('clearBtn'); + + // Light mode checkboxes + this.rgbEnabled = document.getElementById('rgbEnabled'); + this.ledEnabled = document.getElementById('ledEnabled'); } attachEventListeners() { @@ -467,14 +527,19 @@ // 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.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() { @@ -500,8 +565,14 @@ // Start reading responses this.startReading(); - // Refresh settings after connection - setTimeout(() => this.refreshSettings(), 1000); + // 增加延迟并确保配置获取成功 + setTimeout(async () => { + await this.refreshSettings(); + // 如果2秒后仍未正确解析配置,再次尝试 + setTimeout(() => { + this.retryConfigIfNeeded(); + }, 2000); + }, 1000); } catch (error) { this.log(`Connection failed: ${error.message}`); @@ -535,11 +606,72 @@ 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; } } @@ -568,7 +700,6 @@ try { const data = new TextEncoder().encode(cmd + '\n'); await this.writer.write(data); - // 移除本地回显 - 不再调用 this.log if (!command) { this.commandInput.value = ''; @@ -578,7 +709,83 @@ } } + 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; @@ -613,52 +820,128 @@ 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 selected = document.querySelector('input[name="lightMode"]:checked'); - if (selected) { - await this.sendCommand(`light ${selected.value}`); + 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 factoryReset() { - if (confirm('Are you sure you want to factory reset? This will erase all settings.')) { - await this.sendCommand('factory'); + 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}`); + } + } + } } // 初始化控制器 @@ -668,4 +951,4 @@ } - \ No newline at end of file +