minor fixes

This commit is contained in:
admin 2025-08-20 12:07:34 -07:00
parent d5dcf6c0fe
commit 1d661e41ad
23 changed files with 2945 additions and 166 deletions

View File

@ -162,18 +162,26 @@
<label id="status-new-version">New Version: ...</label>
</div>
<div class="btn-container">
<label id="ble-device-name">Device Name:</label>
</div>
<div class="btn-container">
<input type="text" id="input-DeviceName" placeholder="..." style="width: 100%; max-width: 220px;" required>
</div>
<!-- Buttons -->
<div class="btn-container">
<button id="bleConnectBtn">Connect</button>
<button id="checkStatusBtn" disabled>Check Status</button>
<button id="bleConnectBtn" onclick="connectToBle()">Connect</button>
<button id="checkStatusBtn" onclick="checkStatus()" disabled>Check Status</button>
</div>
<!-- Log Area -->
<textarea id="logArea" readonly></textarea>
<div class="btn-container">
<button id="checkVersionBtn" disabled>Check Version</button>
<button id="startUpgradeBtn" disabled>Start Update</button>
<button id="checkVersionBtn" onclick="checkVersion()" disabled>Check Version</button>
<button id="startUpgradeBtn" onclick="startUpgrade()" disabled>Start Update</button>
</div>
<!-- Wi-Fi Input Fields -->
@ -181,14 +189,14 @@
<input type="text" id="wifissid" name="wifissid" placeholder="Enter WiFi SSID" required>
<input type="password" id="wifipassword" name="wifipassword" placeholder="Enter WiFi Password" required>
<div style="display: flex; align-items: center; gap: 5px;">
<input type="checkbox" id="showPassword" style="width: auto;">
<input type="checkbox" id="showPassword" onclick="togglePasswordVisibility()" style="width: auto;">
<label for="showPassword">Show Password</label>
</div>
</div>
<!-- Added margin-top above this button -->
<div class="btn-container wifi">
<button id="wifiConnectBtn" disabled>Connect Wifi</button>
<button id="wifiConnectBtn" onclick="wifiConnect()" disabled>Connect Wifi</button>
</div>
<script>
@ -216,6 +224,14 @@
newVersion: [0, 0, 0]
};
// Function to automatically start the connection process when the page loads
function autoStart() {
document.getElementById('input-DeviceName').value = BLE_SERVER_NAME;
}
// Call autoStart when the page loads
window.addEventListener('DOMContentLoaded', autoStart);
function 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;
@ -255,7 +271,7 @@
}
try {
bleDevice = await navigator.bluetooth.requestDevice({
filters: [{ name: BLE_SERVER_NAME }],
filters: [{ name: document.getElementById('input-DeviceName').value }],
optionalServices: [BLE_SERVICE_UUID]
});
@ -288,9 +304,9 @@
logMessage('--> ' + decodedValue);
});
bleConnected = true;
// Auto-reconnect / state reset handler
bleDevice.addEventListener('gattserverdisconnected', handleDisconnect);
bleConnected = true;
// Auto-reconnect / state reset handler
bleDevice.addEventListener('gattserverdisconnected', handleDisconnect);
document.getElementById('bleConnectBtn').disabled = true;
document.querySelector('.status-indicator-ble').style.backgroundColor = 'green';
document.getElementById('status-ble-connection').textContent ="BLE Status: Connected";
@ -310,6 +326,61 @@
}
}
async function wifiConnect() {
const ssid = document.getElementById('wifissid').value;
const password = document.getElementById('wifipassword').value;
if (ssid && password) {
// Send credentials to the device (retain original spacing format expected by firmware)
const jsonString = ' {"ssid":"' + ssid.trim() + '","pass":"' + password + '"} ';
await sendPacket('wifi-connect' + jsonString);
await readPacket();
processUpdatePacket(updatePacket);
} else {
alert('Please enter both SSID and password.');
}
}
async function checkStatus() {
if (bleCharacteristic1) {
await readPacket();
processUpdatePacket(updatePacket);
} else {
logMessage('BLE device not connected.');
}
}
async function checkVersion() {
await sendPacket('version-check');
// loop and monitor the the updatePacket.newVersion
await new Promise(resolve => setTimeout(resolve, 2000));
let success = false;
for (let i = 0; i < 20; i++) {
await readPacket();
if (updatePacket.newVersion[0] > 0) {
success = true;
break;
}
await new Promise(resolve => setTimeout(resolve, 1000));
}
if(success) {
processUpdatePacket(updatePacket);
logMessage("New Version: Available");
} else {
logMessage("New Version: Not Available");
}
}
async function startUpgrade() {
try {
await sendPacket('upgrade-start');
logMessage("Upgrade Starting... Please wait.");
} catch (error) {
logMessage(`Error starting upgrade: ${error.message}`);
}
}
function handleDisconnect(){
bleConnected = false;
document.querySelector('.status-indicator-ble').style.backgroundColor = 'gray';
@ -426,10 +497,11 @@
document.getElementById('status-new-version').textContent = 'New Version: ' + packet.newVersion.join('.');
document.getElementById('checkVersionBtn').disabled = true;
if(packet.wifiOnline && compareVersions(packet.newVersion, packet.currVersion) > 0) {
logMessage("Latest Update is: " + packet.newVersion.join('.'));
if(packet.wifiOnline && compareVersions(packet.newVersion, packet.currVersion) > 0) {
//enable start upgrade button
logMessage("New Version Available:");
logMessage("New Version: Available");
document.getElementById('startUpgradeBtn').disabled = false;
} else {
//disable start upgrade button
@ -461,67 +533,11 @@
processUpdatePacket(updatePacket);
}
document.getElementById('showPassword').addEventListener('change', function() {
// Event listeners for buttons
function togglePasswordVisibility() {
const passwordInput = document.getElementById('wifipassword');
passwordInput.type = this.checked ? 'text' : 'password';
});
// Event listeners for buttons
document.getElementById('bleConnectBtn').addEventListener('click', connectToBle);
document.getElementById('checkStatusBtn').addEventListener('click', async () => {
if (bleCharacteristic1) {
await readPacket();
processUpdatePacket(updatePacket);
} else {
logMessage('BLE device not connected.');
}
});
document.getElementById('checkVersionBtn').addEventListener('click', async () => {
await sendPacket('version-check');
// loop and monitor the the updatePacket.newVersion
await new Promise(resolve => setTimeout(resolve, 2000));
let success = false;
for (let i = 0; i < 20; i++) {
await readPacket();
if (updatePacket.newVersion[0] > 0) {
success = true;
break;
}
await new Promise(resolve => setTimeout(resolve, 1000));
}
if(success) {
processUpdatePacket(updatePacket);
logMessage("New Version: Available");
} else {
logMessage("New Version: Not Available");
}
});
document.getElementById('wifiConnectBtn').addEventListener('click', async () => {
const ssid = document.getElementById('wifissid').value;
const password = document.getElementById('wifipassword').value;
if (ssid && password) {
// Send credentials to the device (retain original spacing format expected by firmware)
const jsonString = ' {"ssid":"' + ssid.trim() + '","pass":"' + password + '"} ';
await sendPacket('wifi-connect' + jsonString);
await readPacket();
processUpdatePacket(updatePacket);
} else {
alert('Please enter both SSID and password.');
}
});
document.getElementById('startUpgradeBtn').addEventListener('click', async () => {
try {
await sendPacket('upgrade-start');
logMessage("Upgrade Starting... Please wait.");
} catch (error) {
logMessage(`Error starting upgrade: ${error.message}`);
}
});
}
// Initial call to process the update packet with defaults
processUpdatePacket(updatePacket);

View File

@ -0,0 +1,476 @@
<!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>
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 20px;
background-color: #f4f4f4;
text-align: center;
}
h1 {
font-size: 22px;
margin-bottom: 5px;
}
.status-container {
display: flex;
align-items: center;
justify-content: left;
margin-bottom: 4px;
}
.status-indicator-ble {
width: 20px;
height: 20px;
border-radius: 50%;
background-color: gray;
margin-right: 10px;
}
.status-indicator-wifi {
width: 20px;
height: 20px;
border-radius: 50%;
background-color: gray;
margin-right: 10px;
}
.status-indicator-internet {
width: 20px;
height: 20px;
border-radius: 50%;
background-color: gray;
margin-right: 10px;
}
.btn-container {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 10px;
margin-bottom: 10px;
}
/* Adds space above the WiFi Connect button */
.btn-container.wifi {
margin-top: 20px;
}
button {
flex: 1;
max-width: 130px;
padding: 10px;
font-size: 16px;
border: none;
border-radius: 5px;
cursor: pointer;
background-color: #007bff;
color: white;
transition: background 0.3s ease;
}
button:disabled {
background-color: #ccc;
}
button:hover:not(:disabled) {
background-color: #0056b3;
}
textarea {
width: 100%;
height: 300px;
font-size: 14px;
padding: 10px;
border-radius: 5px;
border: 1px solid #ccc;
resize: none;
}
.input-container {
display: flex;
flex-direction: column;
align-items: center;
gap: 10px;
margin-top: 15px;
}
input {
width: 90%;
max-width: 300px;
padding: 10px;
font-size: 16px;
border: 1px solid #ccc;
border-radius: 5px;
text-align: center;
}
input::placeholder {
text-align: center;
}
@media (max-width: 480px) {
body {
padding: 15px;
}
h1 {
font-size: 20px;
}
button {
font-size: 14px;
padding: 8px;
}
input, textarea {
font-size: 14px;
}
}
</style>
</head>
<body>
<h1>ATA Firmware Update</h1>
<!-- Status Indicators -->
<div class="status-container">
<span class="status-indicator-ble"></span>
<label id="status-ble-connection">BLE Status: ...</label>
</div>
<div class="status-container">
<span class="status-indicator-wifi"></span>
<label id="status-wifi-client">Wifi Client: ...</label>
</div>
<div class="status-container">
<span class="status-indicator-internet"></span>
<label id="status-internet">Internet: ...</label>
</div>
<div class="status-container">
<label id="status-current-version">Curr Version: ...</label>
</div>
<div class="status-container">
<label id="status-new-version">New Version: ...</label>
</div>
<div class="btn-container">
<label id="ble-device-name">Device Name:</label>
</div>
<div class="btn-container">
<input type="text" id="input-DeviceName" placeholder="..." style="width: 100%; max-width: 220px;" required>
</div>
<!-- Buttons -->
<div class="btn-container">
<button id="bleConnectBtn" onclick="connectToBle()">Connect</button>
<button id="checkStatusBtn" onclick="checkStatus()" disabled>Check Status</button>
</div>
<!-- Log Area -->
<textarea id="logArea" readonly></textarea>
<div class="btn-container">
<button id="checkVersionBtn" onclick="checkVersion()" disabled>Check Version</button>
<button id="startUpgradeBtn" onclick="startUpgrade()" disabled>Start Update</button>
</div>
<!-- Wi-Fi Input Fields -->
<div class="input-container">
<input type="text" id="wifissid" name="wifissid" placeholder="Enter WiFi SSID" required>
<input type="password" id="wifipassword" name="wifipassword" placeholder="Enter WiFi Password" required>
<div style="display: flex; align-items: center; gap: 5px;">
<input type="checkbox" id="showPassword" onclick="togglePasswordVisibility()" style="width: auto;">
<label for="showPassword">Show Password</label>
</div>
</div>
<!-- Added margin-top above this button -->
<div class="btn-container wifi">
<button id="wifiConnectBtn" onclick="wifiConnect()" disabled>Connect Wifi</button>
</div>
<script>
(function(){
'use strict';
/* ================= Constants & Packet Layout ================= */
const BLE_SERVER_NAME = "ATALIGHTS"; // Keep hardcoded per instruction (ignore external JSON)
const BLE_SERVICE_UUID = "abcdef01-2345-6789-1234-56789abcdef0";
const BLE_CHARACTERISTIC1_UUID = "abcdef01-2345-6789-1234-56789abcdef1"; // Control / status
const BLE_CHARACTERISTIC2_UUID = "abcdef02-2345-6789-1234-56789abcdef1"; // Logs / events
// Packet layout (mirrors firmware struct updateStatus)
// byte 0 : wifiStatus (enum)
// byte 1 : wifiOnline (bool)
// bytes2-5: wifiIP
// bytes6-8: currVersion (major,minor,patch)
// bytes9-11: newVersion (major,minor,patch)
// bytes12-31: wifiSSID (20 bytes, null padded)
const PACKET_LEN = 32;
const OFF_WIFI_STATUS = 0;
const OFF_WIFI_ONLINE = 1;
const OFF_WIFI_IP = 2;
const OFF_CURR_VER = 6;
const OFF_NEW_VER = 9;
const OFF_WIFI_SSID = 12;
const WIFI_STAT = { DISCONNECTED:0, BAD_CREDS:1, NO_AP:2, CONNECTED:3 };
const WIFI_STAT_TEXT = ["Disconnected","Bad Creds","No AP","Connected"];
const MAX_LOG_LINES = 400;
/* ================= State ================= */
let bleDevice=null, bleCharacteristic1=null, bleCharacteristic2=null;
let bleConnected=false;
const state = {
wifiStatus: WIFI_STAT.DISCONNECTED,
wifiOnline:false,
wifiIP:[0,0,0,0],
currVersion:[0,0,0],
newVersion:[0,0,0],
wifiSSID:""
};
/* ================= Cached DOM ================= */
const el = {};
function cacheDom(){
el.bleIndicator = document.querySelector('.status-indicator-ble');
el.wifiIndicator = document.querySelector('.status-indicator-wifi');
el.internetIndicator = document.querySelector('.status-indicator-internet');
el.lblBle = document.getElementById('status-ble-connection');
el.lblWifi = document.getElementById('status-wifi-client');
el.lblInternet = document.getElementById('status-internet');
el.lblCurrVer = document.getElementById('status-current-version');
el.lblNewVer = document.getElementById('status-new-version');
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 ================= */
function logMessage(msg){
const lines = el.logArea.value.trim().length ? el.logArea.value.split(/\n/) : [];
lines.push(msg);
if(lines.length > MAX_LOG_LINES){
lines.splice(0, lines.length - MAX_LOG_LINES);
}
el.logArea.value = lines.join('\n') + '\n';
el.logArea.scrollTop = el.logArea.scrollHeight;
}
function 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;
}
function colorIndicator(elm, color){ if(elm) elm.style.backgroundColor = color; }
function ipToString(ip){ return ip.join('.'); }
/* ================= Packet Handling ================= */
function parsePacket(data){
if(data.length !== PACKET_LEN) return false;
state.wifiStatus = data[OFF_WIFI_STATUS];
if(state.wifiStatus > WIFI_STAT.CONNECTED) state.wifiStatus = WIFI_STAT.DISCONNECTED; // clamp
state.wifiOnline = !!data[OFF_WIFI_ONLINE];
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));
// Extract SSID (stop at first 0)
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');
colorIndicator(el.bleIndicator, bleConnected ? 'green' : 'gray');
// WiFi client
const statText = WIFI_STAT_TEXT[state.wifiStatus] || 'Unknown';
if(state.wifiStatus === WIFI_STAT.CONNECTED){
const ssidPart = state.wifiSSID ? ' SSID: '+state.wifiSSID : '';
el.lblWifi.textContent = 'Wifi Client: ' + statText + (state.wifiIP[0] ? ' ('+ipToString(state.wifiIP)+')':'' ) + ssidPart;
colorIndicator(el.wifiIndicator, 'green');
} else if(state.wifiStatus === WIFI_STAT.BAD_CREDS){
el.lblWifi.textContent = 'Wifi Client: Bad Credentials';
colorIndicator(el.wifiIndicator, 'orange');
} else if(state.wifiStatus === WIFI_STAT.NO_AP){
el.lblWifi.textContent = 'Wifi Client: AP Not Found';
colorIndicator(el.wifiIndicator, 'orange');
} else {
el.lblWifi.textContent = 'Wifi Client: ' + statText;
colorIndicator(el.wifiIndicator, 'gray');
}
// Internet
el.lblInternet.textContent = state.wifiOnline ? 'Online' : 'Offline';
colorIndicator(el.internetIndicator, state.wifiOnline ? 'green' : 'gray');
// Versions
el.lblCurrVer.textContent = state.currVersion[0] ? 'Curr Version: ' + state.currVersion.join('.') : 'Curr Version: ...';
el.lblNewVer.textContent = state.newVersion[0] ? 'New Version: ' + state.newVersion.join('.') : 'New Version: ...';
// Buttons
el.btnCheckStatus.disabled = !bleConnected;
el.btnWifiConnect.disabled = !bleConnected;
el.btnCheckVersion.disabled = !(bleConnected && state.wifiOnline);
const newerAvail = 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:[{ name: 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();
bleCharacteristic2.addEventListener('characteristicvaluechanged', e => {
try{
const txt = new TextDecoder().decode(e.target.value);
logMessage('--> ' + txt.trim());
}catch(_){ /* ignore */ }
});
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; updateUI(); logMessage('BLE disconnected');
}
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; // prevent multiple submissions while polling
await sendPacket('wifi-connect {"ssid":"'+ssid+'","pass":"'+pw+'"} ');
// Poll for status for up to 15s
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) return; // keep disabled if BLE disconnected during process
// Re-enable for retry unless connected
if(state.wifiStatus !== WIFI_STAT.CONNECTED){ el.btnWifiConnect.disabled = false; }
}
async function checkStatus(){ if(await readPacket()) updateUI(); }
async function checkVersion(){
el.btnCheckVersion.disabled = true;
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]){ logMessage('Latest version: '+state.newVersion.join('.')); break; }
}
if(!state.newVersion[0]) logMessage('No new version info received');
updateUI();
}
async function startUpgrade(){
if(el.btnStartUpgrade.disabled) return;
logMessage('Starting upgrade...');
el.btnStartUpgrade.disabled = true;
await sendPacket('upgrade-start');
// Progress will arrive via characteristic2 logs
}
/* ================= Helpers ================= */
const delay = ms => new Promise(r=>setTimeout(r,ms));
function togglePasswordVisibility(){ el.inPass.type = el.chkShowPass.checked ? 'text' : 'password'; }
function init(){
cacheDom();
el.inDeviceName.value = BLE_SERVER_NAME;
el.chkShowPass.addEventListener('change', togglePasswordVisibility);
// Inline onclicks already wired; ensure functions are in scope
updateUI();
logMessage('Ready. Enter device name or use default and press Connect.');
}
// Expose functions for inline handlers
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>

