diff --git a/setup/index.html b/setup/index.html index bc1dbf6..e4150c6 100644 --- a/setup/index.html +++ b/setup/index.html @@ -93,6 +93,31 @@

Background Image

+
+
+

Avatar Image

+
+ + + +
+ +
+
+ + +
+
+
+
@@ -193,6 +218,7 @@

⚠️ Device Reset Required

const PASS_ID = "a987ab18-a940-421a-a1d7-b94ee22bccbe"; const SERVER_URL_ID = "cef520a9-bcb5-4fc6-87f7-82804eee2b20"; const BACKGROUND_IMAGE_ID = "d1f3b2c4-5e6f-4a7b-8c9d-0e1f2a3b4c5d"; + const AVATAR_IMAGE_ID = "e2f4c3b5-6d7e-4f8a-9b0c-1f2e3d4c5b6a"; const RESET_ID = "f0e1d2c3-b4a5-6789-0abc-def123456789"; const AFE_LINEAR_GAIN_ID = "a1b2c3d4-e5f6-4789-0abc-def123456789"; const AGC_TARGET_LEVEL_ID = "b2c3d4e5-f6a7-4890-1bcd-ef2345678901"; @@ -205,6 +231,7 @@

⚠️ Device Reset Required

let isConnected = false; let toast = null; let selectedBackgroundFile = null; + let selectedAvatarFile = null; // DOM const connectButton = document.getElementById('connectButton'); @@ -224,6 +251,11 @@

⚠️ Device Reset Required

const fileError = document.getElementById('fileError'); const writeBgButton = document.getElementById('writeBgButton'); const clearBgButton = document.getElementById('clearBgButton'); + const avatarImage = document.getElementById('avatarImage'); + const avatarPreview = document.getElementById('avatarPreview'); + const avatarFileError = document.getElementById('avatarFileError'); + const writeAvatarButton = document.getElementById('writeAvatarButton'); + const clearAvatarButton = document.getElementById('clearAvatarButton'); const notificationToast = document.getElementById('notificationToast'); const toastMessage = document.getElementById('toastMessage'); const resetNotSupportedModal = document.getElementById('resetNotSupportedModal'); @@ -331,14 +363,46 @@

⚠️ Device Reset Required

return true; } - // background image - function applyBackgroundImage(imageDataUrl) { - document.body.style.backgroundImage = `url(${imageDataUrl})`; + // validate and preview avatar + function validateAndPreviewAvatarFile(input) { + const file = input.files[0]; + avatarFileError.textContent = ''; + avatarPreview.classList.add('hidden'); + writeAvatarButton.disabled = true; + selectedAvatarFile = null; + + if (file) { + // check file type + if (!file.type.startsWith('image/gif')) { + avatarFileError.textContent = 'Please select a GIF image file'; + input.value = ''; + return false; + } + + // check file size (128KB = 128 * 1024 bytes) + const maxSize = 128 * 1024; // 128KB + if (file.size > maxSize) { + avatarFileError.textContent = 'The image file size cannot exceed 128KB'; + input.value = ''; + return false; + } + + // preview image + const reader = new FileReader(); + reader.onload = function (e) { + avatarPreview.style.backgroundImage = `url(${e.target.result})`; + avatarPreview.classList.remove('hidden'); + selectedAvatarFile = file; + writeAvatarButton.disabled = false; + }; + reader.readAsDataURL(file); + } + return true; } // clear background image function clearBackgroundImage() { - document.body.style.backgroundImage = ''; + // No longer changes page background } // connect @@ -424,6 +488,8 @@

⚠️ Device Reset Required

serverUrlInput.disabled = false; backgroundImage.disabled = false; clearBgButton.disabled = false; + avatarImage.disabled = false; + clearAvatarButton.disabled = false; controlPanel.classList.remove('opacity-50', 'pointer-events-none'); // Enable AFE controls @@ -442,6 +508,9 @@

⚠️ Device Reset Required

