Skip to content

Commit

Permalink
Improved the firmware update process with instructions, links, and au…
Browse files Browse the repository at this point in the history
…to-checking server for updates.
  • Loading branch information
Brian Mathews authored and Brian Mathews committed Nov 30, 2024
1 parent 36778d6 commit ecfcacb
Show file tree
Hide file tree
Showing 5 changed files with 291 additions and 29 deletions.
2 changes: 1 addition & 1 deletion firmware/webtools/manifest.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "SOTACAT for Elecraft KX2 and KX3",
"version": "2024-11-28_11:08-Release",
"version": "2024-11-30_10:30-Release",
"builds": [
{
"chipFamily": "ESP32-C3",
Expand Down
2 changes: 1 addition & 1 deletion include/build_info.h
Original file line number Diff line number Diff line change
@@ -1 +1 @@
#define BUILD_DATE_TIME "241128:1108"
#define BUILD_DATE_TIME "241130:1030"
193 changes: 193 additions & 0 deletions src/web/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -418,6 +418,199 @@ async function refreshSotaPotaJson(force) {
}
}

const VERSION_CHECK_INTERVAL_DAYS = 1.0;
const VERSION_CHECK_STORAGE_KEY = 'sotacat_version_check';
const MANIFEST_URL = 'https://sotamat.com/wp-content/uploads/manifest.json';
const VERSION_CHECK_TIMEOUT_MS = 5000;

// Add the version check functions
function normalizeVersion(versionString) {
console.log('[Version Check] Parsing version string:', versionString);

// Extract date and time components from version string
let match;
if (versionString.includes('-Release')) {
// Handle manifest format (e.g., "2024-11-29_11:37-Release")
match = versionString.match(/(\d{4})-(\d{2})-(\d{2})_(\d{2}):(\d{2})/);
console.log('[Version Check] Manifest format detected, match:', match);
} else if (versionString.includes(':')) {
// Handle device format (e.g., "AB6D_1:241129:2346-R")
const parts = versionString.split(':');
match = parts[1].match(/(\d{2})(\d{2})(\d{2})/);
if (match && parts[2]) {
const timeMatch = parts[2].match(/(\d{2})(\d{2})/);
if (timeMatch) {
match = [...match, timeMatch[1], timeMatch[2]];
}
}
console.log('[Version Check] Device format detected, match:', match);
}

if (!match) {
console.log('[Version Check] Failed to match version pattern');
return null;
}

let year, month, day, hour, minute;

if (versionString.includes('-Release')) {
// Manifest format parsing
year = parseInt(match[1]);
month = parseInt(match[2]);
day = parseInt(match[3]);
hour = parseInt(match[4]);
minute = parseInt(match[5]);
} else {
// Device format parsing
year = 2000 + parseInt(match[1]);
month = parseInt(match[2]);
day = parseInt(match[3]);
hour = parseInt(match[4] || '0');
minute = parseInt(match[5] || '0');
}

// Adjust month after parsing (JS months are 0-based)
const originalMonth = month; // Save for logging
month = month - 1;

console.log('[Version Check] Parsed components:', {
year, originalMonth, day, hour, minute
});

const date = new Date(Date.UTC(year, month, day, hour, minute));
const timestamp = date.getTime() / 1000;

console.log('[Version Check] Resulting timestamp:', timestamp,
'Date:', date.toISOString());

return timestamp;
}

function shouldCheckVersion() {
const lastCheck = localStorage.getItem(VERSION_CHECK_STORAGE_KEY);
console.log('[Version Check] Last check timestamp:', lastCheck);
if (!lastCheck) {
console.log('[Version Check] No previous check found, returning true');
return true;
}

const lastCheckDate = new Date(parseInt(lastCheck));
const now = new Date();
const daysSinceLastCheck = (now - lastCheckDate) / (1000 * 60 * 60 * 24);

console.log('[Version Check] Days since last check:', daysSinceLastCheck);
console.log('[Version Check] Check interval:', VERSION_CHECK_INTERVAL_DAYS);
const shouldCheck = daysSinceLastCheck >= VERSION_CHECK_INTERVAL_DAYS;
console.log('[Version Check] Should check?', shouldCheck);
return shouldCheck;
}

// Perform version check
async function checkFirmwareVersion() {
console.log('[Version Check] Starting version check');
if (!shouldCheckVersion()) {
console.log('[Version Check] Skipping check due to interval');
return;
}

try {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), VERSION_CHECK_TIMEOUT_MS);

// Get current version from device
console.log('[Version Check] Fetching current version from device');
const response = await fetch('/api/v1/version', {
signal: controller.signal
});
if (!response.ok) {
console.log('[Version Check] Failed to get current version, status:', response.status);
return;
}
const currentVersion = await response.text();
console.log('[Version Check] Current device version:', currentVersion);
const currentBuildTime = normalizeVersion(currentVersion);
if (!currentBuildTime) {
console.error('[Version Check] Failed to parse current version');
return;
}

// Modify manifest fetch to handle CORS
console.log('[Version Check] Fetching manifest from:', MANIFEST_URL);
const manifestResponse = await fetch(MANIFEST_URL, {
signal: controller.signal,
mode: 'cors', // Try CORS first
headers: {
'Accept': 'application/json'
}
}).catch(async () => {
console.log('[Version Check] CORS failed, trying no-cors mode');
// If CORS fails, try no-cors mode
return fetch(MANIFEST_URL, {
signal: controller.signal,
mode: 'no-cors' // Fallback to no-cors
});
});

if (!manifestResponse.ok) {
console.log('[Version Check] Failed to fetch manifest, status:', manifestResponse.status);
return;
}

let manifest;
try {
manifest = await manifestResponse.json();
} catch (e) {
console.info('Version check skipped: Invalid manifest JSON');
return;
}

// Clear the timeout since we got our response
clearTimeout(timeoutId);

const latestVersion = normalizeVersion(manifest.version);
if (!latestVersion) {
console.info('Version check skipped: Invalid version format in manifest');
return;
}

// Compare versions using Unix timestamps
console.info('[Version Check] Latest version timestamp:', new Date(latestVersion * 1000).toISOString());
console.info('[Version Check] Current version timestamp:', new Date(currentBuildTime * 1000).toISOString());

// Inform the user there is new firmware, and show them the datetime of the new version.
if (latestVersion > currentBuildTime) {
const userResponse = confirm(
'A new firmware version is available for your SOTAcat device.\n\n' +
'Would you like to go to the Settings page to update your firmware?\n\n' +
`Your version: ${new Date(currentBuildTime * 1000).toISOString()}\n` +
`New version: ${new Date(latestVersion * 1000).toISOString()}`
);

if (userResponse) {
openTab('Settings');
}
}

// Update last check timestamp
localStorage.setItem(VERSION_CHECK_STORAGE_KEY, Date.now().toString());

} catch (error) {
console.log('[Version Check] Error during version check:', error.message);
throw error; // Re-throw to be caught by the caller
}
}