View File

@ -0,0 +1,202 @@
import os
import shutil
import hashlib
import json
from typing import Iterable, List, Optional
import datetime
def copy_folder_to_destination(src_path: str, dest_path: str, skip_dirs: Optional[Iterable[str]] = None, skip_files: Optional[Iterable[str]] = None) -> None:
# Ensure the destination directory exists
os.makedirs(os.path.dirname(dest_path), exist_ok=True)
# Remove existing folder if present
if os.path.exists(dest_path):
shutil.rmtree(dest_path)
# Create destination directory
os.makedirs(dest_path)
# Walk through source directory
for root, dirs, files in os.walk(src_path):
# Remove directories to skip from dirs list
if skip_dirs:
dirs[:] = [d for d in dirs if d not in skip_dirs]
# Calculate relative path
rel_path = os.path.relpath(root, src_path)
dest_dir = os.path.join(dest_path, rel_path)
# Create corresponding destination directory
os.makedirs(dest_dir, exist_ok=True)
# Copy files that aren't in skip_files
for file in files:
if skip_files and file in skip_files:
continue
src_file = os.path.join(root, file)
dest_file = os.path.join(dest_dir, file)
shutil.copy2(src_file, dest_file)
def calculate_md5(file_path: str) -> str:
hash_md5 = hashlib.md5()
with open(file_path, "rb") as f:
for chunk in iter(lambda: f.read(4096), b""):
hash_md5.update(chunk)
return hash_md5.hexdigest()
def get_file_size(file_path: str) -> int:
return os.path.getsize(file_path)
def update_json_file(json_array: List[dict], folder_path: str) -> None:
# Create new data array for files
file_array = []
# Walk through the copied folder and collect file details
for root, _, files in os.walk(folder_path):
for file in files:
file_path = os.path.join(root, file)
relative_path = os.path.relpath(file_path, folder_path)
# Replace backslashes with forward slashes
relative_path = relative_path.replace('\\', '/')
# Build remote/local using forward slashes only
file_entry = {
"remote": f"data/{relative_path}",
"local": f"/{relative_path}",
"md5": calculate_md5(file_path),
"size": get_file_size(file_path)
}
file_array.append(file_entry)
# Replace the contents of the input json_array with new data
json_array.clear()
json_array.extend(file_array)
def perform_data_copy(src_path: str, dest_path: str, skip_dirs: Optional[Iterable[str]] = None, skip_files: Optional[Iterable[str]] = None) -> bool:
# Check if the source folder exists
if not os.path.isdir(src_path):
print(f"Source folder does not exist: {src_path}")
return False
copy_folder_to_destination(src_path, dest_path, skip_dirs, skip_files)
print("Data folder copied successfully.")
return True
def main():
here_path = os.path.dirname(os.path.abspath(__file__))
project_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# Path of the folder to copy (you can modify this)
src_folder_name = "data"
src_path = os.path.join(project_path, src_folder_name)
#print(f"source path: {src_path}")
# Path of the destination folder
dest_folder_name = "firmware_update\\latest\\data"
dest_path = os.path.join(project_path, dest_folder_name)
#print(f"destination path: {dest_path}")
# Path of the firmware binary file
bin_name = ".pio\\build\\esp32s3dev\\firmware.bin"
# Skip these directories
skip_dirs = ["boards", "booths"]
# Skip these files
skip_files = ["wifi.json", "system.json", "luma-stiks.json", ]
# Allow non-interactive mode via environment variable (set NON_INTERACTIVE=y to auto 'y')
non_interactive = os.environ.get("NON_INTERACTIVE", "n").lower() in ("1", "y", "yes", "true")
file_choice = os.environ.get("UPDATE_FILES") if non_interactive else input("Do you want to update the files? (y/n): ")
if file_choice is None:
file_choice = "n"
file_choice = file_choice.strip().lower()
# *********************** Copy Data Files ***********************
copied = False
if file_choice == "y":
copied = perform_data_copy(src_path, dest_path, skip_dirs, skip_files)
# *********************** Copy Binary file ***********************
fw_choice = os.environ.get("UPDATE_FIRMWARE") if non_interactive else input("Do you want to update the firmware? (y/n): ")
if fw_choice is None:
fw_choice = "n"
fw_choice = fw_choice.strip().lower()
if fw_choice == "y":
# Copy firmware.bin to the destination
bin_path = os.path.join(project_path, bin_name)
if not os.path.isfile(bin_path):
print(f"Firmware binary not found: {bin_path}")
else:
shutil.copy(bin_path, here_path)
print("firmware.bin copied successfully.")
# *********************** Process update.json ***********************
# Update the JSON file
#json_path = os.path.join(here_path, "update.json")
json_path = os.path.join(here_path, "latest", "update.json")
print(f"json path: {json_path}")
# Read existing JSON
if not os.path.isfile(json_path):
print(f"update.json not found at {json_path}; creating a new one.")
json_doc = {"files": [], "firmware": {"md5": "", "size": 0}}
else:
with open(json_path, "r") as f:
try:
json_doc = json.load(f)
except json.JSONDecodeError:
print("Invalid JSON file! Aborting.")
return
# Ensure required keys exist
json_doc.setdefault("files", [])
json_doc.setdefault("firmware", {"md5": "", "size": 0})
# process the files array
#if update_files.lower() == "y":
if file_choice == "y":
if not copied:
print("Skipping file list update because data copy failed.")
else:
json_files_array = json_doc["files"]
update_json_file(json_files_array, dest_path)
#print(f"Folder {os.path.basename(src_path)} processed successfully.")
# *********************** Process firmwware.bin in update.json ***********************
# process the firmware
if fw_choice == "y":
json_firmware = json_doc["firmware"]
firmware_path = os.path.join(here_path, "firmware.bin")
if os.path.isfile(firmware_path):
json_firmware["md5"] = calculate_md5(firmware_path)
json_firmware["size"] = get_file_size(firmware_path)
else:
print("Firmware file missing; firmware section not updated.")
# *********************** Update release date, time in update.json ***********************
now = datetime.datetime.now()
json_doc["release_date"] = now.strftime("%Y-%m-%d")
json_doc["release_time"] = now.strftime("%H:%M:%S")
# Write updated JSON
with open(json_path, "w") as f:
json.dump(json_doc, f, indent=4)
print("update.json updated successfully.")
if __name__ == "__main__":
main()

View File

