minor fixes
This commit is contained in:
parent
d5dcf6c0fe
commit
1d661e41ad
@ -162,18 +162,26 @@
|
|||||||
<label id="status-new-version">New Version: ...</label>
|
<label id="status-new-version">New Version: ...</label>
|
||||||
</div>
|
</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 -->
|
<!-- Buttons -->
|
||||||
<div class="btn-container">
|
<div class="btn-container">
|
||||||
<button id="bleConnectBtn">Connect</button>
|
<button id="bleConnectBtn" onclick="connectToBle()">Connect</button>
|
||||||
<button id="checkStatusBtn" disabled>Check Status</button>
|
<button id="checkStatusBtn" onclick="checkStatus()" disabled>Check Status</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Log Area -->
|
<!-- Log Area -->
|
||||||
<textarea id="logArea" readonly></textarea>
|
<textarea id="logArea" readonly></textarea>
|
||||||
|
|
||||||
<div class="btn-container">
|
<div class="btn-container">
|
||||||
<button id="checkVersionBtn" disabled>Check Version</button>
|
<button id="checkVersionBtn" onclick="checkVersion()" disabled>Check Version</button>
|
||||||
<button id="startUpgradeBtn" disabled>Start Update</button>
|
<button id="startUpgradeBtn" onclick="startUpgrade()" disabled>Start Update</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Wi-Fi Input Fields -->
|
<!-- Wi-Fi Input Fields -->
|
||||||
@ -181,14 +189,14 @@
|
|||||||
<input type="text" id="wifissid" name="wifissid" placeholder="Enter WiFi SSID" required>
|
<input type="text" id="wifissid" name="wifissid" placeholder="Enter WiFi SSID" required>
|
||||||
<input type="password" id="wifipassword" name="wifipassword" placeholder="Enter WiFi Password" required>
|
<input type="password" id="wifipassword" name="wifipassword" placeholder="Enter WiFi Password" required>
|
||||||
<div style="display: flex; align-items: center; gap: 5px;">
|
<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>
|
<label for="showPassword">Show Password</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Added margin-top above this button -->
|
<!-- Added margin-top above this button -->
|
||||||
<div class="btn-container wifi">
|
<div class="btn-container wifi">
|
||||||
<button id="wifiConnectBtn" disabled>Connect Wifi</button>
|
<button id="wifiConnectBtn" onclick="wifiConnect()" disabled>Connect Wifi</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
@ -216,6 +224,14 @@
|
|||||||
newVersion: [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){
|
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; }
|
for(let i=0;i<3;i++){ if(a[i] > b[i]) return 1; if(a[i] < b[i]) return -1; }
|
||||||
return 0;
|
return 0;
|
||||||
@ -255,7 +271,7 @@
|
|||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
bleDevice = await navigator.bluetooth.requestDevice({
|
bleDevice = await navigator.bluetooth.requestDevice({
|
||||||
filters: [{ name: BLE_SERVER_NAME }],
|
filters: [{ name: document.getElementById('input-DeviceName').value }],
|
||||||
optionalServices: [BLE_SERVICE_UUID]
|
optionalServices: [BLE_SERVICE_UUID]
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -288,9 +304,9 @@
|
|||||||
logMessage('--> ' + decodedValue);
|
logMessage('--> ' + decodedValue);
|
||||||
});
|
});
|
||||||
|
|
||||||
bleConnected = true;
|
bleConnected = true;
|
||||||
// Auto-reconnect / state reset handler
|
// Auto-reconnect / state reset handler
|
||||||
bleDevice.addEventListener('gattserverdisconnected', handleDisconnect);
|
bleDevice.addEventListener('gattserverdisconnected', handleDisconnect);
|
||||||
document.getElementById('bleConnectBtn').disabled = true;
|
document.getElementById('bleConnectBtn').disabled = true;
|
||||||
document.querySelector('.status-indicator-ble').style.backgroundColor = 'green';
|
document.querySelector('.status-indicator-ble').style.backgroundColor = 'green';
|
||||||
document.getElementById('status-ble-connection').textContent ="BLE Status: Connected";
|
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(){
|
function handleDisconnect(){
|
||||||
bleConnected = false;
|
bleConnected = false;
|
||||||
document.querySelector('.status-indicator-ble').style.backgroundColor = 'gray';
|
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('status-new-version').textContent = 'New Version: ' + packet.newVersion.join('.');
|
||||||
document.getElementById('checkVersionBtn').disabled = true;
|
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
|
//enable start upgrade button
|
||||||
logMessage("New Version Available:");
|
logMessage("New Version: Available");
|
||||||
document.getElementById('startUpgradeBtn').disabled = false;
|
document.getElementById('startUpgradeBtn').disabled = false;
|
||||||
} else {
|
} else {
|
||||||
//disable start upgrade button
|
//disable start upgrade button
|
||||||
@ -461,68 +533,12 @@
|
|||||||
processUpdatePacket(updatePacket);
|
processUpdatePacket(updatePacket);
|
||||||
}
|
}
|
||||||
|
|
||||||
document.getElementById('showPassword').addEventListener('change', function() {
|
// Event listeners for buttons
|
||||||
|
function togglePasswordVisibility() {
|
||||||
const passwordInput = document.getElementById('wifipassword');
|
const passwordInput = document.getElementById('wifipassword');
|
||||||
passwordInput.type = this.checked ? 'text' : 'password';
|
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
|
// Initial call to process the update packet with defaults
|
||||||
processUpdatePacket(updatePacket);
|
processUpdatePacket(updatePacket);
|
||||||
})();
|
})();
|
||||||
476
data/ata-boothifier-upgradeV3.html
Normal file
476
data/ata-boothifier-upgradeV3.html
Normal 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>
|
||||||
202
firmware_update/GenUpdateV2.py
Normal file
202
firmware_update/GenUpdateV2.py
Normal 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()
|
||||||
337
firmware_update/UploadToMinioV2.py
Normal file
337
firmware_update/UploadToMinioV2.py
Normal 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()
|
||||||
@ -400,18 +400,17 @@
|
|||||||
|
|
||||||
if (packet.newVersion[0] > 0) {
|
if (packet.newVersion[0] > 0) {
|
||||||
document.getElementById('status-new-version').textContent = 'New Version: ' + packet.newVersion.join('.');
|
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] ||
|
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[0] === packet.currVersion[0] && packet.newVersion[1] === packet.currVersion[1] && packet.newVersion[2] > packet.currVersion[2])) {
|
(packet.newVersion[0] === packet.currVersion[0] && packet.newVersion[1] === packet.currVersion[1] && packet.newVersion[2] > packet.currVersion[2])) {
|
||||||
|
|
||||||
//enable start upgrade button
|
//enable start upgrade button
|
||||||
logMessage("New Version Available:");
|
logMessage("New Version Available:");
|
||||||
document.getElementById('checkVersionBtn').disabled = true;
|
|
||||||
document.getElementById('startUpgradeBtn').disabled = false;
|
document.getElementById('startUpgradeBtn').disabled = false;
|
||||||
} else {
|
} else {
|
||||||
//disable start upgrade button
|
//disable start upgrade button
|
||||||
document.getElementById('checkVersionBtn').disabled = false;
|
|
||||||
document.getElementById('startUpgradeBtn').disabled = true;
|
document.getElementById('startUpgradeBtn').disabled = true;
|
||||||
logMessage("New Version: Not Available");
|
logMessage("New Version: Not Available");
|
||||||
}
|
}
|
||||||
@ -458,10 +457,12 @@
|
|||||||
document.getElementById('checkVersionBtn').addEventListener('click', async () => {
|
document.getElementById('checkVersionBtn').addEventListener('click', async () => {
|
||||||
await sendPacket('version-check');
|
await sendPacket('version-check');
|
||||||
// loop and monitor the the updatePacket.newVersion
|
// loop and monitor the the updatePacket.newVersion
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 2000));
|
||||||
success = false;
|
success = false;
|
||||||
for (let i = 0; i < 20; i++) {
|
for (let i = 0; i < 20; i++) {
|
||||||
await readPacket();
|
await readPacket();
|
||||||
if (updatePacket.newVersion[0] > 0) {
|
if (updatePacket.newVersion[0] > 0) {
|
||||||
|
success = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -492,9 +493,15 @@
|
|||||||
|
|
||||||
});
|
});
|
||||||
document.getElementById('startUpgradeBtn').addEventListener('click', async () => {
|
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);
|
processUpdatePacket(updatePacket);
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
547
firmware_update/latest/data/ata-boothifier-upgradeV2.html
Normal file
547
firmware_update/latest/data/ata-boothifier-upgradeV2.html
Normal 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>
|
||||||
476
firmware_update/latest/data/ata-boothifier-upgradeV3.html
Normal file
476
firmware_update/latest/data/ata-boothifier-upgradeV3.html
Normal 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>
|
||||||
472
firmware_update/latest/data/flashstik-reg.html
Normal file
472
firmware_update/latest/data/flashstik-reg.html
Normal 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>
|
||||||
9
firmware_update/latest/data/system/ble.json
Normal file
9
firmware_update/latest/data/system/ble.json
Normal 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"
|
||||||
|
}
|
||||||
5
firmware_update/latest/data/system/readme.txt
Normal file
5
firmware_update/latest/data/system/readme.txt
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
|
||||||
|
#Setting up /system/system.json
|
||||||
|
1 - Choose the
|
||||||
|
modes:
|
||||||
|
booth, roamer, stick
|
||||||
@ -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/"
|
||||||
}
|
}
|
||||||
@ -2,25 +2,38 @@
|
|||||||
"version": {
|
"version": {
|
||||||
"major": 1,
|
"major": 1,
|
||||||
"minor": 4,
|
"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.",
|
"description": "This is a firmware update.",
|
||||||
"changelog": [
|
"changelog": [
|
||||||
"Fixed issue with device connectivity.",
|
"...",
|
||||||
"Improved firmware update process."
|
"..."
|
||||||
],
|
],
|
||||||
"firmware": {
|
"firmware": {
|
||||||
"file": "firmware.bin",
|
"file": "firmware.bin",
|
||||||
"md5": "b8c880418a180efb23260ee093d13e61",
|
"md5": "fcec6e659842cc0333880b701a3e2634",
|
||||||
"size": 1409568
|
"size": 1401712
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
{
|
{
|
||||||
"remote": "data/ata-boothifier-upgrade.html",
|
"remote": "data/ata-boothifier-upgrade.html",
|
||||||
"local": "/ata-boothifier-upgrade.html",
|
"local": "/ata-boothifier-upgrade.html",
|
||||||
"md5": "e452db020a5ad660599129ab35e01058",
|
"md5": "92074ec24fd467545272eb9e837d644c",
|
||||||
"size": 16736
|
"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",
|
"remote": "data/favicon.ico",
|
||||||
@ -28,6 +41,12 @@
|
|||||||
"md5": "ba4c4e3bf5e5db2bbfc56a52f3657d79",
|
"md5": "ba4c4e3bf5e5db2bbfc56a52f3657d79",
|
||||||
"size": 1150
|
"size": 1150
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"remote": "data/flashstik-reg.html",
|
||||||
|
"local": "/flashstik-reg.html",
|
||||||
|
"md5": "394fdfe3cd3fb89a8ddb3d6edf2d2da4",
|
||||||
|
"size": 15651
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"remote": "data/css/global-style.css",
|
"remote": "data/css/global-style.css",
|
||||||
"local": "/css/global-style.css",
|
"local": "/css/global-style.css",
|
||||||
@ -76,6 +95,18 @@
|
|||||||
"md5": "fdb81281d3773a7462998fdddbe6f5bf",
|
"md5": "fdb81281d3773a7462998fdddbe6f5bf",
|
||||||
"size": 196885
|
"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",
|
"remote": "data/system/tunes.json",
|
||||||
"local": "/system/tunes.json",
|
"local": "/system/tunes.json",
|
||||||
@ -85,8 +116,8 @@
|
|||||||
{
|
{
|
||||||
"remote": "data/system/update.json",
|
"remote": "data/system/update.json",
|
||||||
"local": "/system/update.json",
|
"local": "/system/update.json",
|
||||||
"md5": "8046dbf9490cfd88fe5970b4ec1ac2cd",
|
"md5": "01cc6a1935601085df49308cab646673",
|
||||||
"size": 91
|
"size": 156
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"remote": "data/www/about.html",
|
"remote": "data/www/about.html",
|
||||||
|
|||||||
@ -32,7 +32,8 @@ lib_deps =
|
|||||||
build_flags =
|
build_flags =
|
||||||
-mfix-esp32-psram-cache-issue
|
-mfix-esp32-psram-cache-issue
|
||||||
-D CONFIG_LOG_DYNAMIC_LEVEL_CONTROL=1
|
-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
|
-D CONFIG_ARDUHAL_LOG_COLORS=1
|
||||||
upload_port = COM5
|
upload_port = COM5
|
||||||
debug_init_break = tbreak setup
|
debug_init_break = tbreak setup
|
||||||
|
|||||||
@ -338,7 +338,7 @@ void Lights_Control_Task(void *parameters){
|
|||||||
COLOR_PACK colorPack;
|
COLOR_PACK colorPack;
|
||||||
CRGBPalette16 firePalette;
|
CRGBPalette16 firePalette;
|
||||||
|
|
||||||
ESP_LOGD(tag, "Lights Control Task Entered...");
|
ESP_LOGD(tag, "Lights Control Task Entered & Suspended...");
|
||||||
vTaskSuspend(NULL);
|
vTaskSuspend(NULL);
|
||||||
ESP_LOGD(tag, "Lights Control Task Resumed...");
|
ESP_LOGD(tag, "Lights Control Task Resumed...");
|
||||||
vTaskDelay(1000);
|
vTaskDelay(1000);
|
||||||
@ -349,7 +349,7 @@ void Lights_Control_Task(void *parameters){
|
|||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
if (xQueueReceive(animationQueue, &AnimEvent, portMAX_DELAY) == pdTRUE) {
|
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) {
|
switch (AnimEvent.AnimationIndex) {
|
||||||
case -3: // Set Pixel by index
|
case -3: // Set Pixel by index
|
||||||
if (AnimEvent.data.data[7] >= 0 && AnimEvent.data.data[7] < ledSettings[0].size) {
|
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);
|
Anim_GradientRotate(AnimationLooping, ledSettings[0].leds, ledSettings[0].size, colorPack, 50);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
ESP_LOGD(tag, "Loop default");
|
ESP_LOGW(tag, "Loop default");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -24,10 +24,10 @@ struct updateStatus {
|
|||||||
byte wifiIP[4] = {0, 0, 0, 0};
|
byte wifiIP[4] = {0, 0, 0, 0};
|
||||||
byte currVersion[3] = {FIRMWARE_VERSION_MAJOR, FIRMWARE_VERSION_MINOR, FIRMWARE_VERSION_PATCH};
|
byte currVersion[3] = {FIRMWARE_VERSION_MAJOR, FIRMWARE_VERSION_MINOR, FIRMWARE_VERSION_PATCH};
|
||||||
byte newVersion[3] = {0, 0, 0};
|
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;
|
}updatePacket;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Class for handling characteristic events
|
// Class for handling characteristic events
|
||||||
class UpgradeChar_Callbacks : public NimBLECharacteristicCallbacks {
|
class UpgradeChar_Callbacks : public NimBLECharacteristicCallbacks {
|
||||||
|
|
||||||
@ -64,6 +64,7 @@ class UpgradeChar_Callbacks : public NimBLECharacteristicCallbacks {
|
|||||||
updatePacket.wifiStatus = WIFI_DISCONNECTED;
|
updatePacket.wifiStatus = WIFI_DISCONNECTED;
|
||||||
updatePacket.wifiOnline = false;
|
updatePacket.wifiOnline = false;
|
||||||
updatePacket.wifiIP[0] = updatePacket.wifiIP[1] = updatePacket.wifiIP[2] = updatePacket.wifiIP[3] = 0;
|
updatePacket.wifiIP[0] = updatePacket.wifiIP[1] = updatePacket.wifiIP[2] = updatePacket.wifiIP[3] = 0;
|
||||||
|
strncpy(updatePacket.wifiSSID, ssid.c_str(), sizeof(updatePacket.wifiSSID) - 1);
|
||||||
} else {
|
} else {
|
||||||
ESP_LOGW(tag, "Failed to start WiFi connection task");
|
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[1] = ip[1];
|
||||||
updatePacket.wifiIP[2] = ip[2];
|
updatePacket.wifiIP[2] = ip[2];
|
||||||
updatePacket.wifiIP[3] = ip[3];
|
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 {
|
} else {
|
||||||
updatePacket.wifiStatus = WIFI_DISCONNECTED;
|
updatePacket.wifiStatus = WIFI_DISCONNECTED;
|
||||||
updatePacket.wifiIP[0] = 0;
|
updatePacket.wifiIP[0] = 0;
|
||||||
updatePacket.wifiIP[1] = 0;
|
updatePacket.wifiIP[1] = 0;
|
||||||
updatePacket.wifiIP[2] = 0;
|
updatePacket.wifiIP[2] = 0;
|
||||||
updatePacket.wifiIP[3] = 0;
|
updatePacket.wifiIP[3] = 0;
|
||||||
|
memset(updatePacket.wifiSSID, 0, sizeof(updatePacket.wifiSSID));
|
||||||
}
|
}
|
||||||
|
|
||||||
//update version
|
//update version
|
||||||
|
|||||||
@ -13,14 +13,14 @@ void Init_File_System(void){
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get all information of your LITTLEFS
|
// Get all information of your LITTLEFS
|
||||||
ESP_LOGD(tag, "File system info.");
|
ESP_LOGI(tag, "File system info.");
|
||||||
ESP_LOGD(tag, "Total: %d, Used: %d, Free:%d", LittleFS.totalBytes(), LittleFS.usedBytes(), LittleFS.totalBytes() - LittleFS.usedBytes());
|
ESP_LOGI(tag, "Total: %d, Used: %d, Free:%d", LittleFS.totalBytes(), LittleFS.usedBytes(), LittleFS.totalBytes() - LittleFS.usedBytes());
|
||||||
|
|
||||||
// Open dir folder
|
// Open dir folder
|
||||||
File dir = LittleFS.open("/");
|
File dir = LittleFS.open("/");
|
||||||
|
|
||||||
// Cycle all the content
|
// Cycle all the content
|
||||||
printAllFiles();
|
//printAllSystemFiles();
|
||||||
}
|
}
|
||||||
|
|
||||||
void getAllDirectories(String (&directoryList)[MAX_DIRECTORIES], int &count){
|
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){
|
void printFilesInDirectories(const String directoryList[], int count){
|
||||||
for (int i = 0; i < count; i++){
|
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]);
|
File dir = LittleFS.open(directoryList[i]);
|
||||||
if (!dir || !dir.isDirectory()){
|
if (!dir || !dir.isDirectory()){
|
||||||
@ -63,7 +63,7 @@ void printFilesInDirectories(const String directoryList[], int count){
|
|||||||
File file = dir.openNextFile();
|
File file = dir.openNextFile();
|
||||||
while (file){
|
while (file){
|
||||||
if (!file.isDirectory()){
|
if (!file.isDirectory()){
|
||||||
ESP_LOGD(tag, " File: %s", file.name());
|
ESP_LOGI(tag, " File: %s", file.name());
|
||||||
}
|
}
|
||||||
file = dir.openNextFile();
|
file = dir.openNextFile();
|
||||||
}
|
}
|
||||||
@ -72,13 +72,13 @@ void printFilesInDirectories(const String directoryList[], int count){
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void printAllFiles(void){
|
void printAllSystemFiles(void){
|
||||||
String directories[MAX_DIRECTORIES];
|
String directories[MAX_DIRECTORIES];
|
||||||
int dirCount = 0;
|
int dirCount = 0;
|
||||||
|
|
||||||
getAllDirectories(directories, dirCount);
|
getAllDirectories(directories, dirCount);
|
||||||
|
|
||||||
ESP_LOGD(tag, "File System Listing:");
|
ESP_LOGI(tag, "File System Listing:");
|
||||||
printFilesInDirectories(directories, dirCount);
|
printFilesInDirectories(directories, dirCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -20,7 +20,7 @@ void getAllDirectories(String (&directoryList)[MAX_DIRECTORIES], int& count);
|
|||||||
void printFilesInDirectories(const String directoryList[], int count);
|
void printFilesInDirectories(const String directoryList[], int count);
|
||||||
|
|
||||||
//void printAllFiles(int maxDirs);
|
//void printAllFiles(int maxDirs);
|
||||||
void printAllFiles(void);
|
void printAllSystemFiles(void);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
13
src/main.cpp
13
src/main.cpp
@ -119,6 +119,11 @@ void setup()
|
|||||||
// Init LittleFS
|
// Init LittleFS
|
||||||
Init_File_System();
|
Init_File_System();
|
||||||
|
|
||||||
|
// Print all system files
|
||||||
|
if (digitalRead(sys_settings.boardPins.btn[0]) == LOW){
|
||||||
|
printAllSystemFiles();
|
||||||
|
}
|
||||||
|
|
||||||
String board_file_path, booth_file_path;
|
String board_file_path, booth_file_path;
|
||||||
Get_Board_and_Booth_File_Paths("/system/system.json", board_file_path, booth_file_path);
|
Get_Board_and_Booth_File_Paths("/system/system.json", board_file_path, booth_file_path);
|
||||||
|
|
||||||
@ -156,7 +161,7 @@ void setup()
|
|||||||
// Initialize Temperature Sensor
|
// Initialize Temperature Sensor
|
||||||
Init_TSensor(72);
|
Init_TSensor(72);
|
||||||
|
|
||||||
|
|
||||||
float val = readBoardInputVoltage();
|
float val = readBoardInputVoltage();
|
||||||
ESP_LOGI(tag, "Input Volage = %f", val);
|
ESP_LOGI(tag, "Input Volage = %f", val);
|
||||||
|
|
||||||
@ -166,14 +171,14 @@ void setup()
|
|||||||
{
|
{
|
||||||
setStatusPin1(true);
|
setStatusPin1(true);
|
||||||
UpgradeMode = true;
|
UpgradeMode = true;
|
||||||
ESP_LOGE(tag, "Enabling BLE and Update Service");
|
ESP_LOGW(tag, "Enabling BLE and Update Service");
|
||||||
Init_BleServer(true, true);
|
Init_BleServer(true, true);
|
||||||
ESP_LOGW(tag, "Enabling Wifi AP and Client");
|
ESP_LOGW(tag, "Enabling Wifi AP and Client");
|
||||||
Wifi_Init();
|
Wifi_Init();
|
||||||
}
|
}
|
||||||
else
|
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
|
Init_BleServer(true, false); // Dont start the Upgrade service
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -308,7 +313,7 @@ void loop()
|
|||||||
if(UpgradeMode){
|
if(UpgradeMode){
|
||||||
ON_EVERY_N_MILLISECONDS(5000)
|
ON_EVERY_N_MILLISECONDS(5000)
|
||||||
{
|
{
|
||||||
Buzzer_Play_Tune(TUNE_THUMP, true, true);
|
Buzzer_Play_Tune(TUNE_ACK, true, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -11,25 +11,25 @@ void Init_ButtonEvents(int8_t (&pin)[3]){
|
|||||||
if (pin[0] >= 0) {
|
if (pin[0] >= 0) {
|
||||||
if (boardButtons[0] == nullptr) {
|
if (boardButtons[0] == nullptr) {
|
||||||
boardButtons[0] = new OneButton(pin[0], true, true);
|
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 {
|
} 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 (pin[1] >= 0) {
|
||||||
if (boardButtons[1] == nullptr) {
|
if (boardButtons[1] == nullptr) {
|
||||||
boardButtons[1] = new OneButton(pin[1], true, true);
|
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 {
|
} 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 (pin[2] >= 0) {
|
||||||
if (boardButtons[2] == nullptr) {
|
if (boardButtons[2] == nullptr) {
|
||||||
boardButtons[2] = new OneButton(pin[2], true, false);
|
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 {
|
} 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]->attachLongPressStart(btn3_LongPressStart);
|
||||||
boardButtons[2]->attachLongPressStop(btn3_LongPressStop);
|
boardButtons[2]->attachLongPressStop(btn3_LongPressStop);
|
||||||
boardButtons[2]->attachDuringLongPress(btn3_DuringLongPress);
|
boardButtons[2]->attachDuringLongPress(btn3_DuringLongPress);
|
||||||
ESP_LOGD(tag, "Button3 Events Initialized");
|
ESP_LOGI(tag, "Button3 Events Initialized");
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
ESP_LOGD(tag, "Button3 Not Initialized");
|
ESP_LOGW(tag, "Button3 Not Initialized");
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -27,7 +27,7 @@ void Init_Buzzer(int8_t pin, const char* configFile)
|
|||||||
}
|
}
|
||||||
|
|
||||||
Buzzer_Load_Tunes(configFile); // Load Tunes
|
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)
|
void Buzzer_Play_Tune(TUNE_TYPE tune, bool async, bool hasPriority)
|
||||||
|
|||||||
@ -61,27 +61,27 @@ TI_TMP102_Compatible *tSensor = nullptr;
|
|||||||
if ((FanState == 2) && (temperature < (sp2 - hyst))) {
|
if ((FanState == 2) && (temperature < (sp2 - hyst))) {
|
||||||
newDuty = fp1;
|
newDuty = fp1;
|
||||||
FanState = 1;
|
FanState = 1;
|
||||||
//ESP_LOGD(tag, "Dropping down to FanPower1");
|
ESP_LOGD(tag, "Dropping down to FanPower1");
|
||||||
}
|
}
|
||||||
else if ((FanState == 1) && (temperature < (sp1 - hyst))) {
|
else if ((FanState == 1) && (temperature < (sp1 - hyst))) {
|
||||||
newDuty = 0;
|
newDuty = 0;
|
||||||
FanState = 0;
|
FanState = 0;
|
||||||
//ESP_LOGD(tag, "Dropping down to FanPower0");
|
ESP_LOGD(tag, "Dropping down to FanPower0");
|
||||||
}
|
}
|
||||||
else if ((FanState <= 1) && (temperature > sp1)) {
|
else if ((FanState <= 1) && (temperature > sp1)) {
|
||||||
newDuty = fp1;
|
newDuty = fp1;
|
||||||
if (temperature > sp2) {
|
if (temperature > sp2) {
|
||||||
newDuty = fp2;
|
newDuty = fp2;
|
||||||
FanState = 2;
|
FanState = 2;
|
||||||
//ESP_LOGD(tag, "Raising up to FanPower2");
|
ESP_LOGD(tag, "Raising up to FanPower2");
|
||||||
} //else {
|
} else {
|
||||||
//ESP_LOGD(tag, "Raising up to FanPower1");
|
ESP_LOGD(tag, "Raising up to FanPower1");
|
||||||
//}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply new duty cycle if changed
|
// Apply new duty cycle if changed
|
||||||
if (currentDuty != newDuty) {
|
if (currentDuty != newDuty) {
|
||||||
pwmOut->setOutput(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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -102,7 +102,7 @@ void Wifi_Init()
|
|||||||
WiFi.onEvent(onWiFiEvent);
|
WiFi.onEvent(onWiFiEvent);
|
||||||
WiFi.setHostname(mDnsName.c_str());
|
WiFi.setHostname(mDnsName.c_str());
|
||||||
webServer.begin();
|
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);
|
StartWifiConnectTask(client_ssid, client_pass);
|
||||||
|
|
||||||
@ -123,12 +123,12 @@ bool StartWifiConnectTask(String ssid = "", String pass = "")
|
|||||||
client_pass = pass;
|
client_pass = pass;
|
||||||
if (Wifi_Task_Handle == NULL)
|
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);
|
xTaskCreatePinnedToCore(Wifi_ConnectTask, "Wifi_Task", 1024 * 4, NULL, 1, &Wifi_Task_Handle, 0);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
ESP_LOGD(tag, "WiFi task already running");
|
ESP_LOGI(tag, "WiFi task already running");
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@ -148,7 +148,7 @@ void Wifi_ConnectTask(void *parameter)
|
|||||||
|
|
||||||
if (WiFi.status() != WL_CONNECTED || client_ssid != WiFi.SSID())
|
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
|
// Disconnect and connect to new network
|
||||||
WiFi.disconnect(true);
|
WiFi.disconnect(true);
|
||||||
@ -168,7 +168,7 @@ void Wifi_ConnectTask(void *parameter)
|
|||||||
ESP_LOGW(tag, "Connection failed - check password");
|
ESP_LOGW(tag, "Connection failed - check password");
|
||||||
break;
|
break;
|
||||||
default:
|
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));
|
vTaskDelay(pdMS_TO_TICKS(ATTEMPT_DELAY_MS));
|
||||||
attempts++;
|
attempts++;
|
||||||
@ -383,7 +383,7 @@ void Setup_WebServer_Handlers(AsyncWebServer &server)
|
|||||||
// Start connection
|
// Start connection
|
||||||
StartWifiConnectTask(ssid, pass);
|
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\"}"); });
|
request->send(200, "application/json", "{\"status\":\"connecting\"}"); });
|
||||||
server.on("/wifi/status", HTTP_GET, [](AsyncWebServerRequest *request)
|
server.on("/wifi/status", HTTP_GET, [](AsyncWebServerRequest *request)
|
||||||
{
|
{
|
||||||
@ -420,7 +420,7 @@ void Setup_WebServer_Handlers(AsyncWebServer &server)
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
String filename = uriDecode(request->getParam("file")->value());
|
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");
|
request->send(LittleFS, filename, "application/octet-stream");
|
||||||
}
|
}
|
||||||
catch (const std::exception& e) {
|
catch (const std::exception& e) {
|
||||||
@ -490,7 +490,7 @@ void Setup_WebServer_Handlers(AsyncWebServer &server)
|
|||||||
|
|
||||||
// Get and decode filename
|
// Get and decode filename
|
||||||
String fileName = uriDecode(request->getParam(param_edit_path)->value());
|
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
|
// Set save path
|
||||||
savePath = (fileName == "new") ? "/new.txt" : fileName;
|
savePath = (fileName == "new") ? "/new.txt" : fileName;
|
||||||
@ -500,7 +500,7 @@ void Setup_WebServer_Handlers(AsyncWebServer &server)
|
|||||||
savePath = "/" + savePath;
|
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 {
|
try {
|
||||||
sendHtmlFile("/www/edit.html", request, fileManagerHtmlProcessor);
|
sendHtmlFile("/www/edit.html", request, fileManagerHtmlProcessor);
|
||||||
@ -651,7 +651,7 @@ void Setup_WebServer_Handlers(AsyncWebServer &server)
|
|||||||
contentType = "application/octet-stream";
|
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);
|
request->send(LittleFS, filePath, contentType);
|
||||||
}
|
}
|
||||||
catch (const std::runtime_error &e)
|
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);
|
AsyncWebParameter *p = request->getParam("dir-path", true, false);
|
||||||
String path = p->value() + "/" + filename;
|
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
|
// Validate path
|
||||||
if (!path.startsWith("/"))
|
if (!path.startsWith("/"))
|
||||||
@ -729,7 +729,7 @@ void handleFilesUpload_OnBody(AsyncWebServerRequest *request, String filename, s
|
|||||||
if (final)
|
if (final)
|
||||||
{
|
{
|
||||||
request->_tempFile.close();
|
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");
|
request->redirect("/files");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -750,7 +750,7 @@ void sendHtmlFile(const char *filePath, AsyncWebServerRequest *request, String (
|
|||||||
String processedData = varReplace(htmlFile, callback);
|
String processedData = varReplace(htmlFile, callback);
|
||||||
delete[] htmlFile; // Clean up allocated memory
|
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);
|
request->send(200, "text/html", processedData);
|
||||||
}
|
}
|
||||||
catch (const std::exception &e)
|
catch (const std::exception &e)
|
||||||
@ -773,11 +773,11 @@ const char *getFileExtension(const char *filename)
|
|||||||
const char *lastDot = strrchr(filename, '.');
|
const char *lastDot = strrchr(filename, '.');
|
||||||
if (!lastDot || lastDot == filename || *(lastDot + 1) == '\0')
|
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 "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
ESP_LOGD(tag, "Found extension: %s", lastDot + 1);
|
ESP_LOGI(tag, "Found extension: %s", lastDot + 1);
|
||||||
return lastDot + 1;
|
return lastDot + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -786,7 +786,7 @@ const char *getFileType(const char *ext)
|
|||||||
if (!ext)
|
if (!ext)
|
||||||
return "application/octet-stream";
|
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)
|
if (strcmp(ext, "png") == 0)
|
||||||
return "image/png";
|
return "image/png";
|
||||||
@ -915,92 +915,92 @@ void onWiFiEvent(WiFiEvent_t event)
|
|||||||
switch (event)
|
switch (event)
|
||||||
{
|
{
|
||||||
case ARDUINO_EVENT_WIFI_READY:
|
case ARDUINO_EVENT_WIFI_READY:
|
||||||
ESP_LOGD(tag, "WiFi interface ready");
|
ESP_LOGI(tag, "WiFi interface ready");
|
||||||
break;
|
break;
|
||||||
case ARDUINO_EVENT_WIFI_SCAN_DONE:
|
case ARDUINO_EVENT_WIFI_SCAN_DONE:
|
||||||
ESP_LOGD(tag, "Completed scan for access points");
|
ESP_LOGI(tag, "Completed scan for access points");
|
||||||
break;
|
break;
|
||||||
case ARDUINO_EVENT_WIFI_STA_START:
|
case ARDUINO_EVENT_WIFI_STA_START:
|
||||||
ESP_LOGD(tag, "WiFi client started");
|
ESP_LOGI(tag, "WiFi client started");
|
||||||
break;
|
break;
|
||||||
case ARDUINO_EVENT_WIFI_STA_STOP:
|
case ARDUINO_EVENT_WIFI_STA_STOP:
|
||||||
ESP_LOGD(tag, "WiFi clients stopped");
|
ESP_LOGI(tag, "WiFi clients stopped");
|
||||||
break;
|
break;
|
||||||
case ARDUINO_EVENT_WIFI_STA_CONNECTED:
|
case ARDUINO_EVENT_WIFI_STA_CONNECTED:
|
||||||
ESP_LOGD(tag, "Connected to AP");
|
ESP_LOGI(tag, "Connected to AP");
|
||||||
break;
|
break;
|
||||||
case ARDUINO_EVENT_WIFI_STA_DISCONNECTED:
|
case ARDUINO_EVENT_WIFI_STA_DISCONNECTED:
|
||||||
ESP_LOGD(tag, "WiFi Disconnected");
|
ESP_LOGI(tag, "WiFi Disconnected");
|
||||||
setStatusPin1(false);
|
setStatusPin1(false);
|
||||||
Buzzer_Play_Tune(TUNE_DISCONNECTED);
|
Buzzer_Play_Tune(TUNE_DISCONNECTED);
|
||||||
break;
|
break;
|
||||||
case ARDUINO_EVENT_WIFI_STA_AUTHMODE_CHANGE:
|
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;
|
break;
|
||||||
case ARDUINO_EVENT_WIFI_STA_GOT_IP:
|
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();
|
// Wifi_Start_MDNS();
|
||||||
setStatusPin1(true);
|
setStatusPin1(true);
|
||||||
Buzzer_Play_Tune(TUNE_CONNECTED);
|
Buzzer_Play_Tune(TUNE_CONNECTED);
|
||||||
break;
|
break;
|
||||||
case ARDUINO_EVENT_WIFI_STA_LOST_IP:
|
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;
|
break;
|
||||||
case ARDUINO_EVENT_WPS_ER_SUCCESS:
|
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;
|
break;
|
||||||
case ARDUINO_EVENT_WPS_ER_FAILED:
|
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;
|
break;
|
||||||
case ARDUINO_EVENT_WPS_ER_TIMEOUT:
|
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;
|
break;
|
||||||
case ARDUINO_EVENT_WPS_ER_PIN:
|
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;
|
break;
|
||||||
case ARDUINO_EVENT_WIFI_AP_START:
|
case ARDUINO_EVENT_WIFI_AP_START:
|
||||||
ESP_LOGD(tag, "WiFi access point started");
|
ESP_LOGI(tag, "WiFi access point started");
|
||||||
break;
|
break;
|
||||||
case ARDUINO_EVENT_WIFI_AP_STOP:
|
case ARDUINO_EVENT_WIFI_AP_STOP:
|
||||||
ESP_LOGD(tag, "WiFi access point stopped");
|
ESP_LOGI(tag, "WiFi access point stopped");
|
||||||
break;
|
break;
|
||||||
case ARDUINO_EVENT_WIFI_AP_STACONNECTED:
|
case ARDUINO_EVENT_WIFI_AP_STACONNECTED:
|
||||||
ESP_LOGD(tag, "Client connected");
|
ESP_LOGI(tag, "Client connected");
|
||||||
break;
|
break;
|
||||||
case ARDUINO_EVENT_WIFI_AP_STADISCONNECTED:
|
case ARDUINO_EVENT_WIFI_AP_STADISCONNECTED:
|
||||||
ESP_LOGD(tag, "SoftAP Client Disconnected");
|
ESP_LOGI(tag, "SoftAP Client Disconnected");
|
||||||
Buzzer_Play_Tune(TUNE_DISCONNECTED);
|
Buzzer_Play_Tune(TUNE_DISCONNECTED);
|
||||||
break;
|
break;
|
||||||
case ARDUINO_EVENT_WIFI_AP_STAIPASSIGNED:
|
case ARDUINO_EVENT_WIFI_AP_STAIPASSIGNED:
|
||||||
ESP_LOGD(tag, "SoftAP Client Connected");
|
ESP_LOGI(tag, "SoftAP Client Connected");
|
||||||
Buzzer_Play_Tune(TUNE_CONNECTED);
|
Buzzer_Play_Tune(TUNE_CONNECTED);
|
||||||
break;
|
break;
|
||||||
case ARDUINO_EVENT_WIFI_AP_PROBEREQRECVED:
|
case ARDUINO_EVENT_WIFI_AP_PROBEREQRECVED:
|
||||||
ESP_LOGD(tag, "Received probe request");
|
ESP_LOGI(tag, "Received probe request");
|
||||||
break;
|
break;
|
||||||
case ARDUINO_EVENT_WIFI_AP_GOT_IP6:
|
case ARDUINO_EVENT_WIFI_AP_GOT_IP6:
|
||||||
ESP_LOGD(tag, "AP IPv6 is preferred");
|
ESP_LOGI(tag, "AP IPv6 is preferred");
|
||||||
break;
|
break;
|
||||||
case ARDUINO_EVENT_WIFI_STA_GOT_IP6:
|
case ARDUINO_EVENT_WIFI_STA_GOT_IP6:
|
||||||
ESP_LOGD(tag, "STA IPv6 is preferred");
|
ESP_LOGI(tag, "STA IPv6 is preferred");
|
||||||
break;
|
break;
|
||||||
case ARDUINO_EVENT_ETH_GOT_IP6:
|
case ARDUINO_EVENT_ETH_GOT_IP6:
|
||||||
ESP_LOGD(tag, "Ethernet IPv6 is preferred");
|
ESP_LOGI(tag, "Ethernet IPv6 is preferred");
|
||||||
break;
|
break;
|
||||||
case ARDUINO_EVENT_ETH_START:
|
case ARDUINO_EVENT_ETH_START:
|
||||||
ESP_LOGD(tag, "Ethernet started");
|
ESP_LOGI(tag, "Ethernet started");
|
||||||
break;
|
break;
|
||||||
case ARDUINO_EVENT_ETH_STOP:
|
case ARDUINO_EVENT_ETH_STOP:
|
||||||
ESP_LOGD(tag, "Ethernet stopped");
|
ESP_LOGI(tag, "Ethernet stopped");
|
||||||
break;
|
break;
|
||||||
case ARDUINO_EVENT_ETH_CONNECTED:
|
case ARDUINO_EVENT_ETH_CONNECTED:
|
||||||
ESP_LOGD(tag, "Ethernet connected");
|
ESP_LOGI(tag, "Ethernet connected");
|
||||||
break;
|
break;
|
||||||
case ARDUINO_EVENT_ETH_DISCONNECTED:
|
case ARDUINO_EVENT_ETH_DISCONNECTED:
|
||||||
ESP_LOGD(tag, "Ethernet disconnected");
|
ESP_LOGI(tag, "Ethernet disconnected");
|
||||||
break;
|
break;
|
||||||
case ARDUINO_EVENT_ETH_GOT_IP:
|
case ARDUINO_EVENT_ETH_GOT_IP:
|
||||||
ESP_LOGD(tag, "Obtained IP address");
|
ESP_LOGI(tag, "Obtained IP address");
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
@ -1102,7 +1102,7 @@ bool writeFile(fs::FS &fs, const char *path, const char *message)
|
|||||||
return false;
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1157,7 +1157,7 @@ char *readFile(fs::FS &fs, const char *path)
|
|||||||
|
|
||||||
// Null terminate
|
// Null terminate
|
||||||
fileContent[bytesRead] = '\0';
|
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;
|
return fileContent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
190
update.json
Normal file
190
update.json
Normal 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"
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user