// Add to the DOMContentLoaded event listener in main.js
document.addEventListener('DOMContentLoaded', function() {
openTab('sota');

// Schedule version check after page loads
setTimeout(() => {
console.log('[Version Check] Executing initial version check');
checkFirmwareVersion().catch(error => {
console.log('[Version Check] Error during version check:', error);
});
}, 1000);
});


87 changes: 70 additions & 17 deletions src/web/settings.html
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,12 @@ <h2>WiFi Settings</h2>
(iPhone, MacOS, Windows) or to
<a href="http://192.168.4.1" target="_blank">http://192.168.4.1</a> or
when using an Android hotspot, install the
<a href="https://play.google.com/store/apps/details?id=de.wellenvogel.bonjourbrowser"
target="_blank">Bonjour Browser</a> app
from the Google Play store and use that app to navigate to the
<a
href="https://play.google.com/store/apps/details?id=de.wellenvogel.bonjourbrowser"
target="_blank"
>Bonjour Browser</a
>
app from the Google Play store and use that app to navigate to the
"sotacat.local" address. <br /><br />
</div>
<div class="wifi-client">
Expand Down Expand Up @@ -147,22 +150,72 @@ <h3><b>Server:</b> iPhone connects to SOTACAT hotspot</h3>
<input type="submit" class="save-button" value="Save and Reboot" />
</form>
<hr />
<h2>Firmware Update</h2>
<form id="ota-update-form" enctype="multipart/form-data" class="firmware-update-form">
<div class="firmware-client">
<div class="firmware-row">
<div class="firmware-label">
<label for="ota-file" class="file-label">Select firmware (.bin) file</label>
</div>
<input type="file" id="ota-file" name="ota-file" accept=".bin" required onchange="updateButtonText()" style="display: none;" />
<h2>Firmware Update</h2>
<form
id="ota-update-form"
enctype="multipart/form-data"
class="firmware-update-form"
>
<div class="firmware-instructions">
<br />
SOTACAT automatically checks for firmware updates daily when connected to
the internet. To update your firmware:
<br /><br />
1. <b>Download</b> the firmware .bin from the cloud to your phone's local
storage. The button below downloads the default firmware for the default
SOTACAT hardware. To see if there are other firmwares available, visit
<a href="https://sotamat.com/sotacat" target="_blank" rel="noopener"
>https://sotamat.com/sotacat</a
>.
<br />
2. <b>Select</b> the downloaded firmware .bin file from step 1.
<br />
3. <b>Upload</b> to send the .bin file to the SOTACAT and install it.
</div>
<div class="firmware-row">
<button type="button" id="upload-button" class="upload-button" onclick="uploadFirmware()" disabled>Upload Firmware</button>