@ -0,0 +1,337 @@
#!/usr/bin/env python3
"""Upload firmware, manifest, and data assets to a MinIO (S3-compatible) bucket.
Features preserved from original GCS script:
- Optional backup (copies existing objects under destination prefix to timestamped folder under backups/)
- Upload firmware.bin, update.json, and recursively mirror a data directory
- Cache-Control set to disable caching on clients
Switches from google.cloud.storage to boto3 (S3 API) for MinIO compatibility.
"""
import os
import sys
import datetime
import json
from pathlib import Path
try:
import boto3
from botocore.exceptions import ClientError
from botocore.config import Config
except ImportError:
print("ERROR: boto3 is required. Install with: pip install boto3")
sys.exit(1)
# =============================================================================
# CONFIGURATION CONSTANTS (edit as needed or supply via environment variables)
# =============================================================================
CREATE_BACKUP = False
UPLOAD_FIRMWARE = True
UPLOAD_MANIFEST = True
UPLOAD_DATA = True
# Bucket / endpoint configuration
BUCKET_NAME = os.getenv('MINIO_BUCKET', 'boothifier')
DESTINATION_DIR = os.getenv('MINIO_DEST_PREFIX', 'latest') # prefix inside bucket
BACKUPS_DIR = os.getenv('MINIO_BACKUPS_PREFIX', 'backups')
LOCAL_ROOT_PATH = Path(__file__).parent.resolve()
# Optional service account style JSON key (generated by MinIO Console). Expected fields:
# {"url":"https://minio.example.com/api/v1/service-account-credentials","accessKey":"...","secretKey":"...","api":"s3v4","path":"auto"}
MINIO_KEY_FILE = LOCAL_ROOT_PATH / 'minio-boothifier-key.json'
# Defaults before loading file / env
_json_access = None
_json_secret = None
_json_url = None
def _load_json_key():
global _json_access, _json_secret, _json_url
try:
if MINIO_KEY_FILE.is_file():
with open(MINIO_KEY_FILE, 'r', encoding='utf-8') as fh:
data = json.load(fh)
_json_access = data.get('accessKey') or None
_json_secret = data.get('secretKey') or None
_json_url = data.get('url') or None
except Exception as e:
print(f"WARN: Failed to load MinIO key file '{MINIO_KEY_FILE.name}': {e}")
_load_json_key()
def _derive_endpoint(url_value: str) -> str:
if not url_value:
return 'https://s3-minio.boothwizard.com'
# Remove known API suffix if present (/api/...)
# e.g. https://s3-minio.boothwizard.com/api/v1/service-account-credentials -> https://s3-minio.boothwizard.com
parts = url_value.split('/api/')
return parts[0] if parts else url_value
# MinIO credentials with precedence: ENV > JSON file > fallback
MINIO_ENDPOINT = os.getenv('MINIO_ENDPOINT') or _derive_endpoint(_json_url)
MINIO_ACCESS_KEY = os.getenv('MINIO_ACCESS_KEY') or _json_access or 'CHANGE_ME_ACCESS'
MINIO_SECRET_KEY = os.getenv('MINIO_SECRET_KEY') or _json_secret or 'CHANGE_ME_SECRET'
MINIO_REGION = os.getenv('MINIO_REGION', 'us-east-1') # MinIO ignores but boto3 wants some value
# Addressing / SSL options
MINIO_ADDRESSING = os.getenv('MINIO_ADDRESSING_STYLE', 'path').lower() # 'path' or 'virtual'
MINIO_VERIFY_SSL = os.getenv('MINIO_TLS_VERIFY', '1') not in ('0','false','no')
MINIO_DEBUG = os.getenv('MINIO_DEBUG', '0') in ('1','true','yes')
MINIO_ALLOW_VARIANTS = os.getenv('MINIO_ALLOW_ENDPOINT_VARIANTS', '0') in ('1','true','yes') # normally false with nginx redirect
LOCAL_FIRMWARE_PATH = str(LOCAL_ROOT_PATH / 'latest' / 'firmware.bin')
LOCAL_MANIFEST_PATH = str(LOCAL_ROOT_PATH / 'latest' / 'update.json')
LOCAL_DATA_DIRECTORY = str(LOCAL_ROOT_PATH / 'latest' / 'data')
# =============================================================================
# HELPERS
# =============================================================================
def s3_client():
"""Create an S3 client pointed at MinIO endpoint, forcing path-style unless overridden, with short timeouts."""
addressing = 'path' if MINIO_ADDRESSING not in ('virtual','auto') else 'virtual'
cfg = Config(
s3={'addressing_style': addressing},
signature_version='s3v4',
connect_timeout=3,
read_timeout=5,
retries={'max_attempts': 2}
)
if MINIO_DEBUG:
masked_key = (MINIO_ACCESS_KEY[:3] + '...' + MINIO_ACCESS_KEY[-3:]) if MINIO_ACCESS_KEY else 'None'
print(f"[DEBUG] Creating client: endpoint={MINIO_ENDPOINT} addressing={addressing} verifySSL={MINIO_VERIFY_SSL} region={MINIO_REGION} accessKey={masked_key}")
return boto3.client(
's3',
endpoint_url=MINIO_ENDPOINT,
aws_access_key_id=MINIO_ACCESS_KEY,
aws_secret_access_key=MINIO_SECRET_KEY,
region_name=MINIO_REGION,
verify=MINIO_VERIFY_SSL,
config=cfg,
)
def _endpoint_variants(base: str):
"""Return endpoint variants only if explicitly allowed; otherwise just the base (nginx handles forwarding)."""
if not MINIO_ALLOW_VARIANTS:
return [base]
# Fallback to previous expanded logic if variants are enabled
try:
variants = []
if not base:
return variants
base = base.rstrip('/')
proto_sep = '://'
if proto_sep in base:
scheme, rest = base.split(proto_sep,1)
else:
scheme, rest = 'https', base
host_port = rest
if ':' in host_port:
host, port = host_port.split(':',1)
else:
host, port = host_port, ''
variants.append(f"{scheme}://{host_port}")
common_ports = ['9000','443','80']
for p in common_ports:
if port != p:
variants.append(f"{scheme}://{host}:{p}")
alt_scheme = 'http' if scheme == 'https' else 'https'
variants.append(f"{alt_scheme}://{host_port}")
for p in common_ports:
if port != p:
variants.append(f"{alt_scheme}://{host}:{p}")
seen = set()
uniq = []
for v in variants:
if v not in seen:
uniq.append(v)
seen.add(v)
return uniq
except Exception:
return [base]
def create_validated_client():
"""Validate (or create) client using only provided endpoint unless variants enabled."""
global MINIO_ENDPOINT
primary = MINIO_ENDPOINT
variants = _endpoint_variants(primary) or [primary]
errors = []
probe_bucket = BUCKET_NAME # we will head the target bucket directly
for candidate in variants:
saved = MINIO_ENDPOINT
MINIO_ENDPOINT = candidate
if MINIO_DEBUG:
print(f"[DEBUG] Probing endpoint candidate: {candidate}")
try:
c = s3_client()
try:
c.head_bucket(Bucket=probe_bucket)
if MINIO_DEBUG:
print(f"[DEBUG] head_bucket succeeded on {candidate} for '{probe_bucket}'.")
return c
except ClientError as e:
msg = str(e)
# Acceptable if bucket not found (we can create later)
if any(code in msg for code in ('404', 'NoSuchBucket', 'NotFound')):
if MINIO_DEBUG:
print(f"[DEBUG] Bucket not found on {candidate} (expected if first deploy). Using this endpoint.")
return c
if 'API Requests must be made to API port' in msg:
errors.append(f"{candidate}: wrong port (console endpoint)")
else:
errors.append(f"{candidate}: {msg}")
MINIO_ENDPOINT = saved
except Exception as ex:
errors.append(f"{candidate}: {ex}")
MINIO_ENDPOINT = saved
continue
print("ERROR: Could not validate any endpoint candidate.")
for e in errors:
print(' - ' + e)
print("Provide correct API endpoint (e.g. https://host:9000) via MINIO_ENDPOINT env var.")
sys.exit(3)
def list_objects(client, prefix: str):
"""Generator yielding object keys under a prefix (non-recursive listing with pagination)."""
kwargs = {'Bucket': BUCKET_NAME, 'Prefix': prefix}
while True:
resp = client.list_objects_v2(**kwargs)
for obj in resp.get('Contents', []):
yield obj['Key']
if not resp.get('IsTruncated'):
break
kwargs['ContinuationToken'] = resp['NextContinuationToken']
def normalize_prefix(p: str) -> str:
p = p.strip('/')
return p
def join_key(*parts: str) -> str:
parts_clean = [p.strip('/') for p in parts if p is not None and p != '']
return '/'.join(parts_clean)
def backup_existing_files(client, destination_prefix: str, backups_prefix: str, backup_folder: str):
if not destination_prefix:
prefix = ''
else:
prefix = destination_prefix + '/'
print(f"Scanning existing objects under '{prefix}' for backup...")
for key in list_objects(client, prefix):
if backups_prefix and key.startswith(backups_prefix + '/'): # Skip prior backups
continue
# relative path within destination
relative = key[len(prefix):] if prefix and key.startswith(prefix) else key
backup_key = join_key(backups_prefix, backup_folder, relative)
print(f"Backup copy: {key} -> {backup_key}")
client.copy_object(
Bucket=BUCKET_NAME,
CopySource={'Bucket': BUCKET_NAME, 'Key': key},
Key=backup_key,
MetadataDirective='COPY'
)
def upload_file(client, local_path: str, key: str, cache_control: str = 'private, max-age=0, no-transform'):
if not os.path.isfile(local_path):
print(f"WARN: File missing, skipping: {local_path}")
return
print(f"Upload: {local_path} -> s3://{BUCKET_NAME}/{key}")
extra_args = { 'CacheControl': cache_control }
client.upload_file(local_path, BUCKET_NAME, key, ExtraArgs=extra_args)
def upload_directory(client, local_directory: str, destination_prefix: str):
if not os.path.isdir(local_directory):
print(f"WARN: Data directory missing: {local_directory}")
return
for root, _, files in os.walk(local_directory):
for fname in files:
full = os.path.join(root, fname)
rel = os.path.relpath(full, local_directory)
key = join_key(destination_prefix, rel)
upload_file(client, full, key)
def ensure_bucket(client):
"""Ensure bucket exists; provide diagnostics if HeadBucket returns 400/other errors."""
try:
client.head_bucket(Bucket=BUCKET_NAME)
if MINIO_DEBUG:
print(f"[DEBUG] Bucket '{BUCKET_NAME}' exists.")
return
except ClientError as e:
code = e.response.get('Error', {}).get('Code')
status = e.response.get('ResponseMetadata', {}).get('HTTPStatusCode')
print(f"HeadBucket failed (code={code}, status={status}).")
# List buckets for diagnostics
try:
resp = client.list_buckets()
bucket_names = [b['Name'] for b in resp.get('Buckets', [])]
print(f"Available buckets: {bucket_names or 'None'}")
except Exception as le:
print(f"WARN: list_buckets failed: {le}")
if code in ('404', 'NoSuchBucket', 'NotFound'):
print(f"Bucket '{BUCKET_NAME}' not found. Attempting to create...")
try:
client.create_bucket(Bucket=BUCKET_NAME)
print(f"Created bucket '{BUCKET_NAME}'.")
return
except ClientError as ce:
print(f"ERROR: Cannot create bucket: {ce}")
sys.exit(2)
if status == 400:
print("HINTS: \n - Verify endpoint URL (MINIO_ENDPOINT).\n - Ensure no trailing slash in endpoint.\n - Check that TLS verify matches server cert (set MINIO_TLS_VERIFY=0 to test).\n - Confirm bucket name is correct and DNS compatible.\n - Credentials may lack permission: verify access key policies.")
# Retry once forcing path style if not already
if MINIO_ADDRESSING != 'path':
print("Retrying with path-style addressing...")
os.environ['MINIO_ADDRESSING_STYLE'] = 'path'
new_client = s3_client()
try:
new_client.head_bucket(Bucket=BUCKET_NAME)
print("Second attempt succeeded with path-style addressing.")
return
except ClientError as e2:
print(f"Second HeadBucket attempt failed: {e2}")
print(f"ERROR: head_bucket ultimately failed: {e}")
sys.exit(2)
# =============================================================================
# MAIN
# =============================================================================
def main():
dest_prefix = normalize_prefix(DESTINATION_DIR)
backups_prefix = normalize_prefix(BACKUPS_DIR) if BACKUPS_DIR else ''
client = create_validated_client()
if MINIO_DEBUG:
print("[DEBUG] Starting ensure_bucket phase...")
ensure_bucket(client)
if CREATE_BACKUP:
ts = datetime.datetime.now().strftime('%Y%m%d_%H%M%S')
backup_folder = f"backup_{ts}"
print(f"Creating backup under '{backups_prefix}/{backup_folder}' from prefix '{dest_prefix}'")
backup_existing_files(client, dest_prefix, backups_prefix, backup_folder)
# Firmware
if UPLOAD_FIRMWARE:
firmware_key = join_key(dest_prefix, 'firmware.bin') if dest_prefix else 'firmware.bin'
upload_file(client, LOCAL_FIRMWARE_PATH, firmware_key)
# Manifest
if UPLOAD_MANIFEST:
manifest_key = join_key(dest_prefix, 'update.json') if dest_prefix else 'update.json'
upload_file(client, LOCAL_MANIFEST_PATH, manifest_key)
# Data directory
if UPLOAD_DATA:
data_prefix = join_key(dest_prefix, 'data') if dest_prefix else 'data'
upload_directory(client, LOCAL_DATA_DIRECTORY, data_prefix)
print("All uploads complete.")
if __name__ == '__main__':
main()

View File