backgroundImage.disabled = true; writeBgButton.disabled = true; clearBgButton.disabled = true; + avatarImage.disabled = true; + writeAvatarButton.disabled = true; + clearAvatarButton.disabled = true; controlPanel.classList.add('opacity-50', 'pointer-events-none'); // Disable AFE controls @@ -660,13 +729,6 @@

⚠️ Device Reset Required

await new Promise(resolve => setTimeout(resolve, 50)); } - // background image - const reader = new FileReader(); - reader.onload = function (e) { - applyBackgroundImage(e.target.result); - }; - reader.readAsDataURL(selectedBackgroundFile); - // reset the button writeBgButton.disabled = false; writeBgButton.textContent = 'Set Background'; @@ -684,6 +746,64 @@

⚠️ Device Reset Required

} } + async function writeAvatarImage() { + if (!isConnected || !service) { + showNotification('Error', 'EchoKit is not connected', true); + return; + } + + if (!selectedAvatarFile) { + showNotification('Error', 'Please select an avatar image', true); + return; + } + + try { + const characteristic = await service.getCharacteristic(AVATAR_IMAGE_ID); + + const arrayBuffer = await selectedAvatarFile.arrayBuffer(); + const totalSize = arrayBuffer.byteLength; + const chunkSize = 512; // BLE limit + const totalChunks = Math.ceil(totalSize / chunkSize); + + showNotification('Message', `Sending avatar ${totalChunks} data chunks ...`); + + // prevent double clicking + writeAvatarButton.disabled = true; + writeAvatarButton.textContent = 'Sending data ...'; + + for (let i = 0; i < totalChunks; i++) { + const start = i * chunkSize; + const end = Math.min(start + chunkSize, totalSize); + const chunk = arrayBuffer.slice(start, end); + + const packet = new Uint8Array(chunk.byteLength); + packet.set(new Uint8Array(chunk), 0); + + await characteristic.writeValue(packet); + + const progress = Math.round(((i + 1) / totalChunks) * 100); + writeAvatarButton.textContent = `In progress ... ${progress}%`; + + await new Promise(resolve => setTimeout(resolve, 50)); + } + + // reset the button + writeAvatarButton.disabled = false; + writeAvatarButton.textContent = 'Set Avatar'; + + showNotification('Success', `Avatar uploaded successfully! Sent ${totalChunks} packets, total size ${Math.round(totalSize / 1024)}KB`); + + } catch (error) { + console.error('Avatar error: ', error); + + // reset the button + writeAvatarButton.disabled = false; + writeAvatarButton.textContent = 'Set Avatar'; + + showNotification('Error', 'Avatar error: ' + error.message, true); + } + } + connectButton.addEventListener('click', async () => { if (!isConnected) { await connectToDevice(); @@ -722,6 +842,36 @@

⚠️ Device Reset Required

showNotification('Message', 'Background image cleared'); }); + writeAvatarButton.addEventListener('click', () => { + writeAvatarImage(); + }); + + clearAvatarButton.addEventListener('click', async () => { + if (!isConnected || !service) { + showNotification('Error', 'Device not connected', true); + return; + } + + try { + // Send empty data to clear avatar + const characteristic = await service.getCharacteristic(AVATAR_IMAGE_ID); + const emptyData = new Uint8Array(0); + await characteristic.writeValue(emptyData); + + // Clear UI state + avatarPreview.style.backgroundImage = ''; + avatarPreview.classList.add('hidden'); + avatarImage.value = ''; + selectedAvatarFile = null; + writeAvatarButton.disabled = true; + + showNotification('Success', 'Avatar cleared'); + } catch (error) { + console.error('Failed to clear avatar:', error); + showNotification('Error', 'Failed to clear avatar: ' + error.message, true); + } + }); + // Read AFE Linear Gain (string format f32) async function readAfeLinearGain() { if (!isConnected || !service) return false; diff --git a/setup/index_zh.html b/setup/index_zh.html index 8b4e199..9deda3d 100644 --- a/setup/index_zh.html +++ b/setup/index_zh.html @@ -42,114 +42,141 @@

Wi-Fi(必须是2.4GHz)

网络名 (SSID)
- +
- + -
-
-

服务器URL设置

-