<div class="firmware-client">
<div class="firmware-row">
<button
type="button"
class="download-button"
onclick="window.location.href='https://sotamat.com/wp-content/uploads/SOTACAT-ESP32C3-OTA.bin'"
>
Download Firmware
</button>
</div>
<div class="firmware-row">
<button
type="button"
class="file-label"
onclick="document.getElementById('ota-file').click()"
>
Select firmware (.bin) file
</button>
<input
type="file"
id="ota-file"
name="ota-file"
accept=".bin"
required
onchange="updateButtonText()"
style="display: none"
/>
</div>
<div class="firmware-row">
<button
type="button"
id="upload-button"
class="upload-button"
onclick="uploadFirmware()"
disabled
>
Upload Firmware
</button>
</div>
</div>
</div>
</form>
<div id="ota-status"></div>
<hr />
</form>
<div id="ota-status"></div>
<hr />
<hr />

<!--
Expand Down
36 changes: 26 additions & 10 deletions src/web/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -428,9 +428,9 @@ hr {

.firmware-row {
display: flex;
justify-content: flex-start;
justify-content: center; /* Center the buttons horizontally */
align-items: center;
margin-bottom: 3.5px;
margin-bottom: 10px;
}

.firmware-label, .firmware-client input[type="file"] {
Expand All @@ -441,22 +441,20 @@ hr {
}

.file-label {
display: inline-block;
font-size: 16px;
padding: var(--spacing-medium) var(--spacing-large);
background-color: var(--bg-button);
color: var(--text-button);
border-radius: var(--border-radius);
cursor: pointer;
text-align: center;
transition: background-color var(--transition-speed) ease;
white-space: nowrap; /* Prevents text wrapping */
width: auto; /* Automatically adjust width based on content */
max-width: 100%; /* Ensures it does not exceed container size */
min-width: 250px; /* Adjust this value as needed */
white-space: nowrap;
min-width: 250px;
width: auto;
box-sizing: border-box; /* Include padding and border in the element's total width and height */
overflow: hidden; /* Prevents any overflow issues */
vertical-align: middle; /* Aligns label with other elements */
box-sizing: border-box;
overflow: hidden;
vertical-align: middle;
}

/* Styling the upload button consistently with others */
Expand Down Expand Up @@ -531,3 +529,21 @@ hr {
margin: 0 auto;
}
}

/* Add styles for the download button */
.download-button {
font-size: 16px;
padding: var(--spacing-medium) var(--spacing-large);
margin: var(--spacing-small);
border-radius: var(--border-radius);
background-color: var(--bg-button);
color: var(--text-button);
border: none;
cursor: pointer;
transition: background-color var(--transition-speed) ease;
min-width: 250px; /* Match the width of other firmware buttons */
}

.download-button:active {
background-color: var(--bg-button-active);
}

0 comments on commit ecfcacb

Please sign in to comment.