@ -400,6 +400,7 @@
if (packet.newVersion[0] > 0) {
document.getElementById('status-new-version').textContent = 'New Version: ' + packet.newVersion.join('.');
document.getElementById('checkVersionBtn').disabled = true;
if(packet.wifiOnline && packet.newVersion[0] > packet.currVersion[0] ||
(packet.newVersion[0] === packet.currVersion[0] && packet.newVersion[1] > packet.currVersion[1]) ||
@ -407,11 +408,9 @@
//enable start upgrade button
logMessage("New Version Available:");
document.getElementById('checkVersionBtn').disabled = true;
document.getElementById('startUpgradeBtn').disabled = false;
} else {
//disable start upgrade button
document.getElementById('checkVersionBtn').disabled = false;
document.getElementById('startUpgradeBtn').disabled = true;
logMessage("New Version: Not Available");
}
@ -458,10 +457,12 @@
document.getElementById('checkVersionBtn').addEventListener('click', async () => {
await sendPacket('version-check');
// loop and monitor the the updatePacket.newVersion
await new Promise(resolve => setTimeout(resolve, 2000));
success = false;
for (let i = 0; i < 20; i++) {
await readPacket();
if (updatePacket.newVersion[0] > 0) {
success = true;
break;
}
@ -492,9 +493,15 @@
});
document.getElementById('startUpgradeBtn').addEventListener('click', async () => {
await sendPacket('upgrade-start');
try {
await sendPacket('upgrade-start');
logMessage("Upgrade Starting... Please wait.");
} catch (error) {
logMessage(`Error starting upgrade: ${error.message}`);
}
});
// Initial call to process the update packet with defaults
processUpdatePacket(updatePacket);
</script>
</body>

View File

@ -0,0 +1,547 @@
<!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>
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 20px;
background-color: #f4f4f4;
text-align: center;
}
h1 {
font-size: 22px;
margin-bottom: 5px;
}
.status-container {
display: flex;
align-items: center;
justify-content: left;
margin-bottom: 4px;
}
.status-indicator-ble {
width: 20px;
height: 20px;
border-radius: 50%;
background-color: gray;
margin-right: 10px;
}
.status-indicator-wifi {
width: 20px;
height: 20px;
border-radius: 50%;
background-color: gray;
margin-right: 10px;
}
.status-indicator-internet {
width: 20px;
height: 20px;
border-radius: 50%;
background-color: gray;
margin-right: 10px;
}
.btn-container {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 10px;
margin-bottom: 10px;
}
/* Adds space above the WiFi Connect button */
.btn-container.wifi {
margin-top: 20px;
}
button {
flex: 1;
max-width: 130px;
padding: 10px;
font-size: 16px;
border: none;
border-radius: 5px;
cursor: pointer;
background-color: #007bff;
color: white;
transition: background 0.3s ease;
}
button:disabled {
background-color: #ccc;
}
button:hover:not(:disabled) {
background-color: #0056b3;
}
textarea {
width: 100%;
height: 300px;
font-size: 14px;
padding: 10px;
border-radius: 5px;
border: 1px solid #ccc;
resize: none;
}
.input-container {
display: flex;
flex-direction: column;
align-items: center;
gap: 10px;
margin-top: 15px;
}
input {
width: 90%;
max-width: 300px;
padding: 10px;
font-size: 16px;
border: 1px solid #ccc;
border-radius: 5px;
text-align: center;
}
input::placeholder {
text-align: center;
}
@media (max-width: 480px) {
body {
padding: 15px;
}
h1 {
font-size: 20px;
}
button {
font-size: 14px;
padding: 8px;
}
input, textarea {
font-size: 14px;
}
}
</style>
</head>
<body>
<h1>ATA Firmware Update</h1>
<!-- Status Indicators -->
<div class="status-container">
<span class="status-indicator-ble"></span>
<label id="status-ble-connection">BLE Status: ...</label>
</div>
<div class="status-container">
<span class="status-indicator-wifi"></span>
<label id="status-wifi-client">Wifi Client: ...</label>
</div>
<div class="status-container">
<span class="status-indicator-internet"></span>
<label id="status-internet">Internet: ...</label>
</div>
<div class="status-container">
<label id="status-current-version">Curr Version: ...</label>
</div>
<div class="status-container">
<label id="status-new-version">New Version: ...</label>
</div>
<div class="btn-container">
<label id="ble-device-name">Device Name:</label>
</div>
<div class="btn-container">
<input type="text" id="input-DeviceName" placeholder="..." style="width: 100%; max-width: 220px;" required>
</div>
<!-- Buttons -->
<div class="btn-container">
<button id="bleConnectBtn" onclick="connectToBle()">Connect</button>
<button id="checkStatusBtn" onclick="checkStatus()" disabled>Check Status</button>
</div>
<!-- Log Area -->
<textarea id="logArea" readonly></textarea>
<div class="btn-container">
<button id="checkVersionBtn" onclick="checkVersion()" disabled>Check Version</button>
<button id="startUpgradeBtn" onclick="startUpgrade()" disabled>Start Update</button>
</div>
<!-- Wi-Fi Input Fields -->
<div class="input-container">
<input type="text" id="wifissid" name="wifissid" placeholder="Enter WiFi SSID" required>
<input type="password" id="wifipassword" name="wifipassword" placeholder="Enter WiFi Password" required>
<div style="display: flex; align-items: center; gap: 5px;">
<input type="checkbox" id="showPassword" onclick="togglePasswordVisibility()" style="width: auto;">
<label for="showPassword">Show Password</label>
</div>
</div>
<!-- Added margin-top above this button -->
<div class="btn-container wifi">
<button id="wifiConnectBtn" onclick="wifiConnect()" disabled>Connect Wifi</button>
</div>
<script>
(function(){
'use strict';
// Constants
const BLE_SERVER_NAME = "ATALIGHTS"; // Replace with your server name
const BLE_SERVICE_UUID = "abcdef01-2345-6789-1234-56789abcdef0"; // Replace with your service UUID
const BLE_CHARACTERISTIC1_UUID = "abcdef01-2345-6789-1234-56789abcdef1"; // Replace with your characteristic UUID
const BLE_CHARACTERISTIC2_UUID = "abcdef02-2345-6789-1234-56789abcdef1"; // Replace with your characteristic UUID
let bleDevice = null;
let bleCharacteristic1 = null;
let bleCharacteristic2 = null;
let bleConnected = false;
const WIFI_STAT = { WIFI_DISCONNECTED:0, WIFI_BAD_CREDS:1, WIFI_NO_AP:2, WIFI_CONNECTED:3 };
const updatePacket = {
wifiConnected: false,
wifiOnline: false,
wifiIP: [0, 0, 0, 0],
currVersion: [0, 0, 0],
newVersion: [0, 0, 0]
};
// Function to automatically start the connection process when the page loads
function autoStart() {
document.getElementById('input-DeviceName').value = BLE_SERVER_NAME;
}
// Call autoStart when the page loads
window.addEventListener('DOMContentLoaded', autoStart);
function 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;
}
// Log messages to the textarea
function logMessage(message) {
const logArea = document.getElementById('logArea');
logArea.value += message + '\n';
logArea.scrollTop = logArea.scrollHeight;
}
// Function to scan for BLE devices
async function scanForDevices() {
logMessage('Scanning for BLE devices...');
try {
const device = await navigator.bluetooth.requestDevice({
acceptAllDevices: true,
optionalServices: [BLE_SERVICE_UUID]
});
if (device) {
logMessage(`Found device: ${device.name || "Unnamed"} (ID: ${device.id})`);
} else {
logMessage('No devices found.');
}
} catch (error) {
logMessage(`Scan failed: ${error.message}`);
}
}
// Function to connect to the BLE server
async function connectToBle() {
if(!navigator.bluetooth){
logMessage('Web Bluetooth not supported in this browser.');
return;
}
try {
bleDevice = await navigator.bluetooth.requestDevice({
filters: [{ name: document.getElementById('input-DeviceName').value }],
optionalServices: [BLE_SERVICE_UUID]
});
//logMessage(`Connecting to ${bleDevice.name}`);
const server = await bleDevice.gatt.connect();
//await server.setPreferredMtu(247); // Request larger MTU size
const service = await server.getPrimaryService(BLE_SERVICE_UUID);
bleCharacteristic1 = await service.getCharacteristic(BLE_CHARACTERISTIC1_UUID);
// Subscribe to notifications
//await bleCharacteristic1.startNotifications();
// Add event listener for incoming notifications
//bleCharacteristic1.addEventListener('characteristicvaluechanged', handleChar1Notifications);
//logMessage('Getting characteristic...');
bleCharacteristic2 = await service.getCharacteristic(BLE_CHARACTERISTIC2_UUID);
// Subscribe to notifications
await bleCharacteristic2.startNotifications();
// Add event listener for incoming notifications
bleCharacteristic2.addEventListener('characteristicvaluechanged', (event) => {
const value = event.target.value;
const decoder = new TextDecoder();
const decodedValue = decoder.decode(value);
logMessage('--> ' + decodedValue);
});
bleConnected = true;
// Auto-reconnect / state reset handler
bleDevice.addEventListener('gattserverdisconnected', handleDisconnect);
document.getElementById('bleConnectBtn').disabled = true;
document.querySelector('.status-indicator-ble').style.backgroundColor = 'green';
document.getElementById('status-ble-connection').textContent ="BLE Status: Connected";
document.getElementById('wifiConnectBtn').disabled = false;
document.getElementById('checkStatusBtn').disabled = false;
logMessage(`Connected to ${bleDevice.name}`);
await readPacket();
processUpdatePacket(updatePacket);
} catch (error) {
if (error.message.includes("cancelled")) {
logMessage("Connection cancelled by user.");
} else {
logMessage(`Connection failed: ${error.message}`);
}
}
}
async function wifiConnect() {
const ssid = document.getElementById('wifissid').value;
const password = document.getElementById('wifipassword').value;
if (ssid && password) {
// Send credentials to the device (retain original spacing format expected by firmware)
const jsonString = ' {"ssid":"' + ssid.trim() + '","pass":"' + password + '"} ';
await sendPacket('wifi-connect' + jsonString);
await readPacket();
processUpdatePacket(updatePacket);
} else {
alert('Please enter both SSID and password.');
}
}
async function checkStatus() {
if (bleCharacteristic1) {
await readPacket();
processUpdatePacket(updatePacket);
} else {
logMessage('BLE device not connected.');
}
}
async function checkVersion() {
await sendPacket('version-check');
// loop and monitor the the updatePacket.newVersion
await new Promise(resolve => setTimeout(resolve, 2000));
let success = false;
for (let i = 0; i < 20; i++) {
await readPacket();
if (updatePacket.newVersion[0] > 0) {
success = true;
break;
}
await new Promise(resolve => setTimeout(resolve, 1000));
}
if(success) {
processUpdatePacket(updatePacket);
logMessage("New Version: Available");
} else {
logMessage("New Version: Not Available");
}
}
async function startUpgrade() {
try {
await sendPacket('upgrade-start');
logMessage("Upgrade Starting... Please wait.");
} catch (error) {
logMessage(`Error starting upgrade: ${error.message}`);
}
}
function handleDisconnect(){
bleConnected = false;
document.querySelector('.status-indicator-ble').style.backgroundColor = 'gray';
document.getElementById('status-ble-connection').textContent = 'BLE Status: Disconnected';
document.getElementById('bleConnectBtn').disabled = false;
document.getElementById('checkStatusBtn').disabled = true;
document.getElementById('wifiConnectBtn').disabled = true;
logMessage('BLE disconnected');
}
async function sendPacket(packetMsg) {
if (!bleCharacteristic1) {
console.log("Cannot send packet: Not connected to BLE server.");
return;
}
const maxRetries = 3;
const retryDelay = 1000; // 1 second
let attempt = 0;
while (attempt < maxRetries) {
try {
//logMessage(`Sending request: ${packetMsg} (Attempt ${attempt + 1})`);
const encoder = new TextEncoder();
await bleCharacteristic1.writeValueWithResponse(encoder.encode(packetMsg));
//console.log("Request sent successfully");
return;
} catch (error) {
console.error(`Failed to send packet: ${error.message}`);
attempt++;
if (attempt < maxRetries) {
console.log(`Retrying in ${retryDelay / 1000} seconds...`);
await new Promise(resolve => setTimeout(resolve, retryDelay));
} else {
console.error("Max retries reached. Failed to send request.");
}
}
}
}
async function readPacket() {
if (!bleCharacteristic1) {
console.log("Cannot read packet: Not connected to BLE server.");
return;
}
const maxRetries = 3;
const retryDelay = 1000; // 1 second
let attempt = 0;
while (attempt < maxRetries) {
try {
const value = await bleCharacteristic1.readValue();
const data = new Uint8Array(value.buffer);
if (data.length === 12) {
updatePacket.wifiConnected = data[0] !== 0;
updatePacket.wifiOnline = data[1] !== 0;
updatePacket.wifiIP = [data[2], data[3], data[4], data[5]];
updatePacket.currVersion = [data[6], data[7], data[8]];
updatePacket.newVersion = [data[9], data[10], data[11]];
//processUpdatePacket(updatePacket);
return;
}
console.log("Invalid packet length");
return;
} catch (error) {
console.error(`Failed to read packet: ${error.message}`);
attempt++;
if (attempt < maxRetries) {
console.log(`Retrying in ${retryDelay / 1000} seconds...`);
await new Promise(resolve => setTimeout(resolve, retryDelay));
} else {
console.error("Max retries reached. Failed to read packet.");
}
}
}
}
// Process update packet
function processUpdatePacket(packet) {
// Process the packet data
//console.log("Processing update packet:", packet);
if(packet.wifiConnected === true) {
if(packet.wifiConnected && packet.wifiIP[0] > 0) {
document.getElementById('status-wifi-client').textContent = 'Wifi Client: Connected (' + packet.wifiIP.join('.') + ')';
} else {
document.getElementById('status-wifi-client').textContent = 'Wifi Client: Connected';
}
document.querySelector('.status-indicator-wifi').style.backgroundColor = 'green';
} else {
document.getElementById('status-wifi-client').textContent = 'Wifi Client: ...';
document.querySelector('.status-indicator-wifi').style.backgroundColor = 'gray';
}
if(packet.wifiOnline === true) {
document.getElementById('status-internet').textContent = 'Online';
document.querySelector('.status-indicator-internet').style.backgroundColor = 'green';
document.getElementById('checkVersionBtn').disabled = false;
} else {
document.getElementById('status-internet').textContent = 'Offline';
document.querySelector('.status-indicator-internet').style.backgroundColor = 'gray';
document.getElementById('checkVersionBtn').disabled = true;
}
if (packet.currVersion[0] > 0) {
document.getElementById('status-current-version').textContent = 'Curr Version: ' + packet.currVersion.join('.');
} else {
document.getElementById('status-current-version').textContent = 'Curr Version: ...';
}
if (packet.newVersion[0] > 0) {
document.getElementById('status-new-version').textContent = 'New Version: ' + packet.newVersion.join('.');
document.getElementById('checkVersionBtn').disabled = true;
logMessage("Latest Update is: " + packet.newVersion.join('.'));
if(packet.wifiOnline && compareVersions(packet.newVersion, packet.currVersion) > 0) {
//enable start upgrade button
logMessage("New Version: Available");
document.getElementById('startUpgradeBtn').disabled = false;
} else {
//disable start upgrade button
document.getElementById('startUpgradeBtn').disabled = true;
logMessage("New Version: Not Available");
}
} else {
document.getElementById('status-new-version').textContent = 'New Version: ...';
}
}
//BLE_Characteristic.addEventListener('characteristicvaluechanged', handleNotifications);
function handleChar1Notifications(event) {
const data = new Uint8Array(event.data);
if (data.length !== 12) { // 1 byte for id, 4 bytes for booleans, 4 bytes for wifiIP, 3 bytes for currVersion, 3 bytes for newVersion
console.log("Invalid packet length");
return;
}
// Update existing updatePacket object instead of creating new one
updatePacket.wifiConnected = data[0] !== 0;
updatePacket.wifiOnline = data[1] !== 0;
updatePacket.wifiIP = [data[2], data[3], data[4], data[5]];
updatePacket.currVersion = [data[6], data[7], data[8]];
updatePacket.newVersion = [data[9], data[10], data[11]];
processUpdatePacket(updatePacket);
}
// Event listeners for buttons
function togglePasswordVisibility() {
const passwordInput = document.getElementById('wifipassword');
passwordInput.type = this.checked ? 'text' : 'password';
}
// Initial call to process the update packet with defaults
processUpdatePacket(updatePacket);
})();
</script>
</body>
</html>

View File

