boothifier/data/www/ata-boothifier-upgrade.html
2025-09-28 23:18:18 -07:00

816 lines
33 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ATA Firmware Update</title>
<style>
/* --- CSS Variables for Easy Theming --- */
:root {
--color-bg: #f8f9fa;
--color-surface: #ffffff;
--color-text: #212529;
--color-text-muted: #6c757d;
--color-primary: #007bff;
--color-primary-hover: #0056b3;
--color-disabled: #e9ecef;
--color-disabled-text: #6c757d;
--color-border: #dee2e6;
--color-success: #28a745;
--color-warning: #ffc107;
--font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
--border-radius: 8px;
--shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
--transition-speed: 0.3s;
}
/* --- Base & Layout --- */
* {
box-sizing: border-box;
}
body {
font-family: var(--font-family);
margin: 0;
padding: 20px;
background-color: var(--color-bg);
color: var(--color-text);
display: grid;
place-items: center;
min-height: 100vh;
}
main {
width: 100%;
max-width: 500px;
background: var(--color-surface);
border-radius: var(--border-radius);
box-shadow: var(--shadow);
padding: 24px 32px;
}
/* --- Typography --- */
h1 {
font-size: 24px;
text-align: center;
margin-bottom: 16px;
color: var(--color-text);
}
h2 {
font-size: 20px;
margin: 0 0 16px;
border-bottom: 1px solid var(--color-border);
padding-bottom: 8px;
}
label {
font-weight: 500;
}
/* --- Tabs --- */
.tab-bar {
display: flex;
gap: 8px;
margin-bottom: 24px;
border-bottom: 1px solid var(--color-border);
}
.tab-bar button {
flex: 1;
padding: 10px 15px;
font-size: 16px;
border: none;
border-bottom: 3px solid transparent;
cursor: pointer;
background-color: transparent;
color: var(--color-text-muted);
transition: all var(--transition-speed) ease;
border-radius: 0;
font-weight: 600;
}
.tab-bar button:hover {
color: var(--color-primary);
}
.tab-bar button.active {
color: var(--color-primary);
border-bottom-color: var(--color-primary);
}
.tab-panel { display: none; }
.tab-panel.active { display: block; }
/* --- Status Indicators --- */
.status-grid {
display: grid;
grid-template-columns: auto 1fr;
align-items: center;
gap: 10px 15px;
margin-bottom: 20px;
}
.status-indicator {
width: 12px;
height: 12px;
border-radius: 50%;
background-color: var(--color-text-muted);
transition: background-color var(--transition-speed) ease;
}
.status-indicator.is-success { background-color: var(--color-success); }
.status-indicator.is-warning { background-color: var(--color-warning); }
.status-label {
font-size: 15px;
color: var(--color-text);
}
/* --- Form Elements & Buttons --- */
.form-group {
margin-bottom: 16px;
}
.form-group label {
display: block;
margin-bottom: 6px;
font-size: 14px;
color: var(--color-text-muted);
}
input[type="text"],
input[type="password"] {
width: 100%;
padding: 12px;
font-size: 16px;
border: 1px solid var(--color-border);
border-radius: var(--border-radius);
transition: border-color var(--transition-speed) ease, box-shadow var(--transition-speed) ease;
}
input[type="text"]:focus,
input[type="password"]:focus {
outline: none;
border-color: var(--color-primary);
box-shadow: 0 0 0 3px rgba(0, 123, 255, 0.25);
}
/* Radio button styling */
input[type="radio"] {
width: 16px;
height: 16px;
margin: 0;
accent-color: var(--color-primary);
}
.password-toggle {
display: flex;
align-items: center;
gap: 8px;
font-size: 14px;
margin-top: 8px;
}
.password-toggle input {
width: auto;
}
.btn-container {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 12px;
margin: 20px 0;
}
button {
flex: 1;
min-width: 140px;
padding: 12px 20px;
font-size: 16px;
font-weight: 600;
border: none;
border-radius: var(--border-radius);
cursor: pointer;
background-color: var(--color-primary);
color: white;
transition: background-color var(--transition-speed) ease, transform var(--transition-speed) ease;
}
button:hover:not(:disabled) {
background-color: var(--color-primary-hover);
transform: translateY(-2px);
}
button:disabled {
background-color: var(--color-disabled);
color: var(--color-disabled-text);
cursor: not-allowed;
}
/* --- Log Area --- */
textarea#logArea {
width: 100%;
height: 250px;
font-family: "SF Mono", "Consolas", "Menlo", monospace;
font-size: 13px;
padding: 15px;
border-radius: var(--border-radius);
border: 1px solid var(--color-border);
background-color: var(--color-bg);
resize: vertical;
line-height: 1.5;
}
/* --- Responsive --- */
@media (max-width: 540px) {
body {
padding: 10px;
}
main {
padding: 16px 20px;
}
h1 {
font-size: 22px;
}
}
</style>
</head>
<body>
<main>
<h1>ATA Firmware Update</h1>
<div class="tab-bar">
<button class="tab-btn active" data-tab="tab-upgrade">Upgrade</button>
<button class="tab-btn" data-tab="tab-wifi">WiFi Settings</button>
<button class="tab-btn" data-tab="tab-info">Info</button>
</div>
<div id="tab-upgrade" class="tab-panel active">
<div class="status-grid">
<div class="status-indicator" id="indicator-ble"></div>
<div class="status-label" id="status-ble-connection">BLE Status: ...</div>
<div class="status-indicator" id="indicator-wifi"></div>
<div class="status-label" id="status-wifi-client">WiFi Client: ...</div>
<div class="status-indicator" id="indicator-internet"></div>
<div class="status-label" id="status-internet">Internet: ...</div>
<div class="status-indicator" id="indicator-temperature"></div>
<div class="status-label" id="status-temperature">Temperature: ...</div>
<span></span> <div class="status-label" id="status-current-version">Current Version: ...</div>
<span></span> <div class="status-label" id="status-new-version">New Version: ...</div>
</div>
<div class="form-group">
<label for="input-DeviceName" id="ble-device-name-label">Device Name</label>
<input type="text" id="input-DeviceName" required>
</div>
<div class="btn-container">
<button id="bleConnectBtn" onclick="connectToBle()">Connect</button>
<button id="checkStatusBtn" onclick="checkStatus()" disabled>Check Status</button>
</div>
<textarea id="logArea" readonly></textarea>
<!-- Update Mode Selection -->
<div class="form-group">
<label>Update Mode:</label>
<div style="display: flex; gap: 15px; margin-top: 8px; flex-wrap: wrap;">
<label style="display: flex; align-items: center; gap: 6px; font-weight: normal; cursor: pointer;">
<input type="radio" name="updateMode" value="both" checked>
Update Both
</label>
<label style="display: flex; align-items: center; gap: 6px; font-weight: normal; cursor: pointer;">
<input type="radio" name="updateMode" value="files">
Files Only
</label>
<label style="display: flex; align-items: center; gap: 6px; font-weight: normal; cursor: pointer;">
<input type="radio" name="updateMode" value="firmware">
Firmware Only
</label>
</div>
</div>
<div class="btn-container">
<button id="checkVersionBtn" onclick="checkVersion()" disabled>Check Version</button>
<button id="startUpgradeBtn" onclick="startUpgrade()" disabled>Start Update</button>
</div>
</div>
<div id="tab-wifi" class="tab-panel">
<h2>WiFi Connection</h2>
<div class="form-group">
<label for="wifissid">WiFi Name (SSID)</label>
<input type="text" id="wifissid" name="wifissid" placeholder="Enter WiFi SSID" required>
</div>
<div class="form-group">
<label for="wifipassword">WiFi Password</label>
<input type="password" id="wifipassword" name="wifipassword" placeholder="Enter WiFi Password" required>
<div class="password-toggle">
<input type="checkbox" id="showPassword" onclick="togglePasswordVisibility()">
<label for="showPassword">Show Password</label>
</div>
</div>
<div class="btn-container">
<button id="wifiConnectBtn" onclick="wifiConnect()" disabled>Connect to WiFi</button>
</div>
</div>
<div id="tab-info" class="tab-panel">
<h2>Instructions</h2>
<textarea readonly style="width: 100%; height: 640px; font-family: var(--font-family); font-size: 14px; padding: 15px; border-radius: var(--border-radius); border: 1px solid var(--color-border); background-color: var(--color-bg); resize: vertical; line-height: 1.6;">ATA Firmware Update Tool - Instructions
OVERVIEW:
This tool allows you to update your ATA Boothifier device firmware and configuration files over Bluetooth Low Energy (BLE) and WiFi. You can choose to update files only, firmware only, or both.
GETTING STARTED:
1. SET DEVICE/BOARD IN UPGRADE MODE
• hold button 1 and click the RST button on the board.
- If device is in upgrade mode buzzer will beep every 5 secs.
- LED1 (L1) will be solid.
- RGB LEDS will be set to solid red.
2. CONNECT TO DEVICE
• Make sure your device is powered on and in upgrade mode
• Click "Connect" button to scan for and connect to your device
• The device name should appear as "ATALIGHTS" by default
• Wait for BLE Status to show "Connected"
3. CHECK STATUS
• When connected it will update status automatically:
- WiFi connection status
- Internet connectivity status
- Current firmware version
4. CONNECT TO WIFI (if needed)
• If WiFi is not connected, go to "WiFi Settings" tab
• Enter your WiFi network name (SSID) and password
• Click "Connect to WiFi"
• Return to "Upgrade" tab once connected
5. CHECK FOR UPDATES
• Click "Check Version" to see if new firmware is available
• This requires internet connection through WiFi
• New version will be displayed if available
6. CHOOSE UPDATE MODE
• Update Both: Updates files and firmware (requires restart)
• Files Only: Updates configuration/web files only (no restart)
• Firmware Only: Updates firmware only (requires restart)
7. START UPDATE
• Click "Start Update" to begin the update process
• Monitor progress in the log area
• DO NOT disconnect or power off during update
• Device will restart automatically if firmware was updated
TROUBLESHOOTING:
• BLE Connection Issues:
- Ensure device is in upgrade mode
- Try refreshing the page and reconnecting
- Make sure no other devices are connected to the ATA device
• WiFi Connection Issues:
- Double-check SSID and password
- Ensure WiFi network is available and has internet access
- Some networks may block the device
• Update Issues:
- Ensure stable internet connection
- Try "Files Only" update if firmware update fails
- Check log messages for specific error details
IMPORTANT NOTES:
• Always ensure stable power during firmware updates
• Device will restart after firmware updates
• Files-only updates do not require restart
• Log area shows detailed progress and error information
• You can copy text from the log area for troubleshooting</textarea>
</div>
</main>
<script>
(function(){
'use strict';
/* ================= Constants & Packet Layout ================= */
const BLE_SERVER_NAME = "ATALIGHTS";
const BLE_SERVICE_UUID = "abcdef01-2345-6789-1234-56789abcdef0";
const BLE_CHARACTERISTIC1_UUID = "abcdef01-2345-6789-1234-56789abcdef1";
const BLE_CHARACTERISTIC2_UUID = "abcdef02-2345-6789-1234-56789abcdef1";
const PACKET_LEN = 30; // Packed struct: 1+1+4+4+3+3+20 = 36 bytes, but check actual size
const OFF_WIFI_STATUS = 0, OFF_WIFI_ONLINE = 1, OFF_TEMPERATURE = 2;
const OFF_WIFI_IP = 6, OFF_CURR_VER = 10, OFF_NEW_VER = 13, OFF_WIFI_SSID = 16;
const WIFI_STAT = { DISCONNECTED:0, BAD_CREDS:1, NO_AP:2, CONNECTED:3 };
const WIFI_STAT_TEXT = ["Disconnected", "Bad Credentials", "AP Not Found", "Connected"];
const MAX_LOG_LINES = 400;
/* ================= State ================= */
let bleDevice = null, bleCharacteristic1 = null, bleCharacteristic2 = null;
let bleConnected = false;
let versionCheckPerformed = false;
const state = {
wifiStatus: WIFI_STAT.DISCONNECTED,
wifiOnline: false,
temperature: 0.0,
wifiIP: [0, 0, 0, 0],
currVersion: [0, 0, 0],
newVersion: [0, 0, 0],
wifiSSID: ""
};
/* ================= Cached DOM ================= */
// Cache DOM elements for performance and easier access
const el = {};
function cacheDom() {
el.bleIndicator = document.getElementById('indicator-ble');
el.wifiIndicator = document.getElementById('indicator-wifi');
el.internetIndicator = document.getElementById('indicator-internet');
el.temperatureIndicator = document.getElementById('indicator-temperature');
el.lblBle = document.getElementById('status-ble-connection');
el.lblWifi = document.getElementById('status-wifi-client');
el.lblInternet = document.getElementById('status-internet');
el.lblTemperature = document.getElementById('status-temperature');
el.lblCurrVer = document.getElementById('status-current-version');
el.lblNewVer = document.getElementById('status-new-version');
el.lblDeviceName = document.getElementById('ble-device-name-label');
el.inDeviceName = document.getElementById('input-DeviceName');
el.inSsid = document.getElementById('wifissid');
el.inPass = document.getElementById('wifipassword');
el.chkShowPass = document.getElementById('showPassword');
el.btnBleConnect = document.getElementById('bleConnectBtn');
el.btnCheckStatus = document.getElementById('checkStatusBtn');
el.btnCheckVersion = document.getElementById('checkVersionBtn');
el.btnStartUpgrade = document.getElementById('startUpgradeBtn');
el.btnWifiConnect = document.getElementById('wifiConnectBtn');
el.logArea = document.getElementById('logArea');
}
/* ================= Utilities ================= */
const logMessage = (msg, overwriteLastLine = false) => {
// Append message to log area, managing max lines and scrolling
if (!el.logArea) return;
let lines = el.logArea.value.length ? el.logArea.value.replace(/\r/g, '').split('\n') : [];
if (lines.length && lines[lines.length - 1] === '') lines.pop(); // remove trailing empty from previous newline
const entry = `-> ${msg}`;
// Handle line management
if (overwriteLastLine && lines.length > 0) {
// Replace the last line for progress updates
lines[lines.length - 1] = entry;
} else {
// Add a new line
lines.push(entry);
}
// Trim log if too long
if (lines.length > MAX_LOG_LINES) {
lines = lines.slice(lines.length - MAX_LOG_LINES);
}
el.logArea.value = lines.join('\n');
el.logArea.scrollTop = el.logArea.scrollHeight;
};
// Compare version arrays [major, minor, patch]
// Returns 1 if a > b, -1 if a < b, 0
const compareVersions = (a, b) => {
for(let i = 0; i < 3; i++) {
if(a[i] > b[i]) return 1;
if(a[i] < b[i]) return -1;
}
return 0;
};
const ipToString = (ip) => ip.join('.');
const setIndicatorStatus = (elm, status) => {
if (elm) elm.className = `status-indicator ${status}`;
};
/* ================= Packet Handling ================= */
function parsePacket(data) {
if(data.length < PACKET_LEN) return false;
// Debug: Log received packet info
console.log(`Received packet: ${data.length} bytes`);
console.log('Packet bytes:', Array.from(data.slice(0, 12)).map(b => b.toString(16).padStart(2, '0')).join(' '));
state.wifiStatus = data[OFF_WIFI_STATUS];
if(state.wifiStatus > WIFI_STAT.CONNECTED) state.wifiStatus = WIFI_STAT.DISCONNECTED;
state.wifiOnline = !!data[OFF_WIFI_ONLINE];
// Parse temperature (4-byte little-endian float at offset 2)
const tempBytes = data.slice(OFF_TEMPERATURE, OFF_TEMPERATURE + 4);
const tempDataView = new DataView(tempBytes.buffer, tempBytes.byteOffset, 4);
state.temperature = tempDataView.getFloat32(0, true); // true = little-endian
// Debug: Log temperature parsing
//console.log(`Temperature bytes: ${Array.from(tempBytes).map(b => b.toString(16).padStart(2, '0')).join(' ')}`);
//console.log(`Parsed temperature: ${state.temperature}`);
state.wifiIP = Array.from(data.slice(OFF_WIFI_IP, OFF_WIFI_IP + 4));
state.currVersion = Array.from(data.slice(OFF_CURR_VER, OFF_CURR_VER + 3));
state.newVersion = Array.from(data.slice(OFF_NEW_VER, OFF_NEW_VER + 3));
let rawSsidBytes = data.slice(OFF_WIFI_SSID, OFF_WIFI_SSID + 20);
let zeroIndex = rawSsidBytes.indexOf(0);
if(zeroIndex >= 0) rawSsidBytes = rawSsidBytes.slice(0, zeroIndex);
state.wifiSSID = rawSsidBytes.length ? String.fromCharCode(...rawSsidBytes) : "";
return true;
}
function updateUI() {
// BLE
el.lblBle.textContent = 'BLE Status: ' + (bleConnected ? 'Connected' : 'Disconnected');
setIndicatorStatus(el.bleIndicator, bleConnected ? 'is-success' : '');
// WiFi client
const statText = WIFI_STAT_TEXT[state.wifiStatus] || 'Unknown';
if(state.wifiStatus === WIFI_STAT.CONNECTED) {
const ssidPart = state.wifiSSID ? ` to "${state.wifiSSID}"` : '';
const ipPart = state.wifiIP[0] ? ` (${ipToString(state.wifiIP)})` : '';
el.lblWifi.textContent = 'WiFi Client: ' + statText + ssidPart + ipPart;
setIndicatorStatus(el.wifiIndicator, 'is-success');
} else {
el.lblWifi.textContent = 'WiFi Client: ' + statText;
const statusClass = (state.wifiStatus === WIFI_STAT.BAD_CREDS || state.wifiStatus === WIFI_STAT.NO_AP) ? 'is-warning' : '';
setIndicatorStatus(el.wifiIndicator, statusClass);
}
// Internet
el.lblInternet.textContent = 'Internet: ' + (state.wifiOnline ? 'Online' : 'Offline');
setIndicatorStatus(el.internetIndicator, state.wifiOnline ? 'is-success' : '');
// Temperature
if(state.temperature > 0) {
el.lblTemperature.textContent = `Temperature: ${state.temperature.toFixed(1)}°F`;
// Set indicator based on temperature ranges
if(state.temperature > 90) {
setIndicatorStatus(el.temperatureIndicator, 'is-warning'); // Warning for high temp
} else if(state.temperature > 0) {
setIndicatorStatus(el.temperatureIndicator, 'is-success'); // Normal temp
} else {
setIndicatorStatus(el.temperatureIndicator, ''); // Unknown/invalid temp
}
} else {
el.lblTemperature.textContent = 'Temperature: ...';
setIndicatorStatus(el.temperatureIndicator, '');
}
// Versions
el.lblCurrVer.textContent = state.currVersion[0] ? 'Current Version: ' + state.currVersion.join('.') : 'Current Version: ...';
el.lblNewVer.textContent = state.newVersion[0] ? 'New Version: ' + state.newVersion.join('.') : 'New Version: ...';
// Buttons
el.btnBleConnect.disabled = bleConnected;
el.btnBleConnect.textContent = bleConnected ? 'Connected' : 'Connect';
el.btnCheckStatus.disabled = !bleConnected;
el.btnWifiConnect.disabled = !bleConnected;
el.btnCheckVersion.disabled = !(bleConnected && state.wifiOnline);
const newerAvail = versionCheckPerformed && state.newVersion[0] && state.wifiOnline && compareVersions(state.newVersion, state.currVersion) > 0;
el.btnStartUpgrade.disabled = !newerAvail;
}
/* ================= BLE Operations ================= */
async function connectToBle(){
if(!navigator.bluetooth){ logMessage('Web Bluetooth not supported.'); return; }
try{
bleDevice = await navigator.bluetooth.requestDevice({
filters:[{ namePrefix: el.inDeviceName.value || BLE_SERVER_NAME }],
optionalServices:[BLE_SERVICE_UUID]
});
const server = await bleDevice.gatt.connect();
const service = await server.getPrimaryService(BLE_SERVICE_UUID);
bleCharacteristic1 = await service.getCharacteristic(BLE_CHARACTERISTIC1_UUID);
bleCharacteristic2 = await service.getCharacteristic(BLE_CHARACTERISTIC2_UUID);
await bleCharacteristic2.startNotifications();
let prevProgressFound = false;
bleCharacteristic2.addEventListener('characteristicvaluechanged', e => {
try{
//const view = e.target.value; // DataView
//const bytes = new Uint8Array(view.buffer, view.byteOffset, view.byteLength);
// Debug info
//let debugInfo = `Received ${bytes.length} bytes: `;
//for (let i = 0; i < bytes.length; i++) {
// debugInfo += bytes[i].toString(16).padStart(2, '0') + ' ';
// }
//console.log(debugInfo);
// Try to decode as text
let txt = '';
try {
//txt = new TextDecoder().decode(bytes);
txt = new TextDecoder().decode(e.target.value);
// Remove null terminators and trim
const nullIdx = txt.indexOf('\0');
if (nullIdx !== -1) txt = txt.slice(0, nullIdx);
txt = txt.trim();
} catch (decodeErr) {
console.error('Text decode error:', decodeErr);
// Fallback to showing hex if text decode fails
txt = '[Binary data]';
}
// Show both raw and processed data in console
//console.log('--> Raw bytes:', bytes);
//console.log('--> As text:', txt);
//logMessage(`--> (${bytes.length} bytes) ${txt}`);
// progress messages handling variable captured from outer scope
if(prevProgressFound && txt.includes('progress')){
logMessage(`${txt}`, true); // overwrite last line for progress updates
}
else{
logMessage(`${txt}`);
prevProgressFound = false; // reset if current message isn't progress
}
if(txt.includes('progress')){
prevProgressFound = true;
}
} catch(err) {
console.error('Processing error', err);
logMessage('--> Error processing message: ' + err.message);
}
});
bleConnected=true;
bleDevice.addEventListener('gattserverdisconnected', onDisconnect);
const connectedName = bleDevice.name || el.inDeviceName.value || 'Device';
logMessage('Connected to ' + connectedName);
const nameLabel = document.getElementById('ble-device-name');
if(nameLabel){ nameLabel.textContent = 'Device Name: ' + connectedName; }
await readPacket();
updateUI();
}catch(err){
logMessage( err.message.includes('cancel') ? 'Connection cancelled.' : ('Connection failed: '+err.message) );
}
}
function onDisconnect() {
bleConnected = false;
versionCheckPerformed = false; // Reset version check flag on disconnect
el.lblDeviceName.textContent = 'Device Name';
updateUI();
logMessage('BLE disconnected');
}
const delay = ms => new Promise(r => setTimeout(r, ms));
async function sendPacket(msg) {
if(!bleCharacteristic1) return;
const enc = new TextEncoder();
for(let attempt = 0; attempt < 3; attempt++) {
try { await bleCharacteristic1.writeValueWithResponse(enc.encode(msg)); return true; }
catch(e) { if(attempt === 2) logMessage('Send failed: '+e.message); else await delay(1000); }
}
return false;
}
async function readPacket() {
if(!bleCharacteristic1) return false;
for(let attempt = 0; attempt < 3; attempt++) {
try {
const val = await bleCharacteristic1.readValue();
const data = new Uint8Array(val.buffer);
if(parsePacket(data)) return true;
else { logMessage('Packet parse failed (len=' + data.length + ')'); return false; }
} catch(e) { if(attempt === 2) logMessage('Read failed: '+e.message); else await delay(1000); }
}
return false;
}
/* ================= Actions ================= */
async function wifiConnect() {
const ssid = el.inSsid.value.trim();
const pw = el.inPass.value;
if(!ssid || !pw) { alert('Enter SSID & password'); return; }
logMessage('Sending WiFi credentials...');
el.btnWifiConnect.disabled = true;
await sendPacket(`wifi-connect {"ssid":"${ssid}","pass":"${pw}"} `);
const start = Date.now();
while(Date.now() - start < 15000) {
await delay(1000);
await readPacket();
updateUI();
if(state.wifiStatus === WIFI_STAT.CONNECTED) { logMessage(`WiFi Connected: ${ipToString(state.wifiIP)}`); break; }
if(state.wifiStatus === WIFI_STAT.BAD_CREDS) { logMessage('WiFi Error: Bad Credentials'); break; }
if(state.wifiStatus === WIFI_STAT.NO_AP) { logMessage('WiFi Error: AP Not Found'); break; }
}
if(state.wifiStatus !== WIFI_STAT.CONNECTED) logMessage(`WiFi connect attempt finished with status: ${WIFI_STAT_TEXT[state.wifiStatus]}`);
if(bleConnected && state.wifiStatus !== WIFI_STAT.CONNECTED) { el.btnWifiConnect.disabled = false; }
}
async function checkStatus() { if(await readPacket()) updateUI(); }
async function checkVersion() {
el.btnCheckVersion.disabled = true;
versionCheckPerformed = false; // Reset flag at start
logMessage('Checking for new version...');
await sendPacket('version-check');
const start = Date.now();
while(Date.now() - start < 15000) {
await delay(750);
await readPacket();
if(state.newVersion[0]) {
versionCheckPerformed = true; // Set flag when version is received
if(compareVersions(state.newVersion, state.currVersion) > 0) {
logMessage('New version available: ' + state.newVersion.join('.'));
} else {
logMessage('Newer version not available.');
}
break;
}
}
if(!state.newVersion[0]) {
logMessage('No new version info received');
versionCheckPerformed = false; // Ensure flag is false if no version received
}
updateUI();
}
async function startUpgrade() {
if(el.btnStartUpgrade.disabled) return;
// Get selected update mode
const selectedMode = document.querySelector('input[name="updateMode"]:checked')?.value || 'both';
// Determine packet to send based on selection
let packet;
let modeText;
switch(selectedMode) {
case 'files':
packet = 'upgrade-start-files-only';
modeText = 'files only';
break;
case 'firmware':
packet = 'upgrade-start-firmware-only';
modeText = 'firmware only';
break;
case 'both':
default:
packet = 'upgrade-start';
modeText = 'both files and firmware';
break;
}
logMessage(`Starting upgrade (${modeText})...`);
el.btnStartUpgrade.disabled = true;
await sendPacket(packet);
}
/* ================= Initialization ================= */
function togglePasswordVisibility() {
el.inPass.type = el.chkShowPass.checked ? 'text' : 'password';
}
function init() {
cacheDom();
el.inDeviceName.value = BLE_SERVER_NAME;
// Tab switching
document.querySelectorAll('.tab-btn').forEach(btn => {
btn.addEventListener('click', () => {
document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active'));
document.querySelectorAll('.tab-panel').forEach(p => p.classList.remove('active'));
btn.classList.add('active');
const id = btn.getAttribute('data-tab');
const panel = document.getElementById(id);
if(panel) panel.classList.add('active');
});
});
updateUI();
logMessage('Press Connect -> Check Version -> Start Update');
}
window.connectToBle = connectToBle;
window.checkStatus = checkStatus;
window.checkVersion = checkVersion;
window.startUpgrade = startUpgrade;
window.wifiConnect = wifiConnect;
window.togglePasswordVisibility = togglePasswordVisibility;
window.addEventListener('DOMContentLoaded', init);
})();
</script>
</body>
</html>
```