@@ -92,6 +92,75 @@ <h3 class="card-title text-lg">Background Image</h3>
9292 </ div >
9393 </ div >
9494 </ div >
95+
96+ <!-- Advanced Settings Collapse Panel -->
97+ < div class ="collapse collapse-arrow bg-base-200 border border-base-300 ">
98+ < input type ="checkbox " id ="advancedSettingsToggle " />
99+ < div class ="collapse-title text-lg font-medium ">
100+ 🛠️ Advanced Settings
101+ </ div >
102+ < div class ="collapse-content ">
103+ < div class ="space-y-4 pt-4 ">
104+ <!-- AFE Linear Gain -->
105+ < div class ="card bg-base-100 border border-base-300 ">
106+ < div class ="card-body py-4 ">
107+ < h4 class ="card-title text-base " id ="afeLinearGainTitle "> AFE Linear Gain</ h4 >
108+ < label class ="form-control ">
109+ < div class ="label ">
110+ < span class ="label-text "> Linear gain value (0.1 - 3.0)</ span >
111+ < span class ="badge badge-primary " id ="afeLinearGainValue "> 1.5</ span >
112+ </ div >
113+ < input type ="range " min ="1 " max ="30 " value ="15 " class ="range range-sm "
114+ id ="afeLinearGainRange " />
115+ </ label >
116+ < button class ="btn btn-sm btn-outline mt-2 " id ="saveAfeLinearGainButton "
117+ disabled >
118+ Save AFE Linear Gain
119+ </ button >
120+ </ div >
121+ </ div >
122+
123+ <!-- AGC Target Level -->
124+ < div class ="card bg-base-100 border border-base-300 ">
125+ < div class ="card-body py-4 ">
126+ < h4 class ="card-title text-base " id ="agcTargetLevelTitle "> AGC Target Level</ h4 >
127+ < label class ="form-control ">
128+ < div class ="label ">
129+ < span class ="label-text "> Target level (dBFS, 0 - 32)</ span >
130+ < span class ="badge badge-primary " id ="agcTargetLevelValue "> 1</ span >
131+ </ div >
132+ < input type ="range " min ="0 " max ="32 " value ="1 " class ="range range-sm "
133+ id ="agcTargetLevelRange " />
134+ </ label >
135+ < button class ="btn btn-sm btn-outline mt-2 " id ="saveAgcTargetLevelButton "
136+ disabled >
137+ Save AGC Target Level
138+ </ button >
139+ </ div >
140+ </ div >
141+
142+ <!-- AGC Compression Gain -->
143+ < div class ="card bg-base-100 border border-base-300 ">
144+ < div class ="card-body py-4 ">
145+ < h4 class ="card-title text-base " id ="agcCompressionGainTitle "> AGC Compression
146+ Gain</ h4 >
147+ < label class ="form-control ">
148+ < div class ="label ">
149+ < span class ="label-text "> Compression gain (dB, 0 - 32)</ span >
150+ < span class ="badge badge-primary " id ="agcCompressionGainValue "> 15</ span >
151+ </ div >
152+ < input type ="range " min ="0 " max ="32 " value ="15 " class ="range range-sm "
153+ id ="agcCompressionGainRange " />
154+ </ label >
155+ < button class ="btn btn-sm btn-outline mt-2 " id ="saveAgcCompressionGainButton "
156+ disabled >
157+ Save AGC Compression Gain
158+ </ button >
159+ </ div >
160+ </ div >
161+ </ div >
162+ </ div >
163+ </ div >
95164 </ div >
96165 </ div >
97166 </ div >
@@ -125,6 +194,9 @@ <h3 class="font-bold text-lg">⚠️ Device Reset Required</h3>
125194 const SERVER_URL_ID = "cef520a9-bcb5-4fc6-87f7-82804eee2b20" ;
126195 const BACKGROUND_IMAGE_ID = "d1f3b2c4-5e6f-4a7b-8c9d-0e1f2a3b4c5d" ;
127196 const RESET_ID = "f0e1d2c3-b4a5-6789-0abc-def123456789" ;
197+ const AFE_LINEAR_GAIN_ID = "a1b2c3d4-e5f6-4789-0abc-def123456789" ;
198+ const AGC_TARGET_LEVEL_ID = "b2c3d4e5-f6a7-4890-1bcd-ef2345678901" ;
199+ const AGC_COMPRESSION_GAIN_ID = "c3d4e5f6-a7b8-4901-2cde-f34567890123" ;
128200
129201 // global variables
130202 let device = null ;
@@ -145,6 +217,7 @@ <h3 class="font-bold text-lg">⚠️ Device Reset Required</h3>
145217 const passInput = document . getElementById ( 'passInput' ) ;
146218 const serverUrlInput = document . getElementById ( 'serverUrlInput' ) ;
147219 const ssidTitle = document . getElementById ( 'ssidTitle' ) ;
220+ const passTitle = document . getElementById ( 'passTitle' ) ;
148221 const urlTitle = document . getElementById ( 'urlTitle' ) ;
149222 const backgroundImage = document . getElementById ( 'backgroundImage' ) ;
150223 const bgPreview = document . getElementById ( 'bgPreview' ) ;
@@ -155,6 +228,22 @@ <h3 class="font-bold text-lg">⚠️ Device Reset Required</h3>
155228 const toastMessage = document . getElementById ( 'toastMessage' ) ;
156229 const resetNotSupportedModal = document . getElementById ( 'resetNotSupportedModal' ) ;
157230
231+ // AFE related DOM elements
232+ const afeLinearGainRange = document . getElementById ( 'afeLinearGainRange' ) ;
233+ const afeLinearGainValue = document . getElementById ( 'afeLinearGainValue' ) ;
234+ const saveAfeLinearGainButton = document . getElementById ( 'saveAfeLinearGainButton' ) ;
235+ const afeLinearGainTitle = document . getElementById ( 'afeLinearGainTitle' ) ;
236+
237+ const agcTargetLevelRange = document . getElementById ( 'agcTargetLevelRange' ) ;
238+ const agcTargetLevelValue = document . getElementById ( 'agcTargetLevelValue' ) ;
239+ const saveAgcTargetLevelButton = document . getElementById ( 'saveAgcTargetLevelButton' ) ;
240+ const agcTargetLevelTitle = document . getElementById ( 'agcTargetLevelTitle' ) ;
241+
242+ const agcCompressionGainRange = document . getElementById ( 'agcCompressionGainRange' ) ;
243+ const agcCompressionGainValue = document . getElementById ( 'agcCompressionGainValue' ) ;
244+ const saveAgcCompressionGainButton = document . getElementById ( 'saveAgcCompressionGainButton' ) ;
245+ const agcCompressionGainTitle = document . getElementById ( 'agcCompressionGainTitle' ) ;
246+
158247 // Track modified fields
159248 const modifiedFields = {
160249 ssid : false ,
@@ -174,7 +263,9 @@ <h3 class="font-bold text-lg">⚠️ Device Reset Required</h3>
174263 // Clear field modification mark
175264 function clearFieldModification ( fieldName , titleElement ) {
176265 modifiedFields [ fieldName ] = false ;
177- titleElement . textContent = titleElement . textContent . replace ( ' *' , '' ) ;
266+ if ( titleElement ) {
267+ titleElement . textContent = titleElement . textContent . replace ( ' *' , '' ) ;
268+ }
178269 updateSaveButtonState ( ) ;
179270 }
180271
@@ -334,6 +425,11 @@ <h3 class="font-bold text-lg">⚠️ Device Reset Required</h3>
334425 backgroundImage . disabled = false ;
335426 clearBgButton . disabled = false ;
336427 controlPanel . classList . remove ( 'opacity-50' , 'pointer-events-none' ) ;
428+
429+ // Enable AFE controls
430+ afeLinearGainRange . disabled = false ;
431+ agcTargetLevelRange . disabled = false ;
432+ agcCompressionGainRange . disabled = false ;
337433 }
338434
339435 // Disable all controls
@@ -347,6 +443,14 @@ <h3 class="font-bold text-lg">⚠️ Device Reset Required</h3>
347443 writeBgButton . disabled = true ;
348444 clearBgButton . disabled = true ;
349445 controlPanel . classList . add ( 'opacity-50' , 'pointer-events-none' ) ;
446+
447+ // Disable AFE controls
448+ afeLinearGainRange . disabled = true ;
449+ agcTargetLevelRange . disabled = true ;
450+ agcCompressionGainRange . disabled = true ;
451+ saveAfeLinearGainButton . disabled = true ;
452+ saveAgcTargetLevelButton . disabled = true ;
453+ saveAgcCompressionGainButton . disabled = true ;
350454 }
351455
352456 // Reads Characteristic
@@ -387,8 +491,14 @@ <h3 class="font-bold text-lg">⚠️ Device Reset Required</h3>
387491 await readCharacteristic ( PASS_ID , passInput ) ;
388492 await readCharacteristic ( SERVER_URL_ID , serverUrlInput ) ;
389493
494+ // Load AFE parameters
495+ await readAfeLinearGain ( ) ;
496+ await readAgcTargetLevel ( ) ;
497+ await readAgcCompressionGain ( ) ;
498+
390499 // Clear all modification marks
391500 clearFieldModification ( 'ssid' , ssidTitle ) ;
501+ clearFieldModification ( 'pass' , passTitle ) ;
392502 clearFieldModification ( 'url' , urlTitle ) ;
393503
394504 showNotification ( 'Success' , 'All configuration loaded' ) ;
@@ -431,7 +541,7 @@ <h3 class="font-bold text-lg">⚠️ Device Reset Required</h3>
431541
432542 if ( modifiedFields . pass ) {
433543 await writeCharacteristic ( PASS_ID , passInput . value ) ;
434- clearFieldModification ( 'ssid ' , ssidTitle ) ;
544+ clearFieldModification ( 'pass ' , passTitle ) ;
435545 savedCount ++ ;
436546 }
437547
@@ -596,7 +706,7 @@ <h3 class="font-bold text-lg">⚠️ Device Reset Required</h3>
596706 } ) ;
597707
598708 passInput . addEventListener ( 'input' , ( ) => {
599- markFieldAsModified ( 'pass' , ssidTitle ) ;
709+ markFieldAsModified ( 'pass' , passTitle ) ;
600710 } ) ;
601711
602712 serverUrlInput . addEventListener ( 'input' , ( ) => {
@@ -612,6 +722,173 @@ <h3 class="font-bold text-lg">⚠️ Device Reset Required</h3>
612722 showNotification ( 'Message' , 'Background image cleared' ) ;
613723 } ) ;
614724
725+ // Read AFE Linear Gain (string format f32)
726+ async function readAfeLinearGain ( ) {
727+ if ( ! isConnected || ! service ) return false ;
728+ try {
729+ const characteristic = await service . getCharacteristic ( AFE_LINEAR_GAIN_ID ) ;
730+ const value = await characteristic . readValue ( ) ;
731+ const decoder = new TextDecoder ( ) ;
732+ const stringValue = decoder . decode ( value ) ;
733+ const gain = parseFloat ( stringValue ) ;
734+ if ( ! isNaN ( gain ) ) {
735+ afeLinearGainRange . value = Math . round ( gain * 10 ) ;
736+ afeLinearGainValue . textContent = gain . toFixed ( 1 ) ;
737+ // Reset title (remove not supported label)
738+ afeLinearGainTitle . textContent = 'AFE Linear Gain' ;
739+ saveAfeLinearGainButton . disabled = true ;
740+ }
741+ return true ;
742+ } catch ( error ) {
743+ console . error ( 'Failed to read AFE Linear Gain:' , error ) ;
744+ // Backward compatibility: disable this control
745+ afeLinearGainRange . disabled = true ;
746+ saveAfeLinearGainButton . disabled = true ;
747+ afeLinearGainTitle . textContent = 'AFE Linear Gain (Not Supported)' ;
748+ return false ;
749+ }
750+ }
751+
752+ // Read AGC Target Level (i32, 4 bytes little endian)
753+ async function readAgcTargetLevel ( ) {
754+ if ( ! isConnected || ! service ) return false ;
755+ try {
756+ const characteristic = await service . getCharacteristic ( AGC_TARGET_LEVEL_ID ) ;
757+ const value = await characteristic . readValue ( ) ;
758+ // value is already DataView, use directly
759+ const level = value . getInt32 ( 0 , true ) ;
760+ agcTargetLevelRange . value = level ;
761+ agcTargetLevelValue . textContent = level ;
762+ // Reset title (remove not supported label)
763+ agcTargetLevelTitle . textContent = 'AGC Target Level' ;
764+ saveAgcTargetLevelButton . disabled = true ;
765+ return true ;
766+ } catch ( error ) {
767+ console . error ( 'Failed to read AGC Target Level:' , error ) ;
768+ // Backward compatibility: disable this control
769+ agcTargetLevelRange . disabled = true ;
770+ saveAgcTargetLevelButton . disabled = true ;
771+ agcTargetLevelTitle . textContent = 'AGC Target Level (Not Supported)' ;
772+ return false ;
773+ }
774+ }
775+
776+ // Read AGC Compression Gain (i32, 4 bytes little endian)
777+ async function readAgcCompressionGain ( ) {
778+ if ( ! isConnected || ! service ) return false ;
779+ try {
780+ const characteristic = await service . getCharacteristic ( AGC_COMPRESSION_GAIN_ID ) ;
781+ const value = await characteristic . readValue ( ) ;
782+ // value is already DataView, use directly
783+ const gain = value . getInt32 ( 0 , true ) ;
784+ agcCompressionGainRange . value = gain ;
785+ agcCompressionGainValue . textContent = gain ;
786+ // Reset title (remove not supported label)
787+ agcCompressionGainTitle . textContent = 'AGC Compression Gain' ;
788+ saveAgcCompressionGainButton . disabled = true ;
789+ return true ;
790+ } catch ( error ) {
791+ console . error ( 'Failed to read AGC Compression Gain:' , error ) ;
792+ // Backward compatibility: disable this control
793+ agcCompressionGainRange . disabled = true ;
794+ saveAgcCompressionGainButton . disabled = true ;
795+ agcCompressionGainTitle . textContent = 'AGC Compression Gain (Not Supported)' ;
796+ return false ;
797+ }
798+ }
799+
800+ // AFE Linear Gain save function
801+ async function saveAfeLinearGain ( ) {
802+ if ( ! isConnected || ! service ) {
803+ showNotification ( 'Error' , 'Device not connected' , true ) ;
804+ return ;
805+ }
806+ try {
807+ const gain = parseFloat ( ( afeLinearGainRange . value / 10 ) . toFixed ( 1 ) ) ;
808+ const characteristic = await service . getCharacteristic ( AFE_LINEAR_GAIN_ID ) ;
809+ const encoder = new TextEncoder ( ) ;
810+ const data = encoder . encode ( gain . toString ( ) ) ;
811+ await characteristic . writeValue ( data ) ;
812+ showNotification ( 'Success' , `AFE Linear Gain set to ${ gain } ` ) ;
813+ clearFieldModification ( 'afeLinearGain' , afeLinearGainTitle ) ;
814+ saveAfeLinearGainButton . disabled = true ;
815+ } catch ( error ) {
816+ console . error ( 'Failed to save AFE Linear Gain:' , error ) ;
817+ showNotification ( 'Error' , 'Failed to save AFE Linear Gain: ' + error . message , true ) ;
818+ }
819+ }
820+
821+ // AGC Target Level save function
822+ async function saveAgcTargetLevel ( ) {
823+ if ( ! isConnected || ! service ) {
824+ showNotification ( 'Error' , 'Device not connected' , true ) ;
825+ return ;
826+ }
827+ try {
828+ const level = parseInt ( agcTargetLevelRange . value ) ;
829+ const characteristic = await service . getCharacteristic ( AGC_TARGET_LEVEL_ID ) ;
830+ const data = new ArrayBuffer ( 4 ) ;
831+ const view = new DataView ( data ) ;
832+ view . setInt32 ( 0 , level , true ) ;
833+ await characteristic . writeValue ( data ) ;
834+ showNotification ( 'Success' , `AGC Target Level set to ${ level } ` ) ;
835+ clearFieldModification ( 'agcTargetLevel' , agcTargetLevelTitle ) ;
836+ saveAgcTargetLevelButton . disabled = true ;
837+ } catch ( error ) {
838+ console . error ( 'Failed to save AGC Target Level:' , error ) ;
839+ showNotification ( 'Error' , 'Failed to save AGC Target Level: ' + error . message , true ) ;
840+ }
841+ }
842+
843+ // AGC Compression Gain save function
844+ async function saveAgcCompressionGain ( ) {
845+ if ( ! isConnected || ! service ) {
846+ showNotification ( 'Error' , 'Device not connected' , true ) ;
847+ return ;
848+ }
849+ try {
850+ const gain = parseInt ( agcCompressionGainRange . value ) ;
851+ const characteristic = await service . getCharacteristic ( AGC_COMPRESSION_GAIN_ID ) ;
852+ const data = new ArrayBuffer ( 4 ) ;
853+ const view = new DataView ( data ) ;
854+ view . setInt32 ( 0 , gain , true ) ;
855+ await characteristic . writeValue ( data ) ;
856+ showNotification ( 'Success' , `AGC Compression Gain set to ${ gain } ` ) ;
857+ clearFieldModification ( 'agcCompressionGain' , agcCompressionGainTitle ) ;
858+ saveAgcCompressionGainButton . disabled = true ;
859+ } catch ( error ) {
860+ console . error ( 'Failed to save AGC Compression Gain:' , error ) ;
861+ showNotification ( 'Error' , 'Failed to save AGC Compression Gain: ' + error . message , true ) ;
862+ }
863+ }
864+
865+ // AFE Linear Gain slider event
866+ afeLinearGainRange . addEventListener ( 'input' , ( ) => {
867+ const gain = ( afeLinearGainRange . value / 10 ) . toFixed ( 1 ) ;
868+ afeLinearGainValue . textContent = gain ;
869+ markFieldAsModified ( 'afeLinearGain' , afeLinearGainTitle ) ;
870+ saveAfeLinearGainButton . disabled = false ;
871+ } ) ;
872+
873+ // AGC Target Level slider event
874+ agcTargetLevelRange . addEventListener ( 'input' , ( ) => {
875+ agcTargetLevelValue . textContent = agcTargetLevelRange . value ;
876+ markFieldAsModified ( 'agcTargetLevel' , agcTargetLevelTitle ) ;
877+ saveAgcTargetLevelButton . disabled = false ;
878+ } ) ;
879+
880+ // AGC Compression Gain slider event
881+ agcCompressionGainRange . addEventListener ( 'input' , ( ) => {
882+ agcCompressionGainValue . textContent = agcCompressionGainRange . value ;
883+ markFieldAsModified ( 'agcCompressionGain' , agcCompressionGainTitle ) ;
884+ saveAgcCompressionGainButton . disabled = false ;
885+ } ) ;
886+
887+ // AFE save button events
888+ saveAfeLinearGainButton . addEventListener ( 'click' , saveAfeLinearGain ) ;
889+ saveAgcTargetLevelButton . addEventListener ( 'click' , saveAgcTargetLevel ) ;
890+ saveAgcCompressionGainButton . addEventListener ( 'click' , saveAgcCompressionGain ) ;
891+
615892 if ( ! navigator . bluetooth ) {
616893 showNotification ( 'Error' , 'Your browser does not support the Web Bluetooth API. Please use Chrome or Edge' , true ) ;
617894 connectButton . disabled = true ;
@@ -622,4 +899,4 @@ <h3 class="font-bold text-lg">⚠️ Device Reset Required</h3>
622899 </ script >
623900</ body >
624901
625- </ html >
902+ </ html >
0 commit comments