@ -0,0 +1,476 @@
<!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>
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 20px;
background-color: #f4f4f4;
text-align: center;
}
h1 {
font-size: 22px;
margin-bottom: 5px;
}
.status-container {
display: flex;
align-items: center;
justify-content: left;
margin-bottom: 4px;
}
.status-indicator-ble {
width: 20px;
height: 20px;
border-radius: 50%;
background-color: gray;
margin-right: 10px;
}
.status-indicator-wifi {
width: 20px;
height: 20px;
border-radius: 50%;
background-color: gray;
margin-right: 10px;
}
.status-indicator-internet {
width: 20px;
height: 20px;
border-radius: 50%;
background-color: gray;
margin-right: 10px;
}
.btn-container {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 10px;
margin-bottom: 10px;
}
/* Adds space above the WiFi Connect button */
.btn-container.wifi {
margin-top: 20px;
}
button {
flex: 1;
max-width: 130px;
padding: 10px;
font-size: 16px;
border: none;
border-radius: 5px;
cursor: pointer;
background-color: #007bff;
color: white;
transition: background 0.3s ease;
}
button:disabled {
background-color: #ccc;
}
button:hover:not(:disabled) {
background-color: #0056b3;
}
textarea {
width: 100%;
height: 300px;
font-size: 14px;
padding: 10px;
border-radius: 5px;
border: 1px solid #ccc;
resize: none;
}
.input-container {
display: flex;
flex-direction: column;
align-items: center;
gap: 10px;
margin-top: 15px;
}
input {
width: 90%;
max-width: 300px;
padding: 10px;
font-size: 16px;
border: 1px solid #ccc;
border-radius: 5px;
text-align: center;
}
input::placeholder {
text-align: center;
}
@media (max-width: 480px) {
body {
padding: 15px;
}
h1 {
font-size: 20px;
}
button {
font-size: 14px;
padding: 8px;
}
input, textarea {
font-size: 14px;
}
}
</style>
</head>
<body>
<h1>ATA Firmware Update</h1>
<!-- Status Indicators -->
<div class="status-container">
<span class="status-indicator-ble"></span>
<label id="status-ble-connection">BLE Status: ...</label>
</div>
<div class="status-container">
<span class="status-indicator-wifi"></span>
<label id="status-wifi-client">Wifi Client: ...</label>
</div>
<div class="status-container">
<span class="status-indicator-internet"></span>
<label id="status-internet">Internet: ...</label>
</div>
<div class="status-container">
<label id="status-current-version">Curr Version: ...</label>
</div>
<div class="status-container">
<label id="status-new-version">New Version: ...</label>
</div>
<div class="btn-container">
<label id="ble-device-name">Device Name:</label>
</div>
<div class="btn-container">
<input type="text" id="input-DeviceName" placeholder="..." style="width: 100%; max-width: 220px;" required>
</div>
<!-- Buttons -->
<div class="btn-container">
<button id="bleConnectBtn" onclick="connectToBle()">Connect</button>
<button id="checkStatusBtn" onclick="checkStatus()" disabled>Check Status</button>
</div>
<!-- Log Area -->
<textarea id="logArea" readonly></textarea>
<div class="btn-container">
<button id="checkVersionBtn" onclick="checkVersion()" disabled>Check Version</button>
<button id="startUpgradeBtn" onclick="startUpgrade()" disabled>Start Update</button>
</div>
<!-- Wi-Fi Input Fields -->
<div class="input-container">
<input type="text" id="wifissid" name="wifissid" placeholder="Enter WiFi SSID" required>
<input type="password" id="wifipassword" name="wifipassword" placeholder="Enter WiFi Password" required>
<div style="display: flex; align-items: center; gap: 5px;">
<input type="checkbox" id="showPassword" onclick="togglePasswordVisibility()" style="width: auto;">
<label for="showPassword">Show Password</label>
</div>
</div>
<!-- Added margin-top above this button -->
<div class="btn-container wifi">
<button id="wifiConnectBtn" onclick="wifiConnect()" disabled>Connect Wifi</button>
</div>
<script>
(function(){
'use strict';
/* ================= Constants & Packet Layout ================= */
const BLE_SERVER_NAME = "ATALIGHTS"; // Keep hardcoded per instruction (ignore external JSON)
const BLE_SERVICE_UUID = "abcdef01-2345-6789-1234-56789abcdef0";
const BLE_CHARACTERISTIC1_UUID = "abcdef01-2345-6789-1234-56789abcdef1"; // Control / status
const BLE_CHARACTERISTIC2_UUID = "abcdef02-2345-6789-1234-56789abcdef1"; // Logs / events
// Packet layout (mirrors firmware struct updateStatus)
// byte 0 : wifiStatus (enum)
// byte 1 : wifiOnline (bool)
// bytes2-5: wifiIP
// bytes6-8: currVersion (major,minor,patch)
// bytes9-11: newVersion (major,minor,patch)
// bytes12-31: wifiSSID (20 bytes, null padded)
const PACKET_LEN = 32;
const OFF_WIFI_STATUS = 0;
const OFF_WIFI_ONLINE = 1;
const OFF_WIFI_IP = 2;
const OFF_CURR_VER = 6;
const OFF_NEW_VER = 9;
const OFF_WIFI_SSID = 12;
const WIFI_STAT = { DISCONNECTED:0, BAD_CREDS:1, NO_AP:2, CONNECTED:3 };
const WIFI_STAT_TEXT = ["Disconnected","Bad Creds","No AP","Connected"];
const MAX_LOG_LINES = 400;
/* ================= State ================= */
let bleDevice=null, bleCharacteristic1=null, bleCharacteristic2=null;
let bleConnected=false;
const state = {
wifiStatus: WIFI_STAT.DISCONNECTED,
wifiOnline:false,
wifiIP:[0,0,0,0],
currVersion:[0,0,0],
newVersion:[0,0,0],
wifiSSID:""
};
/* ================= Cached DOM ================= */
const el = {};
function cacheDom(){
el.bleIndicator = document.querySelector('.status-indicator-ble');
el.wifiIndicator = document.querySelector('.status-indicator-wifi');
el.internetIndicator = document.querySelector('.status-indicator-internet');
el.lblBle = document.getElementById('status-ble-connection');
el.lblWifi = document.getElementById('status-wifi-client');
el.lblInternet = document.getElementById('status-internet');
el.lblCurrVer = document.getElementById('status-current-version');
el.lblNewVer = document.getElementById('status-new-version');
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 ================= */
function logMessage(msg){
const lines = el.logArea.value.trim().length ? el.logArea.value.split(/\n/) : [];
lines.push(msg);
if(lines.length > MAX_LOG_LINES){
lines.splice(0, lines.length - MAX_LOG_LINES);
}
el.logArea.value = lines.join('\n') + '\n';
el.logArea.scrollTop = el.logArea.scrollHeight;
}
function 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;
}
function colorIndicator(elm, color){ if(elm) elm.style.backgroundColor = color; }
function ipToString(ip){ return ip.join('.'); }
/* ================= Packet Handling ================= */
function parsePacket(data){
if(data.length !== PACKET_LEN) return false;
state.wifiStatus = data[OFF_WIFI_STATUS];
if(state.wifiStatus > WIFI_STAT.CONNECTED) state.wifiStatus = WIFI_STAT.DISCONNECTED; // clamp
state.wifiOnline = !!data[OFF_WIFI_ONLINE];
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));
// Extract SSID (stop at first 0)
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');
colorIndicator(el.bleIndicator, bleConnected ? 'green' : 'gray');
// WiFi client
const statText = WIFI_STAT_TEXT[state.wifiStatus] || 'Unknown';
if(state.wifiStatus === WIFI_STAT.CONNECTED){
const ssidPart = state.wifiSSID ? ' SSID: '+state.wifiSSID : '';
el.lblWifi.textContent = 'Wifi Client: ' + statText + (state.wifiIP[0] ? ' ('+ipToString(state.wifiIP)+')':'' ) + ssidPart;
colorIndicator(el.wifiIndicator, 'green');
} else if(state.wifiStatus === WIFI_STAT.BAD_CREDS){
el.lblWifi.textContent = 'Wifi Client: Bad Credentials';
colorIndicator(el.wifiIndicator, 'orange');
} else if(state.wifiStatus === WIFI_STAT.NO_AP){
el.lblWifi.textContent = 'Wifi Client: AP Not Found';
colorIndicator(el.wifiIndicator, 'orange');
} else {
el.lblWifi.textContent = 'Wifi Client: ' + statText;
colorIndicator(el.wifiIndicator, 'gray');
}
// Internet
el.lblInternet.textContent = state.wifiOnline ? 'Online' : 'Offline';
colorIndicator(el.internetIndicator, state.wifiOnline ? 'green' : 'gray');
// Versions
el.lblCurrVer.textContent = state.currVersion[0] ? 'Curr Version: ' + state.currVersion.join('.') : 'Curr Version: ...';
el.lblNewVer.textContent = state.newVersion[0] ? 'New Version: ' + state.newVersion.join('.') : 'New Version: ...';
// Buttons
el.btnCheckStatus.disabled = !bleConnected;
el.btnWifiConnect.disabled = !bleConnected;
el.btnCheckVersion.disabled = !(bleConnected && state.wifiOnline);
const newerAvail = 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:[{ name: 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();
bleCharacteristic2.addEventListener('characteristicvaluechanged', e => {
try{
const txt = new TextDecoder().decode(e.target.value);
logMessage('--> ' + txt.trim());
}catch(_){ /* ignore */ }
});
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; updateUI(); logMessage('BLE disconnected');
}
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; // prevent multiple submissions while polling
await sendPacket('wifi-connect {"ssid":"'+ssid+'","pass":"'+pw+'"} ');
// Poll for status for up to 15s
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) return; // keep disabled if BLE disconnected during process
// Re-enable for retry unless connected
if(state.wifiStatus !== WIFI_STAT.CONNECTED){ el.btnWifiConnect.disabled = false; }
}
async function checkStatus(){ if(await readPacket()) updateUI(); }
async function checkVersion(){
el.btnCheckVersion.disabled = true;
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]){ logMessage('Latest version: '+state.newVersion.join('.')); break; }
}
if(!state.newVersion[0]) logMessage('No new version info received');
updateUI();
}
async function startUpgrade(){
if(el.btnStartUpgrade.disabled) return;
logMessage('Starting upgrade...');
el.btnStartUpgrade.disabled = true;
await sendPacket('upgrade-start');
// Progress will arrive via characteristic2 logs
}
/* ================= Helpers ================= */
const delay = ms => new Promise(r=>setTimeout(r,ms));
function togglePasswordVisibility(){ el.inPass.type = el.chkShowPass.checked ? 'text' : 'password'; }
function init(){
cacheDom();
el.inDeviceName.value = BLE_SERVER_NAME;
el.chkShowPass.addEventListener('change', togglePasswordVisibility);
// Inline onclicks already wired; ensure functions are in scope
updateUI();
logMessage('Ready. Enter device name or use default and press Connect.');
}
// Expose functions for inline handlers
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>

View File

