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 @@
@@ -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)
-
+
-
+
-