@ -0,0 +1,472 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ATA Light Stick Reg</title>
<style>
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 20px;
background-color: #f4f4f4;
text-align: center;
}
h1 {
font-size: 22px;
margin-bottom: 20px;
}
.status-container {
display: flex;
align-items: center;
justify-content: left;
margin-bottom: 15px;
}
.status-indicator-ble {
width: 20px;
height: 20px;
border-radius: 50%;
background-color: gray;
margin-right: 10px;
}
.status-indicator-wifi {
width: 20px;
height: 20px;
border-radius: 50%;
background-color: gray;
margin-right: 10px;
}
.status-indicator-internet {
width: 20px;
height: 20px;
border-radius: 50%;
background-color: gray;
margin-right: 10px;
}
.btn-container {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 10px;
margin-bottom: 10px;
}
/* Adds space above the WiFi Connect button */
.btn-container.wifi {
margin-top: 20px;
}
button {
flex: 1;
max-width: 130px;
padding: 10px;
font-size: 16px;
border: none;
border-radius: 5px;
cursor: pointer;
background-color: #007bff;
color: white;
transition: background 0.3s ease;
}
button:disabled {
background-color: #ccc;
}
button:hover:not(:disabled) {
background-color: #0056b3;
}
textarea {
width: 97%;
height: 100px;
font-size: 14px;
padding: 10px;
border-radius: 5px;
border: 1px solid #ccc;
resize: none;
}
.input-container {
display: flex;
flex-direction: column;
align-items: center;
gap: 10px;
margin-top: 15px;
}
input {
width: 90%;
max-width: 300px;
padding: 10px;
font-size: 16px;
border: 1px solid #ccc;
border-radius: 5px;
text-align: center;
}
input::placeholder {
text-align: center;
}
@media (max-width: 480px) {
body {
padding: 15px;
}
h1 {
font-size: 20px;
}
button {
font-size: 14px;
padding: 8px;
}
input, textarea {
font-size: 14px;
}
}
</style>
</head>
<body>
<h1>ATA Flash-Stick Link/Registration</h1>
<!-- Status Indicators -->
<div class="status-container">
<span class="status-indicator-ble"></span>
<label id="status-ble-connection">Master: ...</label>
</div>
<div class="status-container">
<span class="status-indicator-registration"></span>
<label id="status-registration">Registration: ...</label>
</div>
<div class="btn-container">
<button id="bleConnectBtn">Connect</button>
<button id="bleDisconnectBtn">Disconnect</button>
<button id="bleSaveBtn">Save</button>
</div>
<textarea id="logArea" readonly></textarea>
<script>
// Constants
const BLE_SERVER_NAME = "ATALIGHTS"; // Replace with your server name
const BLE_SERVICE_UUID = "abcdef01-2345-6789-1234-56789abcdef0"; // Replace with your service UUID
const BLE_CHARACTERISTIC1_UUID = "abcdef01-2345-6789-1234-56789abcdef1"; // Replace with your characteristic UUID
const BLE_CHARACTERISTIC2_UUID = "abcdef02-2345-6789-1234-56789abcdef1"; // Replace with your characteristic UUID
let bleDevice = null;
let bleCharacteristic1 = null;
let bleCharacteristic2 = null;
let bleConnected = false;
const WIFI_STAT = { WIFI_DISCONNECTED:0, WIFI_BAD_CREDS:1, WIFI_NO_AP:2, WIFI_CONNECTED:3 };
let updatePacket = {
name: "",
};
// Log messages to the textarea
function logMessage(message) {
const logArea = document.getElementById('logArea');
logArea.value += message + '\n';
logArea.scrollTop = logArea.scrollHeight;
}
// Function to scan for BLE devices
async function scanForDevices() {
logMessage('Scanning for BLE devices...');
try {
const device = await navigator.bluetooth.requestDevice({
acceptAllDevices: true,
optionalServices: [BLE_SERVICE_UUID]
});
if (device) {
logMessage(`Found device: ${device.name || "Unnamed"} (ID: ${device.id})`);
} else {
logMessage('No devices found.');
}
} catch (error) {
logMessage(`Scan failed: ${error.message}`);
}
}
// Function to connect to the BLE server
async function connectToBle() {
try {
bleDevice = await navigator.bluetooth.requestDevice({
filters: [{ name: BLE_SERVER_NAME }],
optionalServices: [BLE_SERVICE_UUID]
});
//logMessage(`Connecting to ${bleDevice.name}`);
const server = await bleDevice.gatt.connect();
//await server.setPreferredMtu(247); // Request larger MTU size
const service = await server.getPrimaryService(BLE_SERVICE_UUID);
bleCharacteristic1 = await service.getCharacteristic(BLE_CHARACTERISTIC1_UUID);
// Subscribe to notifications
//await bleCharacteristic1.startNotifications();
// Add event listener for incoming notifications
//bleCharacteristic1.addEventListener('characteristicvaluechanged', handleChar1Notifications);
//logMessage('Getting characteristic...');
bleCharacteristic2 = await service.getCharacteristic(BLE_CHARACTERISTIC2_UUID);
// Subscribe to notifications
await bleCharacteristic2.startNotifications();
// Add event listener for incoming notifications
bleCharacteristic2.addEventListener('characteristicvaluechanged', (event) => {
const value = event.target.value;
const decoder = new TextDecoder();
const decodedValue = decoder.decode(value);
logMessage('--> ' + decodedValue);
});
bleConnected = true;
document.getElementById('bleConnectBtn').disabled = true;
document.querySelector('.status-indicator-ble').style.backgroundColor = 'green';
document.getElementById('status-ble-connection').textContent = 'Device: Connected';
document.getElementById('wifiConnectBtn').disabled = false;
document.getElementById('checkStatusBtn').disabled = false;
logMessage(`Connected to ${bleDevice.name}`);
await readPacket();
processUpdatePacket(updatePacket);
} catch (error) {
if (error.message.includes("cancelled")) {
logMessage("Connection cancelled by user.");
} else {
logMessage(`Connection failed: ${error.message}`);
}
}
}
async function sendPacket(packetMsg) {
if (!bleCharacteristic1) {
console.log("Cannot send packet: Not connected to BLE server.");
return;
}
const maxRetries = 3;
const retryDelay = 1000; // 1 second
let attempt = 0;
while (attempt < maxRetries) {
try {
//logMessage(`Sending request: ${packetMsg} (Attempt ${attempt + 1})`);
const encoder = new TextEncoder();
await bleCharacteristic1.writeValueWithResponse(encoder.encode(packetMsg));
//console.log("Request sent successfully");
return;
} catch (error) {
console.error(`Failed to send packet: ${error.message}`);
attempt++;
if (attempt < maxRetries) {
console.log(`Retrying in ${retryDelay / 1000} seconds...`);
await new Promise(resolve => setTimeout(resolve, retryDelay));
} else {
console.error("Max retries reached. Failed to send request.");
}
}
}
}
async function readPacket() {
if (!bleCharacteristic1) {
console.log("Cannot read packet: Not connected to BLE server.");
return;
}
const maxRetries = 3;
const retryDelay = 1000; // 1 second
let attempt = 0;
while (attempt < maxRetries) {
try {
const value = await bleCharacteristic1.readValue();
const data = new Uint8Array(value.buffer);
if (data.length === 12) {
updatePacket.wifiConnected = data[0] !== 0;
updatePacket.wifiOnline = data[1] !== 0;
updatePacket.wifiIP = [data[2], data[3], data[4], data[5]];
updatePacket.currVersion = [data[6], data[7], data[8]];
updatePacket.newVersion = [data[9], data[10], data[11]];
//processUpdatePacket(updatePacket);
return;
}
console.log("Invalid packet length");
return;
} catch (error) {
console.error(`Failed to read packet: ${error.message}`);
attempt++;
if (attempt < maxRetries) {
console.log(`Retrying in ${retryDelay / 1000} seconds...`);
await new Promise(resolve => setTimeout(resolve, retryDelay));
} else {
console.error("Max retries reached. Failed to read packet.");
}
}
}
}
// Process update packet
function processUpdatePacket(packet) {
// Process the packet data
//console.log("Processing update packet:", packet);
if(packet.wifiConnected === true) {
if(packet.wifiConnected && packet.wifiIP[0] > 0) {
document.getElementById('status-wifi-client').textContent = 'Wifi Client: Connected (' + packet.wifiIP.join('.') + ')';
} else {
document.getElementById('status-wifi-client').textContent = 'Wifi Client: Connected';
}
document.querySelector('.status-indicator-wifi').style.backgroundColor = 'green';
} else {
document.getElementById('status-wifi-client').textContent = 'Wifi Client: ...';
document.querySelector('.status-indicator-wifi').style.backgroundColor = 'gray';
}
if(packet.wifiOnline === true) {
document.getElementById('status-internet').textContent = 'Online';
document.querySelector('.status-indicator-internet').style.backgroundColor = 'green';
document.getElementById('checkVersionBtn').disabled = false;
} else {
document.getElementById('status-internet').textContent = 'Offline';
document.querySelector('.status-indicator-internet').style.backgroundColor = 'gray';
document.getElementById('checkVersionBtn').disabled = true;
}
if (packet.currVersion[0] > 0) {
document.getElementById('status-current-version').textContent = 'Curr Version: ' + packet.currVersion.join('.');
} else {
document.getElementById('status-current-version').textContent = 'Curr Version: ...';
}
if (packet.newVersion[0] > 0) {
document.getElementById('status-new-version').textContent = 'New Version: ' + packet.newVersion.join('.');
document.getElementById('checkVersionBtn').disabled = true;
if(packet.wifiOnline && packet.newVersion[0] > packet.currVersion[0] ||
(packet.newVersion[0] === packet.currVersion[0] && packet.newVersion[1] > packet.currVersion[1]) ||
(packet.newVersion[0] === packet.currVersion[0] && packet.newVersion[1] === packet.currVersion[1] && packet.newVersion[2] > packet.currVersion[2])) {
//enable start upgrade button
logMessage("New Version Available:");
document.getElementById('startUpgradeBtn').disabled = false;
} else {
//disable start upgrade button
document.getElementById('startUpgradeBtn').disabled = true;
logMessage("New Version: Not Available");
}
} else {
document.getElementById('status-new-version').textContent = 'New Version: ...';
}
}
//BLE_Characteristic.addEventListener('characteristicvaluechanged', handleNotifications);
function handleChar1Notifications(event) {
const data = new Uint8Array(event.data);
if (data.length !== 12) { // 1 byte for id, 4 bytes for booleans, 4 bytes for wifiIP, 3 bytes for currVersion, 3 bytes for newVersion
console.log("Invalid packet length");
return;
}
// Update existing updatePacket object instead of creating new one
updatePacket.wifiConnected = data[0] !== 0;
updatePacket.internetAvailable = data[1] !== 0;
updatePacket.wifiIP = [data[2], data[3], data[4], data[5]];
updatePacket.currVersion = [data[6], data[7], data[8]];
updatePacket.newVersion = [data[9], data[10], data[11]];
processUpdatePacket(updatePacket);
}
document.getElementById('showPassword').addEventListener('change', function() {
const passwordInput = document.getElementById('wifipassword');
passwordInput.type = this.checked ? 'text' : 'password';
});
// Event listeners for buttons
document.getElementById('bleConnectBtn').addEventListener('click', connectToBle);
document.getElementById('checkStatusBtn').addEventListener('click', async () => {
if (bleCharacteristic1) {
await readPacket();
processUpdatePacket(updatePacket);
} else {
logMessage('BLE device not connected.');
}
});
document.getElementById('checkVersionBtn').addEventListener('click', async () => {
await sendPacket('version-check');
// loop and monitor the the updatePacket.newVersion
await new Promise(resolve => setTimeout(resolve, 2000));
success = false;
for (let i = 0; i < 20; i++) {
await readPacket();
if (updatePacket.newVersion[0] > 0) {
success = true;
break;
}
await new Promise(resolve => setTimeout(resolve, 1000));
}
if(success) {
processUpdatePacket(updatePacket);
logMessage("New Version: Available");
} else {
logMessage("New Version: Not Available");
}
});
document.getElementById('wifiConnectBtn').addEventListener('click', async () => {
const ssid = document.getElementById('wifissid').value;
const password = document.getElementById('wifipassword').value;
if (ssid && password) {
// Send credentials to the device
jsonString = ' {"ssid":"' + ssid + '","pass":"' + password + '"} ';
await sendPacket('wifi-connect' + jsonString);
await readPacket();
processUpdatePacket(updatePacket);
} else {
alert('Please enter both SSID and password.');
}
});
document.getElementById('startUpgradeBtn').addEventListener('click', async () => {
try {
await sendPacket('upgrade-start');
logMessage("Upgrade Starting... Please wait.");
} catch (error) {
logMessage(`Error starting upgrade: ${error.message}`);
}
});
// Initial call to process the update packet with defaults
processUpdatePacket(updatePacket);
</script>
</body>
</html>

View File

@ -0,0 +1,9 @@
{
"name": "ATALIGHTS",
"lights-service": "FFE0",
"lights-char": "FFE1",
"stick-char": "FFE2",
"upgrade-service": "abcdef01-2345-6789-1234-56789abcdef0",
"upgrade-char1": "abcdef01-2345-6789-1234-56789abcdef1",
"upgrade-char2": "abcdef02-2345-6789-1234-56789abcdef1"
}

View File

@ -0,0 +1,5 @@
#Setting up /system/system.json
1 - Choose the
modes:
booth, roamer, stick

View File

@ -1,4 +1,5 @@
{
"baseurl": "https://storage.googleapis.com/boothifier/",
"folder": "latest/"
"folder": "latest/",
"baseurl": "https://s3-minio.boothwizard.com/boothifier/",
"baseurl2": "https://storage.googleapis.com/boothifier/"
}

View File

@ -2,25 +2,38 @@
"version": {
"major": 1,
"minor": 4,
"patch": 7
"patch": 8
},
"release_date": "2024-01-15",
"release_date": "2025-08-20",
"release_time": "08:57:43",
"description": "This is a firmware update.",
"changelog": [
"Fixed issue with device connectivity.",
"Improved firmware update process."
"...",
"..."
],
"firmware": {
"file": "firmware.bin",
"md5": "b8c880418a180efb23260ee093d13e61",
"size": 1409568
"md5": "fcec6e659842cc0333880b701a3e2634",
"size": 1401712
},
"files": [
{
"remote": "data/ata-boothifier-upgrade.html",
"local": "/ata-boothifier-upgrade.html",
"md5": "e452db020a5ad660599129ab35e01058",
"size": 16736
"md5": "92074ec24fd467545272eb9e837d644c",
"size": 16980
},
{
"remote": "data/ata-boothifier-upgradeV2.html",
"local": "/ata-boothifier-upgradeV2.html",
"md5": "484648993370e4b6aff13124bd1c55c1",
"size": 18178
},
{
"remote": "data/ata-boothifier-upgradeV3.html",
"local": "/ata-boothifier-upgradeV3.html",
"md5": "79426145db379ac15ccc742245a31ecb",
"size": 17233
},
{
"remote": "data/favicon.ico",
@ -28,6 +41,12 @@
"md5": "ba4c4e3bf5e5db2bbfc56a52f3657d79",
"size": 1150
},
{
"remote": "data/flashstik-reg.html",
"local": "/flashstik-reg.html",
"md5": "394fdfe3cd3fb89a8ddb3d6edf2d2da4",
"size": 15651
},
{
"remote": "data/css/global-style.css",
"local": "/css/global-style.css",
@ -76,6 +95,18 @@
"md5": "fdb81281d3773a7462998fdddbe6f5bf",
"size": 196885
},
{
"remote": "data/system/ble.json",
"local": "/system/ble.json",
"md5": "faebefd5b50046a01d4a8aa27f8504d0",
"size": 307
},
{
"remote": "data/system/readme.txt",
"local": "/system/readme.txt",
"md5": "198c92a2cc06b3effce3b5ba313cc6a0",
"size": 86
},
{
"remote": "data/system/tunes.json",
"local": "/system/tunes.json",
@ -85,8 +116,8 @@
{
"remote": "data/system/update.json",
"local": "/system/update.json",
"md5": "8046dbf9490cfd88fe5970b4ec1ac2cd",
"size": 91
"md5": "01cc6a1935601085df49308cab646673",
"size": 156
},
{
"remote": "data/www/about.html",

View File

@ -32,7 +32,8 @@ lib_deps =
build_flags =
-mfix-esp32-psram-cache-issue
-D CONFIG_LOG_DYNAMIC_LEVEL_CONTROL=1
-D CORE_DEBUG_LEVEL=ARDUHAL_LOG_LEVEL_VERBOSE
#-D CORE_DEBUG_LEVEL=ARDUHAL_LOG_LEVEL_VERBOSE
-D CORE_DEBUG_LEVEL=ARDUHAL_LOG_LEVEL_INFO
-D CONFIG_ARDUHAL_LOG_COLORS=1
upload_port = COM5
debug_init_break = tbreak setup

View File

@ -338,7 +338,7 @@ void Lights_Control_Task(void *parameters){
COLOR_PACK colorPack;
CRGBPalette16 firePalette;
ESP_LOGD(tag, "Lights Control Task Entered...");
ESP_LOGD(tag, "Lights Control Task Entered & Suspended...");
vTaskSuspend(NULL);
ESP_LOGD(tag, "Lights Control Task Resumed...");
vTaskDelay(1000);
@ -349,7 +349,7 @@ void Lights_Control_Task(void *parameters){
while (true) {
if (xQueueReceive(animationQueue, &AnimEvent, portMAX_DELAY) == pdTRUE) {
ESP_LOGD(tag, "New Animation Event: Index: %d", AnimEvent.AnimationIndex);
ESP_LOGI(tag, "New Animation Event: Index: %d", AnimEvent.AnimationIndex);
switch (AnimEvent.AnimationIndex) {
case -3: // Set Pixel by index
if (AnimEvent.data.data[7] >= 0 && AnimEvent.data.data[7] < ledSettings[0].size) {
@ -460,7 +460,7 @@ void Lights_Control_Task(void *parameters){
Anim_GradientRotate(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, colorPack, 50);
break;
default:
ESP_LOGD(tag, "Loop default");
ESP_LOGW(tag, "Loop default");
break;
}

View File

@ -24,10 +24,10 @@ struct updateStatus {
byte wifiIP[4] = {0, 0, 0, 0};
byte currVersion[3] = {FIRMWARE_VERSION_MAJOR, FIRMWARE_VERSION_MINOR, FIRMWARE_VERSION_PATCH};
byte newVersion[3] = {0, 0, 0};
char wifiSSID[20] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
}updatePacket;
// Class for handling characteristic events
class UpgradeChar_Callbacks : public NimBLECharacteristicCallbacks {
@ -64,6 +64,7 @@ class UpgradeChar_Callbacks : public NimBLECharacteristicCallbacks {
updatePacket.wifiStatus = WIFI_DISCONNECTED;
updatePacket.wifiOnline = false;
updatePacket.wifiIP[0] = updatePacket.wifiIP[1] = updatePacket.wifiIP[2] = updatePacket.wifiIP[3] = 0;
strncpy(updatePacket.wifiSSID, ssid.c_str(), sizeof(updatePacket.wifiSSID) - 1);
} else {
ESP_LOGW(tag, "Failed to start WiFi connection task");
}
@ -97,12 +98,15 @@ class UpgradeChar_Callbacks : public NimBLECharacteristicCallbacks {
updatePacket.wifiIP[1] = ip[1];
updatePacket.wifiIP[2] = ip[2];
updatePacket.wifiIP[3] = ip[3];
strncpy(updatePacket.wifiSSID, WiFi.SSID().c_str(), sizeof(updatePacket.wifiSSID) - 1);
ESP_LOGI(tag, "WiFi packet: SSID='%s'", updatePacket.wifiSSID);
} else {
updatePacket.wifiStatus = WIFI_DISCONNECTED;
updatePacket.wifiIP[0] = 0;
updatePacket.wifiIP[1] = 0;
updatePacket.wifiIP[2] = 0;
updatePacket.wifiIP[3] = 0;
memset(updatePacket.wifiSSID, 0, sizeof(updatePacket.wifiSSID));
}
//update version

View File

@ -13,14 +13,14 @@ void Init_File_System(void){
}
// Get all information of your LITTLEFS
ESP_LOGD(tag, "File system info.");
ESP_LOGD(tag, "Total: %d, Used: %d, Free:%d", LittleFS.totalBytes(), LittleFS.usedBytes(), LittleFS.totalBytes() - LittleFS.usedBytes());
ESP_LOGI(tag, "File system info.");
ESP_LOGI(tag, "Total: %d, Used: %d, Free:%d", LittleFS.totalBytes(), LittleFS.usedBytes(), LittleFS.totalBytes() - LittleFS.usedBytes());
// Open dir folder
File dir = LittleFS.open("/");
// Cycle all the content
printAllFiles();
//printAllSystemFiles();
}
void getAllDirectories(String (&directoryList)[MAX_DIRECTORIES], int &count){
@ -52,7 +52,7 @@ void getAllDirectories(String (&directoryList)[MAX_DIRECTORIES], int &count){
void printFilesInDirectories(const String directoryList[], int count){
for (int i = 0; i < count; i++){
ESP_LOGD(tag, "Dir: %s", directoryList[i].c_str());
ESP_LOGI(tag, "Dir: %s", directoryList[i].c_str());
File dir = LittleFS.open(directoryList[i]);
if (!dir || !dir.isDirectory()){
@ -63,7 +63,7 @@ void printFilesInDirectories(const String directoryList[], int count){
File file = dir.openNextFile();
while (file){
if (!file.isDirectory()){
ESP_LOGD(tag, " File: %s", file.name());
ESP_LOGI(tag, " File: %s", file.name());
}
file = dir.openNextFile();
}
@ -72,13 +72,13 @@ void printFilesInDirectories(const String directoryList[], int count){
}
}
void printAllFiles(void){
void printAllSystemFiles(void){
String directories[MAX_DIRECTORIES];
int dirCount = 0;
getAllDirectories(directories, dirCount);
ESP_LOGD(tag, "File System Listing:");
ESP_LOGI(tag, "File System Listing:");
printFilesInDirectories(directories, dirCount);
}

View File

@ -20,7 +20,7 @@ void getAllDirectories(String (&directoryList)[MAX_DIRECTORIES], int& count);
void printFilesInDirectories(const String directoryList[], int count);
//void printAllFiles(int maxDirs);
void printAllFiles(void);
void printAllSystemFiles(void);

View File

@ -119,6 +119,11 @@ void setup()
// Init LittleFS
Init_File_System();
// Print all system files
if (digitalRead(sys_settings.boardPins.btn[0]) == LOW){
printAllSystemFiles();
}
String board_file_path, booth_file_path;
Get_Board_and_Booth_File_Paths("/system/system.json", board_file_path, booth_file_path);
@ -166,14 +171,14 @@ void setup()
{
setStatusPin1(true);
UpgradeMode = true;
ESP_LOGE(tag, "Enabling BLE and Update Service");
ESP_LOGW(tag, "Enabling BLE and Update Service");
Init_BleServer(true, true);
ESP_LOGW(tag, "Enabling Wifi AP and Client");
Wifi_Init();
}
else
{
ESP_LOGE(tag, "Enabling BLE, No Update Service");
ESP_LOGI(tag, "Enabling BLE, No Update Service");
Init_BleServer(true, false); // Dont start the Upgrade service
}
@ -308,7 +313,7 @@ void loop()
if(UpgradeMode){
ON_EVERY_N_MILLISECONDS(5000)
{
Buzzer_Play_Tune(TUNE_THUMP, true, true);
Buzzer_Play_Tune(TUNE_ACK, true, true);
}
}

View File

@ -11,25 +11,25 @@ void Init_ButtonEvents(int8_t (&pin)[3]){
if (pin[0] >= 0) {
if (boardButtons[0] == nullptr) {
boardButtons[0] = new OneButton(pin[0], true, true);
ESP_LOGD(tag, "Button1 Events, pin=%d", pin[0]);
ESP_LOGI(tag, "Button1 Events, pin=%d", pin[0]);
} else {
ESP_LOGD(tag, "Button1 already initialized (pin=%d)", pin[0]);
ESP_LOGW(tag, "Button1 already initialized (pin=%d)", pin[0]);
}
}
if (pin[1] >= 0) {
if (boardButtons[1] == nullptr) {
boardButtons[1] = new OneButton(pin[1], true, true);
ESP_LOGD(tag, "Button2 Events, pin=%d", pin[1]);
ESP_LOGI(tag, "Button2 Events, pin=%d", pin[1]);
} else {
ESP_LOGD(tag, "Button2 already initialized (pin=%d)", pin[1]);
ESP_LOGW(tag, "Button2 already initialized (pin=%d)", pin[1]);
}
}
if (pin[2] >= 0) {
if (boardButtons[2] == nullptr) {
boardButtons[2] = new OneButton(pin[2], true, false);
ESP_LOGD(tag, "Button3 Events, pin=%d", pin[2]);
ESP_LOGI(tag, "Button3 Events, pin=%d", pin[2]);
} else {
ESP_LOGD(tag, "Button3 already initialized (pin=%d)", pin[2]);
ESP_LOGW(tag, "Button3 already initialized (pin=%d)", pin[2]);
}
}
@ -66,10 +66,10 @@ void Init_ButtonEvents(int8_t (&pin)[3]){
boardButtons[2]->attachLongPressStart(btn3_LongPressStart);
boardButtons[2]->attachLongPressStop(btn3_LongPressStop);
boardButtons[2]->attachDuringLongPress(btn3_DuringLongPress);
ESP_LOGD(tag, "Button3 Events Initialized");
ESP_LOGI(tag, "Button3 Events Initialized");
}
else {
ESP_LOGD(tag, "Button3 Not Initialized");
ESP_LOGW(tag, "Button3 Not Initialized");
}
}

View File

@ -27,7 +27,7 @@ void Init_Buzzer(int8_t pin, const char* configFile)
}
Buzzer_Load_Tunes(configFile); // Load Tunes
ESP_LOGD(tag, "Buzzer initialized..");
ESP_LOGI(tag, "Buzzer initialized..");
}
void Buzzer_Play_Tune(TUNE_TYPE tune, bool async, bool hasPriority)

View File

@ -61,27 +61,27 @@ TI_TMP102_Compatible *tSensor = nullptr;
if ((FanState == 2) && (temperature < (sp2 - hyst))) {
newDuty = fp1;
FanState = 1;
//ESP_LOGD(tag, "Dropping down to FanPower1");
ESP_LOGD(tag, "Dropping down to FanPower1");
}
else if ((FanState == 1) && (temperature < (sp1 - hyst))) {
newDuty = 0;
FanState = 0;
//ESP_LOGD(tag, "Dropping down to FanPower0");
ESP_LOGD(tag, "Dropping down to FanPower0");
}
else if ((FanState <= 1) && (temperature > sp1)) {
newDuty = fp1;
if (temperature > sp2) {
newDuty = fp2;
FanState = 2;
//ESP_LOGD(tag, "Raising up to FanPower2");
} //else {
//ESP_LOGD(tag, "Raising up to FanPower1");
//}
ESP_LOGD(tag, "Raising up to FanPower2");
} else {
ESP_LOGD(tag, "Raising up to FanPower1");
}
}
// Apply new duty cycle if changed
if (currentDuty != newDuty) {
pwmOut->setOutput(newDuty);
ESP_LOGD(tag, "Board T: %.2f F, Fan -> %.2f (state=%u)", temperature, newDuty, FanState);
ESP_LOGI(tag, "Board T: %.2f F, Fan -> %.2f (state=%u)", temperature, newDuty, FanState);
}
}

View File

@ -102,7 +102,7 @@ void Wifi_Init()
WiFi.onEvent(onWiFiEvent);
WiFi.setHostname(mDnsName.c_str());
webServer.begin();
ESP_LOGD(tag, "AP started with IP: %s", WiFi.softAPIP().toString().c_str());
ESP_LOGI(tag, "AP started with IP: %s", WiFi.softAPIP().toString().c_str());
StartWifiConnectTask(client_ssid, client_pass);
@ -123,12 +123,12 @@ bool StartWifiConnectTask(String ssid = "", String pass = "")
client_pass = pass;
if (Wifi_Task_Handle == NULL)
{
ESP_LOGD(tag, "Creating WiFi task");
ESP_LOGI(tag, "Creating WiFi task");
xTaskCreatePinnedToCore(Wifi_ConnectTask, "Wifi_Task", 1024 * 4, NULL, 1, &Wifi_Task_Handle, 0);
}
else
{
ESP_LOGD(tag, "WiFi task already running");
ESP_LOGI(tag, "WiFi task already running");
}
return true;
@ -148,7 +148,7 @@ void Wifi_ConnectTask(void *parameter)
if (WiFi.status() != WL_CONNECTED || client_ssid != WiFi.SSID())
{
ESP_LOGD(tag, "Connecting to: %s", client_ssid.c_str());
ESP_LOGI(tag, "Connecting to: %s", client_ssid.c_str());
// Disconnect and connect to new network
WiFi.disconnect(true);
@ -168,7 +168,7 @@ void Wifi_ConnectTask(void *parameter)
ESP_LOGW(tag, "Connection failed - check password");
break;
default:
ESP_LOGD(tag, "Connecting... (%d/%d)", attempts + 1, MAX_ATTEMPTS);
ESP_LOGI(tag, "Connecting... (%d/%d)", attempts + 1, MAX_ATTEMPTS);
}
vTaskDelay(pdMS_TO_TICKS(ATTEMPT_DELAY_MS));
attempts++;
@ -383,7 +383,7 @@ void Setup_WebServer_Handlers(AsyncWebServer &server)
// Start connection
StartWifiConnectTask(ssid, pass);
ESP_LOGD(tag, "Starting connection to %s", client_ssid.c_str());
ESP_LOGI(tag, "Starting connection to %s", client_ssid.c_str());
request->send(200, "application/json", "{\"status\":\"connecting\"}"); });
server.on("/wifi/status", HTTP_GET, [](AsyncWebServerRequest *request)
{
@ -420,7 +420,7 @@ void Setup_WebServer_Handlers(AsyncWebServer &server)
try {
String filename = uriDecode(request->getParam("file")->value());
ESP_LOGD(tag, "Download request for: %s", filename.c_str());
ESP_LOGI(tag, "Download request for: %s", filename.c_str());
request->send(LittleFS, filename, "application/octet-stream");
}
catch (const std::exception& e) {
@ -490,7 +490,7 @@ void Setup_WebServer_Handlers(AsyncWebServer &server)
// Get and decode filename
String fileName = uriDecode(request->getParam(param_edit_path)->value());
ESP_LOGD(tag, "Edit request for file: %s", fileName.c_str());
ESP_LOGI(tag, "Edit request for file: %s", fileName.c_str());
// Set save path
savePath = (fileName == "new") ? "/new.txt" : fileName;
@ -500,7 +500,7 @@ void Setup_WebServer_Handlers(AsyncWebServer &server)
savePath = "/" + savePath;
}
ESP_LOGD(tag, "Save path set to: %s", savePath.c_str());
ESP_LOGI(tag, "Save path set to: %s", savePath.c_str());
try {
sendHtmlFile("/www/edit.html", request, fileManagerHtmlProcessor);
@ -651,7 +651,7 @@ void Setup_WebServer_Handlers(AsyncWebServer &server)
contentType = "application/octet-stream";
}
ESP_LOGD(tag, "Sending file: %s (%s)", filePath.c_str(), contentType);
ESP_LOGI(tag, "Sending file: %s (%s)", filePath.c_str(), contentType);
request->send(LittleFS, filePath, contentType);
}
catch (const std::runtime_error &e)
@ -688,7 +688,7 @@ void handleFilesUpload_OnBody(AsyncWebServerRequest *request, String filename, s
AsyncWebParameter *p = request->getParam("dir-path", true, false);
String path = p->value() + "/" + filename;
ESP_LOGD(tag, "Starting upload: %s", path.c_str());
ESP_LOGI(tag, "Starting upload: %s", path.c_str());
// Validate path
if (!path.startsWith("/"))
@ -729,7 +729,7 @@ void handleFilesUpload_OnBody(AsyncWebServerRequest *request, String filename, s
if (final)
{
request->_tempFile.close();
ESP_LOGD(tag, "Upload complete: %s, %u bytes", filename.c_str(), index + len);
ESP_LOGI(tag, "Upload complete: %s, %u bytes", filename.c_str(), index + len);
request->redirect("/files");
}
}
@ -750,7 +750,7 @@ void sendHtmlFile(const char *filePath, AsyncWebServerRequest *request, String (
String processedData = varReplace(htmlFile, callback);
delete[] htmlFile; // Clean up allocated memory
ESP_LOGD(tag, "Sent file: %s", filePath);
ESP_LOGI(tag, "Sent file: %s", filePath);
request->send(200, "text/html", processedData);
}
catch (const std::exception &e)
@ -773,11 +773,11 @@ const char *getFileExtension(const char *filename)
const char *lastDot = strrchr(filename, '.');
if (!lastDot || lastDot == filename || *(lastDot + 1) == '\0')
{
ESP_LOGD(tag, "No valid extension found in: %s", filename);
ESP_LOGI(tag, "No valid extension found in: %s", filename);
return "";
}
ESP_LOGD(tag, "Found extension: %s", lastDot + 1);
ESP_LOGI(tag, "Found extension: %s", lastDot + 1);
return lastDot + 1;
}
@ -786,7 +786,7 @@ const char *getFileType(const char *ext)
if (!ext)
return "application/octet-stream";
ESP_LOGD(tag, "Getting file type for extension: %s", ext);
ESP_LOGI(tag, "Getting file type for extension: %s", ext);
if (strcmp(ext, "png") == 0)
return "image/png";
@ -915,92 +915,92 @@ void onWiFiEvent(WiFiEvent_t event)
switch (event)
{
case ARDUINO_EVENT_WIFI_READY:
ESP_LOGD(tag, "WiFi interface ready");
ESP_LOGI(tag, "WiFi interface ready");
break;
case ARDUINO_EVENT_WIFI_SCAN_DONE:
ESP_LOGD(tag, "Completed scan for access points");
ESP_LOGI(tag, "Completed scan for access points");
break;
case ARDUINO_EVENT_WIFI_STA_START:
ESP_LOGD(tag, "WiFi client started");
ESP_LOGI(tag, "WiFi client started");
break;
case ARDUINO_EVENT_WIFI_STA_STOP:
ESP_LOGD(tag, "WiFi clients stopped");
ESP_LOGI(tag, "WiFi clients stopped");
break;
case ARDUINO_EVENT_WIFI_STA_CONNECTED:
ESP_LOGD(tag, "Connected to AP");
ESP_LOGI(tag, "Connected to AP");
break;
case ARDUINO_EVENT_WIFI_STA_DISCONNECTED:
ESP_LOGD(tag, "WiFi Disconnected");
ESP_LOGI(tag, "WiFi Disconnected");
setStatusPin1(false);
Buzzer_Play_Tune(TUNE_DISCONNECTED);
break;
case ARDUINO_EVENT_WIFI_STA_AUTHMODE_CHANGE:
ESP_LOGD(tag, "Authentication mode of access point has changed");
ESP_LOGI(tag, "Authentication mode of access point has changed");
break;
case ARDUINO_EVENT_WIFI_STA_GOT_IP:
ESP_LOGD(tag, "My IP: %s", WiFi.localIP().toString());
ESP_LOGI(tag, "My IP: %s", WiFi.localIP().toString());
// Wifi_Start_MDNS();
setStatusPin1(true);
Buzzer_Play_Tune(TUNE_CONNECTED);
break;
case ARDUINO_EVENT_WIFI_STA_LOST_IP:
ESP_LOGD(tag, "Lost IP address and IP address is reset to 0");
ESP_LOGI(tag, "Lost IP address and IP address is reset to 0");
break;
case ARDUINO_EVENT_WPS_ER_SUCCESS:
ESP_LOGD(tag, "WiFi Protected Setup (WPS): succeeded in enrollee mode");
ESP_LOGI(tag, "WiFi Protected Setup (WPS): succeeded in enrollee mode");
break;
case ARDUINO_EVENT_WPS_ER_FAILED:
ESP_LOGD(tag, "WiFi Protected Setup (WPS): failed in enrollee mode");
ESP_LOGI(tag, "WiFi Protected Setup (WPS): failed in enrollee mode");
break;
case ARDUINO_EVENT_WPS_ER_TIMEOUT:
ESP_LOGD(tag, "WiFi Protected Setup (WPS): timeout in enrollee mode");
ESP_LOGI(tag, "WiFi Protected Setup (WPS): timeout in enrollee mode");
break;
case ARDUINO_EVENT_WPS_ER_PIN:
ESP_LOGD(tag, "WiFi Protected Setup (WPS): pin code in enrollee mode");
ESP_LOGI(tag, "WiFi Protected Setup (WPS): pin code in enrollee mode");
break;
case ARDUINO_EVENT_WIFI_AP_START:
ESP_LOGD(tag, "WiFi access point started");
ESP_LOGI(tag, "WiFi access point started");
break;
case ARDUINO_EVENT_WIFI_AP_STOP:
ESP_LOGD(tag, "WiFi access point stopped");
ESP_LOGI(tag, "WiFi access point stopped");
break;
case ARDUINO_EVENT_WIFI_AP_STACONNECTED:
ESP_LOGD(tag, "Client connected");
ESP_LOGI(tag, "Client connected");
break;
case ARDUINO_EVENT_WIFI_AP_STADISCONNECTED:
ESP_LOGD(tag, "SoftAP Client Disconnected");
ESP_LOGI(tag, "SoftAP Client Disconnected");
Buzzer_Play_Tune(TUNE_DISCONNECTED);
break;
case ARDUINO_EVENT_WIFI_AP_STAIPASSIGNED:
ESP_LOGD(tag, "SoftAP Client Connected");
ESP_LOGI(tag, "SoftAP Client Connected");
Buzzer_Play_Tune(TUNE_CONNECTED);
break;
case ARDUINO_EVENT_WIFI_AP_PROBEREQRECVED:
ESP_LOGD(tag, "Received probe request");
ESP_LOGI(tag, "Received probe request");
break;
case ARDUINO_EVENT_WIFI_AP_GOT_IP6:
ESP_LOGD(tag, "AP IPv6 is preferred");
ESP_LOGI(tag, "AP IPv6 is preferred");
break;
case ARDUINO_EVENT_WIFI_STA_GOT_IP6:
ESP_LOGD(tag, "STA IPv6 is preferred");
ESP_LOGI(tag, "STA IPv6 is preferred");
break;
case ARDUINO_EVENT_ETH_GOT_IP6:
ESP_LOGD(tag, "Ethernet IPv6 is preferred");
ESP_LOGI(tag, "Ethernet IPv6 is preferred");
break;
case ARDUINO_EVENT_ETH_START:
ESP_LOGD(tag, "Ethernet started");
ESP_LOGI(tag, "Ethernet started");
break;
case ARDUINO_EVENT_ETH_STOP:
ESP_LOGD(tag, "Ethernet stopped");
ESP_LOGI(tag, "Ethernet stopped");
break;
case ARDUINO_EVENT_ETH_CONNECTED:
ESP_LOGD(tag, "Ethernet connected");
ESP_LOGI(tag, "Ethernet connected");
break;
case ARDUINO_EVENT_ETH_DISCONNECTED:
ESP_LOGD(tag, "Ethernet disconnected");
ESP_LOGI(tag, "Ethernet disconnected");
break;
case ARDUINO_EVENT_ETH_GOT_IP:
ESP_LOGD(tag, "Obtained IP address");
ESP_LOGI(tag, "Obtained IP address");
break;
default:
break;
@ -1102,7 +1102,7 @@ bool writeFile(fs::FS &fs, const char *path, const char *message)
return false;
}
ESP_LOGD(tag, "Successfully wrote %u bytes to %s", (unsigned)totalSize, finalPath.c_str());
ESP_LOGI(tag, "Successfully wrote %u bytes to %s", (unsigned)totalSize, finalPath.c_str());
return true;
}
@ -1157,7 +1157,7 @@ char *readFile(fs::FS &fs, const char *path)
// Null terminate
fileContent[bytesRead] = '\0';
ESP_LOGD(tag, "Successfully read %u bytes from %s", bytesRead, path);
ESP_LOGI(tag, "Successfully read %u bytes from %s", bytesRead, path);
return fileContent;
}

190
update.json Normal file
View File

@ -0,0 +1,190 @@
{
"files": [
{
"remote": "data/ata-boothifier-upgrade.html",
"local": "/ata-boothifier-upgrade.html",
"md5": "92074ec24fd467545272eb9e837d644c",
"size": 16980
},
{
"remote": "data/ata-boothifier-upgradeV2.html",
"local": "/ata-boothifier-upgradeV2.html",
"md5": "484648993370e4b6aff13124bd1c55c1",
"size": 18178
},
{
"remote": "data/ata-boothifier-upgradeV3.html",
"local": "/ata-boothifier-upgradeV3.html",
"md5": "79426145db379ac15ccc742245a31ecb",
"size": 17233
},
{
"remote": "data/favicon.ico",
"local": "/favicon.ico",
"md5": "ba4c4e3bf5e5db2bbfc56a52f3657d79",
"size": 1150
},
{
"remote": "data/flashstik-reg.html",
"local": "/flashstik-reg.html",
"md5": "394fdfe3cd3fb89a8ddb3d6edf2d2da4",
"size": 15651
},
{
"remote": "data/css/global-style.css",
"local": "/css/global-style.css",
"md5": "217a9cca8b4eae2d28fa8bc5a0f6db09",
"size": 1239
},
{
"remote": "data/css/nav.css",
"local": "/css/nav.css",
"md5": "e653a75433056f28e3f410f567b8622c",
"size": 1538
},
{
"remote": "data/images/atalogo.png",
"local": "/images/atalogo.png",
"md5": "5a18c88a4ea80c8d8d0ad52b2fdbbadc",
"size": 16690
},
{
"remote": "data/images/favicon-32x32.png",
"local": "/images/favicon-32x32.png",
"md5": "d80cf74ace3a8be487a5158bca48f9b6",
"size": 2428
},
{
"remote": "data/js/event-box.js",
"local": "/js/event-box.js",
"md5": "553f26707686038e275227a97eb96659",
"size": 12341
},
{
"remote": "data/js/fwUoload.js",
"local": "/js/fwUoload.js",
"md5": "d4a734cce529c3adf831ea46259f9c2d",
"size": 1524
},
{
"remote": "data/js/hue-select.js",
"local": "/js/hue-select.js",
"md5": "0a58f1a339af5c54aecfc5ce1aac7168",
"size": 6367
},
{
"remote": "data/js/jquery-3.7.1.js",
"local": "/js/jquery-3.7.1.js",
"md5": "fdb81281d3773a7462998fdddbe6f5bf",
"size": 196885
},
{
"remote": "data/system/ble.json",
"local": "/system/ble.json",
"md5": "faebefd5b50046a01d4a8aa27f8504d0",
"size": 307
},
{
"remote": "data/system/readme.txt",
"local": "/system/readme.txt",
"md5": "198c92a2cc06b3effce3b5ba313cc6a0",
"size": 86
},
{
"remote": "data/system/tunes.json",
"local": "/system/tunes.json",
"md5": "814999e88296bee179cef5d394f3f696",
"size": 1149
},
{
"remote": "data/system/update.json",
"local": "/system/update.json",
"md5": "01cc6a1935601085df49308cab646673",
"size": 156
},
{
"remote": "data/www/about.html",
"local": "/www/about.html",
"md5": "23f0c991c5c67e7eefe6c24aa50b59fb",
"size": 4222
},
{
"remote": "data/www/edit.html",
"local": "/www/edit.html",
"md5": "fed238dcf87d3797b08ed371330ea800",
"size": 5546
},
{
"remote": "data/www/edit_old.html",
"local": "/www/edit_old.html",
"md5": "9aafba533ac77352d30841cdc34db0c4",
"size": 4240
},
{
"remote": "data/www/failed.html",
"local": "/www/failed.html",
"md5": "a24025d56bef1cd2ed5375f6e8853dde",
"size": 828
},
{
"remote": "data/www/files.html",
"local": "/www/files.html",
"md5": "52d82d4d23b038929691cd0fb20e8ee0",
"size": 9369
},
{
"remote": "data/www/home.html",
"local": "/www/home.html",
"md5": "767fd73b733d8e37dc79252fcd20b610",
"size": 7822
},
{
"remote": "data/www/index.html",
"local": "/www/index.html",
"md5": "af690aaa4dec02691ffdb2765c837370",
"size": 806
},
{
"remote": "data/www/lights.html",
"local": "/www/lights.html",
"md5": "272f8dc423923bb5026837240f654efe",
"size": 19056
},
{
"remote": "data/www/navbar.html",
"local": "/www/navbar.html",
"md5": "89af40b990e297e41e116105b04b66ee",
"size": 533
},
{
"remote": "data/www/ok.html",
"local": "/www/ok.html",
"md5": "db42bd2ff52293c3b4d6ef62fb4e3a42",
"size": 865
},
{
"remote": "data/www/setup.html",
"local": "/www/setup.html",
"md5": "19e1f419844852800757ccd36bb7892a",
"size": 11349
},
{
"remote": "data/www/upgrade.html",
"local": "/www/upgrade.html",
"md5": "f4a3efb67be66b5214d905e605984c93",
"size": 9080
},
{
"remote": "data/www/wifi.html",
"local": "/www/wifi.html",
"md5": "c6e55946a02cb0ff28689dd10d265987",
"size": 5320
}
],
"firmware": {
"md5": "fcec6e659842cc0333880b701a3e2634",
"size": 1401712
},
"release_date": "2025-08-20",
"release_time": "08:53:05"
}