| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  | 'use strict'; | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  | (async () => { | 
					
						
							|  |  |  |  |   // bind to the html
 | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |   const uiBody = document.body; | 
					
						
							|  |  |  |  |   const uiToggleThemeBtn = document.getElementById('theme-toggle'); | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |   const uiConnectWebUsbSerialBtn = document.getElementById('connect_webusb_serial_btn'); | 
					
						
							|  |  |  |  |   const uiConnectSerialBtn = document.getElementById('connect_serial_btn'); | 
					
						
							|  |  |  |  |   const uiDisconnectBtn = document.getElementById('disconnect_btn'); | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |   const uiNewlineModeSelect = document.getElementById('newline_mode_select'); | 
					
						
							|  |  |  |  |   const uiAutoReconnectCheckbox = document.getElementById('auto_reconnect_checkbox'); | 
					
						
							|  |  |  |  |   const uiForgetDeviceBtn = document.getElementById('forget_device_btn'); | 
					
						
							|  |  |  |  |   const uiForgetAllDevicesBtn = document.getElementById('forget_all_devices_btn'); | 
					
						
							|  |  |  |  |   const uiResetAllBtn = document.getElementById('reset_all_btn'); | 
					
						
							|  |  |  |  |   const uiCopyOutputBtn = document.getElementById('copy_output_btn'); | 
					
						
							|  |  |  |  |   const uiDownloadOutputCsvBtn = document.getElementById('download_csv_output_btn'); | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |   const uiStatusSpan = document.getElementById('status_span'); | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |   const uiCommandHistoryClearBtn = document.getElementById('clear_command_history_btn'); | 
					
						
							|  |  |  |  |   const uiCommandHistoryScrollbox = document.getElementById('command_history_scrollbox'); | 
					
						
							|  |  |  |  |   const uiCommandLineInput = document.getElementById('command_line_input'); | 
					
						
							|  |  |  |  |   const uiSendModeBtn = document.getElementById('send_mode_btn'); | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |   const uiReceivedDataClearBtn = document.getElementById('clear_received_data_btn'); | 
					
						
							|  |  |  |  |   const uiReceivedDataScrollbox = document.getElementById('received_data_scrollbox'); | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |   const uiNearTheBottomThreshold = 100; // pixels from the bottom to trigger scroll
 | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |   const maxCommandHistoryLength = 123; // max number of command history entries
 | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |   const maxReceivedDataLength = 8192 / 8; // max number of received data entries
 | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |   const THEME_STATES = ['auto', 'light', 'dark']; | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-11 12:41:09 +02:00
										 |  |  |  |   /// https://stackoverflow.com/a/6234804/4479969
 | 
					
						
							|  |  |  |  |   const escapeHtml = unsafe => { | 
					
						
							|  |  |  |  |     if (typeof unsafe !== 'string') unsafe = String(unsafe); | 
					
						
							|  |  |  |  |     return unsafe | 
					
						
							|  |  |  |  |       .replaceAll("&", "&") | 
					
						
							|  |  |  |  |       .replaceAll("<", "<") | 
					
						
							|  |  |  |  |       .replaceAll(">", ">") | 
					
						
							|  |  |  |  |       .replaceAll('"', """) | 
					
						
							|  |  |  |  |       .replaceAll("'", "'"); | 
					
						
							|  |  |  |  |   }; | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |   class CommandHistoryEntry { | 
					
						
							|  |  |  |  |     constructor(text) { | 
					
						
							|  |  |  |  |       this.text = text; | 
					
						
							|  |  |  |  |       this.time = Date.now(); | 
					
						
							|  |  |  |  |       this.count = 1; | 
					
						
							|  |  |  |  |     } | 
					
						
							|  |  |  |  |   } | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |   class ReceivedDataEntry { | 
					
						
							|  |  |  |  |     constructor(text) { | 
					
						
							|  |  |  |  |       this.text = text; | 
					
						
							|  |  |  |  |       this.time = Date.now(); | 
					
						
							|  |  |  |  |       this.terminated = false; | 
					
						
							|  |  |  |  |     } | 
					
						
							|  |  |  |  |   } | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |   class Application { | 
					
						
							|  |  |  |  |     constructor() { | 
					
						
							|  |  |  |  |       this.currentPort = null; | 
					
						
							|  |  |  |  |       this.textEncoder = new TextEncoder(); | 
					
						
							|  |  |  |  |       this.textDecoder = new TextDecoder(); | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |       this.reconnectTimeoutId = null; | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |       this.commandHistory = []; | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |       this.uiCommandHistoryIndex = -1; | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |       this.receivedData = []; | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  | 
 | 
					
						
							|  |  |  |  |       // bind the UI elements
 | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |       uiToggleThemeBtn.addEventListener('click', () => this.toggleTheme()); | 
					
						
							|  |  |  |  |       // Listener for OS Theme Changes
 | 
					
						
							|  |  |  |  |       window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (e) => { | 
					
						
							|  |  |  |  |         const currentPreference = localStorage.getItem('theme') || 'auto'; | 
					
						
							|  |  |  |  |         // Only act if the user is in automatic mode
 | 
					
						
							|  |  |  |  |         if (currentPreference === 'auto') { | 
					
						
							|  |  |  |  |           this.setTheme('auto'); | 
					
						
							|  |  |  |  |         } | 
					
						
							|  |  |  |  |       }); | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |       uiConnectWebUsbSerialBtn.addEventListener('click', () => this.connectWebUsbSerialPort()); | 
					
						
							|  |  |  |  |       uiConnectSerialBtn.addEventListener('click', () => this.connectSerialPort()); | 
					
						
							|  |  |  |  |       uiDisconnectBtn.addEventListener('click', () => this.disconnectPort()); | 
					
						
							|  |  |  |  |       uiNewlineModeSelect.addEventListener('change', () => this.setNewlineMode()); | 
					
						
							|  |  |  |  |       uiAutoReconnectCheckbox.addEventListener('change', () => this.autoReconnectChanged()); | 
					
						
							|  |  |  |  |       uiForgetDeviceBtn.addEventListener('click', () => this.forgetPort()); | 
					
						
							|  |  |  |  |       uiForgetAllDevicesBtn.addEventListener('click', () => this.forgetAllPorts()); | 
					
						
							|  |  |  |  |       uiResetAllBtn.addEventListener('click', () => this.resetAll()); | 
					
						
							|  |  |  |  |       uiCopyOutputBtn.addEventListener('click', () => this.copyOutput()); | 
					
						
							|  |  |  |  |       uiDownloadOutputCsvBtn.addEventListener('click', () => this.downloadOutputCsv()); | 
					
						
							|  |  |  |  |       uiCommandHistoryClearBtn.addEventListener('click', () => this.clearCommandHistory()); | 
					
						
							|  |  |  |  |       uiCommandLineInput.addEventListener('keydown', (e) => this.handleCommandLineInput(e)); | 
					
						
							|  |  |  |  |       uiSendModeBtn.addEventListener('click', () => this.toggleSendMode()); | 
					
						
							|  |  |  |  |       uiReceivedDataClearBtn.addEventListener('click', () => this.clearReceivedData()); | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-08 12:04:01 +02:00
										 |  |  |  |       window.addEventListener('beforeunload', () => this.beforeUnloadHandler()); | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |       // restore state from localStorage
 | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |       try { | 
					
						
							|  |  |  |  |         this.restoreState(); | 
					
						
							|  |  |  |  |       } catch (error) { | 
					
						
							|  |  |  |  |         console.error('Failed to restore state from localStorage', error); | 
					
						
							|  |  |  |  |         this.resetAll(); | 
					
						
							|  |  |  |  |         this.restoreState(); | 
					
						
							|  |  |  |  |       } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |       this.updateUIConnectionState(); | 
					
						
							|  |  |  |  |       this.connectWebUsbSerialPort(true); | 
					
						
							|  |  |  |  |     } | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-08 12:04:01 +02:00
										 |  |  |  |     beforeUnloadHandler() { | 
					
						
							|  |  |  |  |       // Save the scroll position of the command history and received data
 | 
					
						
							|  |  |  |  |       localStorage.setItem('commandHistoryScrollTop', uiCommandHistoryScrollbox.scrollTop); | 
					
						
							|  |  |  |  |       localStorage.setItem('receivedDataScrollTop', uiReceivedDataScrollbox.scrollTop); | 
					
						
							|  |  |  |  |     } | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |     restoreState() { | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |       // Restore theme choice
 | 
					
						
							|  |  |  |  |       const savedTheme = localStorage.getItem('theme'); | 
					
						
							|  |  |  |  |       if (savedTheme) { | 
					
						
							|  |  |  |  |         this.setTheme(savedTheme); | 
					
						
							|  |  |  |  |       } | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |       // Restore command history
 | 
					
						
							|  |  |  |  |       let savedCommandHistory = JSON.parse(localStorage.getItem('commandHistory') || '[]'); | 
					
						
							|  |  |  |  |       for (const cmd of savedCommandHistory) { | 
					
						
							| 
									
										
										
										
											2025-07-08 12:04:01 +02:00
										 |  |  |  |         this.addCommandToHistoryUI(cmd); | 
					
						
							|  |  |  |  |       } | 
					
						
							|  |  |  |  |       // Restore scroll position for command history
 | 
					
						
							|  |  |  |  |       const commandHistoryScrollTop = localStorage.getItem('commandHistoryScrollTop'); | 
					
						
							|  |  |  |  |       if (commandHistoryScrollTop) { | 
					
						
							|  |  |  |  |         uiCommandHistoryScrollbox.scrollTop = parseInt(commandHistoryScrollTop, 10); | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |       } | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |       // Restore received data
 | 
					
						
							|  |  |  |  |       let savedReceivedData = JSON.parse(localStorage.getItem('receivedData') || '[]'); | 
					
						
							|  |  |  |  |       for (let line of savedReceivedData) { | 
					
						
							|  |  |  |  |         line.terminated = true; | 
					
						
							| 
									
										
										
										
											2025-07-08 12:04:01 +02:00
										 |  |  |  |         this.addReceivedDataEntryUI(line); | 
					
						
							|  |  |  |  |       } | 
					
						
							|  |  |  |  |       // Restore scroll position for received data
 | 
					
						
							|  |  |  |  |       const receivedDataScrollTop = localStorage.getItem('receivedDataScrollTop'); | 
					
						
							|  |  |  |  |       if (receivedDataScrollTop) { | 
					
						
							|  |  |  |  |         uiReceivedDataScrollbox.scrollTop = parseInt(receivedDataScrollTop, 10); | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |       } | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |       this.sendMode = localStorage.getItem('sendMode') || 'command'; | 
					
						
							|  |  |  |  |       this.setSendMode(this.sendMode); | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |       uiAutoReconnectCheckbox.checked = !(localStorage.getItem('autoReconnect') === 'false'); | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  | 
 | 
					
						
							|  |  |  |  |       let savedNewlineMode = localStorage.getItem('newlineMode'); | 
					
						
							|  |  |  |  |       if (savedNewlineMode) { | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |         uiNewlineModeSelect.value = savedNewlineMode; | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |       } | 
					
						
							|  |  |  |  |     } | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |     setTheme(theme) { | 
					
						
							|  |  |  |  |       const modeName = theme.charAt(0).toUpperCase() + theme.slice(1); | 
					
						
							|  |  |  |  |       uiToggleThemeBtn.textContent = `Theme: ${modeName}`; | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |       if (theme === 'auto') { | 
					
						
							|  |  |  |  |         // In auto mode, we rely on the OS preference.
 | 
					
						
							|  |  |  |  |         // We check the media query and add/remove the class accordingly.
 | 
					
						
							|  |  |  |  |         const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches; | 
					
						
							|  |  |  |  |         if (prefersDark) { | 
					
						
							|  |  |  |  |           uiBody.classList.add('dark-mode'); | 
					
						
							|  |  |  |  |         } else { | 
					
						
							|  |  |  |  |           uiBody.classList.remove('dark-mode'); | 
					
						
							|  |  |  |  |         } | 
					
						
							|  |  |  |  |       } else if (theme === 'light') { | 
					
						
							|  |  |  |  |         // Force light mode by removing the class.
 | 
					
						
							|  |  |  |  |         uiBody.classList.remove('dark-mode'); | 
					
						
							|  |  |  |  |       } else if (theme === 'dark') { | 
					
						
							|  |  |  |  |         // Force dark mode by adding the class.
 | 
					
						
							|  |  |  |  |         uiBody.classList.add('dark-mode'); | 
					
						
							|  |  |  |  |       } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |       // Save the theme to localStorage
 | 
					
						
							|  |  |  |  |       localStorage.setItem('theme', theme); | 
					
						
							|  |  |  |  |     } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |     toggleTheme() { | 
					
						
							|  |  |  |  |       const currentTheme = localStorage.getItem('theme') || 'auto'; | 
					
						
							|  |  |  |  |       const nextThemeIndex = (THEME_STATES.indexOf(currentTheme) + 1) % THEME_STATES.length; | 
					
						
							|  |  |  |  |       const nextTheme = THEME_STATES[nextThemeIndex]; | 
					
						
							|  |  |  |  |       this.setTheme(nextTheme); | 
					
						
							|  |  |  |  |     } | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-08 12:04:01 +02:00
										 |  |  |  |     addCommandToHistoryUI(commandHistoryEntry) { | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |       let commandHistoryEntryBtn = null; | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |       let lastCommandMatched = false; | 
					
						
							|  |  |  |  |       if (this.commandHistory.length > 0) { | 
					
						
							|  |  |  |  |         let lastCommandEntry = this.commandHistory[this.commandHistory.length - 1]; | 
					
						
							|  |  |  |  |         if (lastCommandEntry.text === commandHistoryEntry.text) { | 
					
						
							|  |  |  |  |           lastCommandEntry.count++; | 
					
						
							|  |  |  |  |           lastCommandEntry.time = Date.now(); | 
					
						
							|  |  |  |  |           lastCommandMatched = true; | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |           // Update the last command entry
 | 
					
						
							|  |  |  |  |           commandHistoryEntryBtn = uiCommandHistoryScrollbox.lastElementChild; | 
					
						
							|  |  |  |  |           let time_str = new Date(lastCommandEntry.time).toLocaleString(); | 
					
						
							|  |  |  |  |           commandHistoryEntryBtn.querySelector('.command-history-entry-time').textContent = time_str; | 
					
						
							|  |  |  |  |           commandHistoryEntryBtn.querySelector('.command-history-entry-text').textContent = lastCommandEntry.text; | 
					
						
							|  |  |  |  |           commandHistoryEntryBtn.querySelector('.command-history-entry-count').textContent = '×' + lastCommandEntry.count; | 
					
						
							|  |  |  |  |         } | 
					
						
							|  |  |  |  |       } | 
					
						
							|  |  |  |  |       if (!lastCommandMatched) { | 
					
						
							|  |  |  |  |         this.commandHistory.push(commandHistoryEntry); | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |         // Create a new command history entry
 | 
					
						
							|  |  |  |  |         commandHistoryEntryBtn = document.createElement('button'); | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |         commandHistoryEntryBtn.className = 'command-history-entry'; | 
					
						
							|  |  |  |  |         commandHistoryEntryBtn.type = 'button'; | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |         let time_str = new Date(commandHistoryEntry.time).toLocaleString(); | 
					
						
							|  |  |  |  |         commandHistoryEntryBtn.innerHTML = `
 | 
					
						
							| 
									
										
										
										
											2025-07-11 12:41:09 +02:00
										 |  |  |  |           <span class="command-history-entry-time">${escapeHtml(time_str)}</span> | 
					
						
							|  |  |  |  |           <span class="command-history-entry-text">${escapeHtml(commandHistoryEntry.text)}</span> | 
					
						
							|  |  |  |  |           <span class="command-history-entry-count">×${escapeHtml(commandHistoryEntry.count)}</span> | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |         `;
 | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |         commandHistoryEntryBtn.addEventListener('click', () => { | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |           if (uiCommandLineInput.disabled) return; | 
					
						
							|  |  |  |  |           uiCommandLineInput.value = commandHistoryEntry.text; | 
					
						
							|  |  |  |  |           uiCommandLineInput.focus(); | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |         }); | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |         uiCommandHistoryScrollbox.appendChild(commandHistoryEntryBtn); | 
					
						
							|  |  |  |  |       } | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |       // Limit the command history length
 | 
					
						
							|  |  |  |  |       while (this.commandHistory.length > maxCommandHistoryLength) { | 
					
						
							|  |  |  |  |         this.commandHistory.shift(); | 
					
						
							|  |  |  |  |         uiCommandHistoryScrollbox.removeChild(uiCommandHistoryScrollbox.firstElementChild); | 
					
						
							|  |  |  |  |       } | 
					
						
							| 
									
										
										
										
											2025-07-08 12:04:01 +02:00
										 |  |  |  |     } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |     appendNewCommandToHistory(commandHistoryEntry) { | 
					
						
							|  |  |  |  |       const wasNearBottom = this.isNearBottom(uiCommandHistoryScrollbox); | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |       this.addCommandToHistoryUI(commandHistoryEntry); | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  | 
 | 
					
						
							|  |  |  |  |       // Save the command history to localStorage
 | 
					
						
							|  |  |  |  |       localStorage.setItem('commandHistory', JSON.stringify(this.commandHistory)); | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |       // Scroll to the new entry if near the bottom
 | 
					
						
							|  |  |  |  |       if (wasNearBottom) { | 
					
						
							| 
									
										
										
										
											2025-07-08 12:04:01 +02:00
										 |  |  |  |         this.scrollToBottom(uiCommandHistoryScrollbox); | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |       } | 
					
						
							|  |  |  |  |     } | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |     clearCommandHistory() { | 
					
						
							|  |  |  |  |       this.commandHistory = []; | 
					
						
							| 
									
										
										
										
											2025-07-11 12:41:09 +02:00
										 |  |  |  |       uiCommandHistoryScrollbox.textContent = ''; | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |       localStorage.removeItem('commandHistory'); | 
					
						
							|  |  |  |  |       this.setStatus('Command history cleared', 'info'); | 
					
						
							|  |  |  |  |     } | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-08 12:04:01 +02:00
										 |  |  |  |     isNearBottom(container) { | 
					
						
							|  |  |  |  |       return container.scrollHeight - container.scrollTop <= container.clientHeight + uiNearTheBottomThreshold; | 
					
						
							|  |  |  |  |     } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |     scrollToBottom(container) { | 
					
						
							|  |  |  |  |       requestAnimationFrame(() => { | 
					
						
							|  |  |  |  |         container.scrollTop = container.scrollHeight; | 
					
						
							|  |  |  |  |       }); | 
					
						
							|  |  |  |  |     } | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-08 12:04:01 +02:00
										 |  |  |  |     addReceivedDataEntryUI(receivedDataEntry) { | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |       let newReceivedDataEntries = []; | 
					
						
							|  |  |  |  |       let updateLastReceivedDataEntry = false; | 
					
						
							|  |  |  |  |       if (this.receivedData.length <= 0) { | 
					
						
							|  |  |  |  |         newReceivedDataEntries.push(receivedDataEntry); | 
					
						
							|  |  |  |  |       } else { | 
					
						
							|  |  |  |  |         let lastReceivedDataEntry = this.receivedData[this.receivedData.length - 1]; | 
					
						
							|  |  |  |  |         // Check if the last entry is terminated
 | 
					
						
							|  |  |  |  |         if (lastReceivedDataEntry.terminated) { | 
					
						
							|  |  |  |  |           newReceivedDataEntries.push(receivedDataEntry); | 
					
						
							|  |  |  |  |         } else { | 
					
						
							|  |  |  |  |           if (!lastReceivedDataEntry.terminated) { | 
					
						
							|  |  |  |  |             updateLastReceivedDataEntry = true; | 
					
						
							|  |  |  |  |             this.receivedData.pop(); | 
					
						
							|  |  |  |  |             receivedDataEntry.text = lastReceivedDataEntry.text + receivedDataEntry.text; | 
					
						
							|  |  |  |  |           } | 
					
						
							|  |  |  |  |           // split the text into lines
 | 
					
						
							|  |  |  |  |           let lines = receivedDataEntry.text.split(/\r?\n/); | 
					
						
							|  |  |  |  |           // check if the last line is terminated by checking if it ends with an empty string
 | 
					
						
							|  |  |  |  |           let lastLineTerminated = lines[lines.length - 1] === ''; | 
					
						
							|  |  |  |  |           if (lastLineTerminated) { | 
					
						
							|  |  |  |  |             lines.pop(); // remove the last empty line
 | 
					
						
							|  |  |  |  |           } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |           // create new entries for each line
 | 
					
						
							|  |  |  |  |           for (let i = 0; i < lines.length; i++) { | 
					
						
							|  |  |  |  |             let line = lines[i]; | 
					
						
							|  |  |  |  |             let entry = new ReceivedDataEntry(line); | 
					
						
							|  |  |  |  |             if (i === lines.length - 1) { | 
					
						
							|  |  |  |  |               entry.terminated = lastLineTerminated; | 
					
						
							|  |  |  |  |             } else { | 
					
						
							|  |  |  |  |               entry.terminated = true; | 
					
						
							|  |  |  |  |             } | 
					
						
							|  |  |  |  |             newReceivedDataEntries.push(entry); | 
					
						
							|  |  |  |  |           } | 
					
						
							|  |  |  |  |           // if the last line is terminated, modify the last entry
 | 
					
						
							|  |  |  |  |           if (lastLineTerminated) { | 
					
						
							|  |  |  |  |             newReceivedDataEntries[newReceivedDataEntries.length - 1].terminated = true; | 
					
						
							|  |  |  |  |           } else { | 
					
						
							|  |  |  |  |             newReceivedDataEntries[newReceivedDataEntries.length - 1].terminated = false; | 
					
						
							|  |  |  |  |           } | 
					
						
							|  |  |  |  |         } | 
					
						
							|  |  |  |  |       } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |       this.receivedData.push(...newReceivedDataEntries); | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |       if (updateLastReceivedDataEntry) { | 
					
						
							|  |  |  |  |         // update the rendering of the last entry
 | 
					
						
							|  |  |  |  |         let lastReceivedDataEntryBtn = uiReceivedDataScrollbox.lastElementChild; | 
					
						
							|  |  |  |  |         lastReceivedDataEntryBtn.querySelector('.received-data-entry-text').textContent = newReceivedDataEntries[0].text; | 
					
						
							|  |  |  |  |         lastReceivedDataEntryBtn.querySelector('.received-data-entry-time').textContent = new Date(newReceivedDataEntries[0].time).toLocaleString(); | 
					
						
							|  |  |  |  |         newReceivedDataEntries.shift(); | 
					
						
							|  |  |  |  |       } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |       // render the new entries
 | 
					
						
							|  |  |  |  |       let documentFragment = document.createDocumentFragment(); | 
					
						
							|  |  |  |  |       for (const entry of newReceivedDataEntries) { | 
					
						
							|  |  |  |  |         let receivedDataEntryBtn = document.createElement('div'); | 
					
						
							|  |  |  |  |         receivedDataEntryBtn.className = 'received-data-entry'; | 
					
						
							|  |  |  |  |         receivedDataEntryBtn.innerHTML = `
 | 
					
						
							| 
									
										
										
										
											2025-07-11 12:41:09 +02:00
										 |  |  |  |           <span class="received-data-entry-time">${escapeHtml(new Date(entry.time).toLocaleString())}</span> | 
					
						
							|  |  |  |  |           <span class="received-data-entry-text">${escapeHtml(entry.text)}</span> | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |         `;
 | 
					
						
							|  |  |  |  |         documentFragment.appendChild(receivedDataEntryBtn); | 
					
						
							|  |  |  |  |       } | 
					
						
							|  |  |  |  |       uiReceivedDataScrollbox.appendChild(documentFragment); | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |       // Limit the received data length
 | 
					
						
							|  |  |  |  |       while (this.receivedData.length > maxReceivedDataLength) { | 
					
						
							|  |  |  |  |         this.receivedData.shift(); | 
					
						
							|  |  |  |  |         uiReceivedDataScrollbox.removeChild(uiReceivedDataScrollbox.firstElementChild); | 
					
						
							|  |  |  |  |       } | 
					
						
							| 
									
										
										
										
											2025-07-08 12:04:01 +02:00
										 |  |  |  |     } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |     appendNewReceivedData(receivedDataEntry) { | 
					
						
							|  |  |  |  |       const wasNearBottom = this.isNearBottom(uiReceivedDataScrollbox); | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |       this.addReceivedDataEntryUI(receivedDataEntry); | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  | 
 | 
					
						
							|  |  |  |  |       // Save the received data to localStorage
 | 
					
						
							|  |  |  |  |       localStorage.setItem('receivedData', JSON.stringify(this.receivedData)); | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  | 
 | 
					
						
							|  |  |  |  |       // Scroll to the new entry if near the bottom
 | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |       if (wasNearBottom) { | 
					
						
							| 
									
										
										
										
											2025-07-08 12:04:01 +02:00
										 |  |  |  |         this.scrollToBottom(uiReceivedDataScrollbox); | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |       } | 
					
						
							|  |  |  |  |     } | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |     clearReceivedData() { | 
					
						
							|  |  |  |  |       this.receivedData = []; | 
					
						
							| 
									
										
										
										
											2025-07-11 12:41:09 +02:00
										 |  |  |  |       uiReceivedDataScrollbox.textContent = ''; | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |       localStorage.removeItem('receivedData'); | 
					
						
							|  |  |  |  |       this.setStatus('Received data cleared', 'info'); | 
					
						
							|  |  |  |  |     } | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |     setStatus(msg, level = 'info') { | 
					
						
							| 
									
										
										
										
											2025-07-29 15:51:54 +02:00
										 |  |  |  |       console.error(msg); | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |       uiStatusSpan.textContent = msg; | 
					
						
							|  |  |  |  |       uiStatusSpan.className = 'status status-' + level; | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |     } | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-08 11:31:57 +02:00
										 |  |  |  |     /// force_connected is used to instantly change the UI to the connected state while the device is still connecting
 | 
					
						
							|  |  |  |  |     /// Otherwise we would have to wait for the connection to be established.
 | 
					
						
							|  |  |  |  |     /// This can take until the device sends the first data packet.
 | 
					
						
							|  |  |  |  |     updateUIConnectionState(force_connected = false) { | 
					
						
							|  |  |  |  |       if (force_connected || (this.currentPort && this.currentPort.isConnected)) { | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |         uiConnectWebUsbSerialBtn.style.display = 'none'; | 
					
						
							|  |  |  |  |         uiConnectSerialBtn.style.display = 'none'; | 
					
						
							|  |  |  |  |         uiDisconnectBtn.style.display = 'block'; | 
					
						
							|  |  |  |  |         uiCommandLineInput.disabled = false; | 
					
						
							| 
									
										
										
										
											2025-07-24 23:58:54 +02:00
										 |  |  |  | 
 | 
					
						
							|  |  |  |  |         if (this.currentPort instanceof SerialPort) { | 
					
						
							|  |  |  |  |           uiDisconnectBtn.textContent = 'Disconnect from WebSerial'; | 
					
						
							|  |  |  |  |         } else if (this.currentPort instanceof WebUsbSerialPort) { | 
					
						
							|  |  |  |  |           uiDisconnectBtn.textContent = 'Disconnect from WebUSB'; | 
					
						
							|  |  |  |  |         } else { | 
					
						
							|  |  |  |  |           uiDisconnectBtn.textContent = 'Disconnect'; | 
					
						
							|  |  |  |  |         } | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |       } else { | 
					
						
							|  |  |  |  |         if (serial.isWebUsbSupported()) { | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |           uiConnectWebUsbSerialBtn.style.display = 'block'; | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |         } | 
					
						
							|  |  |  |  |         if (serial.isWebSerialSupported()) { | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |           uiConnectSerialBtn.style.display = 'block'; | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |         } | 
					
						
							|  |  |  |  |         if (!serial.isWebUsbSupported() && !serial.isWebSerialSupported()) { | 
					
						
							|  |  |  |  |           this.setStatus('Your browser does not support WebUSB or WebSerial', 'error'); | 
					
						
							|  |  |  |  |         } | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |         uiDisconnectBtn.style.display = 'none'; | 
					
						
							|  |  |  |  |         uiCommandLineInput.disabled = true; | 
					
						
							|  |  |  |  |         uiCommandLineInput.value = ''; | 
					
						
							|  |  |  |  |         uiCommandLineInput.blur(); | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |       } | 
					
						
							|  |  |  |  |     } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |     async disconnectPort() { | 
					
						
							|  |  |  |  |       this.stopAutoReconnect(); | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  | 
 | 
					
						
							|  |  |  |  |       if (!this.currentPort) { | 
					
						
							|  |  |  |  |         this.updateUIConnectionState(); | 
					
						
							|  |  |  |  |         return; | 
					
						
							|  |  |  |  |       }; | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  | 
 | 
					
						
							|  |  |  |  |       try { | 
					
						
							|  |  |  |  |         await this.currentPort.disconnect(); | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |         this.setStatus('Disconnected', 'info'); | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |       } | 
					
						
							|  |  |  |  |       catch (error) { | 
					
						
							|  |  |  |  |         this.setStatus(`Disconnect error: ${error.message}`, 'error'); | 
					
						
							|  |  |  |  |       } | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |       this.updateUIConnectionState(); | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |     } | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  | 
 | 
					
						
							|  |  |  |  |     async onReceive(dataView) { | 
					
						
							|  |  |  |  |       this.updateUIConnectionState(); | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |       let text = this.textDecoder.decode(dataView); | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |       let receivedDataEntry = new ReceivedDataEntry(text); | 
					
						
							| 
									
										
										
										
											2025-07-08 12:04:01 +02:00
										 |  |  |  |       this.appendNewReceivedData(receivedDataEntry); | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |     } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |     async onReceiveError(error) { | 
					
						
							|  |  |  |  |       this.setStatus(`Read error: ${error.message}`, 'error'); | 
					
						
							|  |  |  |  |       await this.disconnectPort(); | 
					
						
							|  |  |  |  |       // Start auto reconnect on error if enabled
 | 
					
						
							|  |  |  |  |       this.tryAutoReconnect(); | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |     } | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  | 
 | 
					
						
							|  |  |  |  |     async connectSerialPort() { | 
					
						
							|  |  |  |  |       if (!serial.isWebSerialSupported()) { | 
					
						
							|  |  |  |  |         this.setStatus('Serial not supported on this browser', 'error'); | 
					
						
							|  |  |  |  |         return; | 
					
						
							|  |  |  |  |       } | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |       try { | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |         this.setStatus('Requesting device...', 'info'); | 
					
						
							|  |  |  |  |         this.currentPort = await serial.requestSerialPort(); | 
					
						
							| 
									
										
										
										
											2025-07-08 11:31:57 +02:00
										 |  |  |  |         this.updateUIConnectionState(true); | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |         this.currentPort.onReceiveError = error => this.onReceiveError(error); | 
					
						
							|  |  |  |  |         this.currentPort.onReceive = dataView => this.onReceive(dataView); | 
					
						
							|  |  |  |  |         await this.currentPort.connect(); | 
					
						
							|  |  |  |  |         this.setStatus('Connected', 'info'); | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |       } catch (error) { | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |         this.setStatus(`Connection failed: ${error.message}`, 'error'); | 
					
						
							|  |  |  |  |         if (this.currentPort) { | 
					
						
							|  |  |  |  |           await this.currentPort.forgetDevice(); | 
					
						
							|  |  |  |  |           this.currentPort = null; | 
					
						
							|  |  |  |  |         } | 
					
						
							| 
									
										
										
										
											2025-07-24 23:58:54 +02:00
										 |  |  |  |       } finally { | 
					
						
							|  |  |  |  |         this.updateUIConnectionState(); | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |       } | 
					
						
							|  |  |  |  |     } | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  | 
 | 
					
						
							|  |  |  |  |     async connectWebUsbSerialPort(initial = false) { | 
					
						
							|  |  |  |  |       if (!serial.isWebUsbSupported()) { | 
					
						
							|  |  |  |  |         this.setStatus('WebUSB not supported on this browser', 'error'); | 
					
						
							|  |  |  |  |         return; | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |       } | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |       try { | 
					
						
							|  |  |  |  |         let first_time_connection = false; | 
					
						
							|  |  |  |  |         let grantedDevices = await serial.getWebUsbSerialPorts(); | 
					
						
							|  |  |  |  |         if (initial) { | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |           if (!uiAutoReconnectCheckbox.checked || grantedDevices.length === 0) { | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |             return false; | 
					
						
							|  |  |  |  |           } | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  | 
 | 
					
						
							|  |  |  |  |           // Connect to the device that was saved to localStorage otherwise use the first one
 | 
					
						
							|  |  |  |  |           const savedPortInfo = JSON.parse(localStorage.getItem('webUSBSerialPort')); | 
					
						
							|  |  |  |  |           if (savedPortInfo) { | 
					
						
							|  |  |  |  |             for (const device of grantedDevices) { | 
					
						
							| 
									
										
										
										
											2025-07-24 23:58:54 +02:00
										 |  |  |  |               if (device._device.vendorId === savedPortInfo.vendorId && device._device.productId === savedPortInfo.productId) { | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |                 this.currentPort = device; | 
					
						
							|  |  |  |  |                 break; | 
					
						
							|  |  |  |  |               } | 
					
						
							|  |  |  |  |             } | 
					
						
							|  |  |  |  |           } | 
					
						
							|  |  |  |  |           if (!this.currentPort) { | 
					
						
							|  |  |  |  |             this.currentPort = grantedDevices[0]; | 
					
						
							|  |  |  |  |           } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |           this.setStatus('Connecting to first device...', 'info'); | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |         } else { | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |           // Prompt the user to select a device
 | 
					
						
							|  |  |  |  |           this.setStatus('Requesting device...', 'info'); | 
					
						
							|  |  |  |  |           this.currentPort = await serial.requestWebUsbSerialPort(); | 
					
						
							|  |  |  |  |           first_time_connection = true; | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |         } | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |         this.currentPort.onReceiveError = error => this.onReceiveError(error); | 
					
						
							|  |  |  |  |         this.currentPort.onReceive = dataView => this.onReceive(dataView); | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |         try { | 
					
						
							| 
									
										
										
										
											2025-07-08 11:31:57 +02:00
										 |  |  |  |           this.updateUIConnectionState(true); | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |           await this.currentPort.connect(); | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |           // save the port to localStorage
 | 
					
						
							|  |  |  |  |           const portInfo = { | 
					
						
							| 
									
										
										
										
											2025-07-24 23:58:54 +02:00
										 |  |  |  |             vendorId: this.currentPort._device.vendorId, | 
					
						
							|  |  |  |  |             productId: this.currentPort._device.productId, | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |           } | 
					
						
							|  |  |  |  |           localStorage.setItem('webUSBSerialPort', JSON.stringify(portInfo)); | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |           this.setStatus('Connected', 'info'); | 
					
						
							| 
									
										
										
										
											2025-07-24 23:58:54 +02:00
										 |  |  |  |           uiCommandLineInput.focus(); | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |         } catch (error) { | 
					
						
							|  |  |  |  |           if (first_time_connection) { | 
					
						
							|  |  |  |  |             // Forget the device if a first time connection fails
 | 
					
						
							|  |  |  |  |             await this.currentPort.forgetDevice(); | 
					
						
							|  |  |  |  |             this.currentPort = null; | 
					
						
							|  |  |  |  |           } | 
					
						
							|  |  |  |  |           throw error; | 
					
						
							| 
									
										
										
										
											2025-07-24 23:58:54 +02:00
										 |  |  |  |         } finally { | 
					
						
							|  |  |  |  |           this.updateUIConnectionState(); | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |         } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |         this.updateUIConnectionState(); | 
					
						
							|  |  |  |  |       } catch (error) { | 
					
						
							|  |  |  |  |         this.setStatus(`Connection failed: ${error.message}`, 'error'); | 
					
						
							|  |  |  |  |       } | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |     } | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  | 
 | 
					
						
							|  |  |  |  |     async reconnectPort() { | 
					
						
							|  |  |  |  |       if (this.currentPort) { | 
					
						
							|  |  |  |  |         this.setStatus('Reconnecting...', 'info'); | 
					
						
							|  |  |  |  |         try { | 
					
						
							|  |  |  |  |           await this.currentPort.connect(); | 
					
						
							|  |  |  |  |           this.setStatus('Reconnected', 'info'); | 
					
						
							|  |  |  |  |         } catch (error) { | 
					
						
							|  |  |  |  |           this.setStatus(`Reconnect failed: ${error.message}`, 'error'); | 
					
						
							| 
									
										
										
										
											2025-07-24 23:58:54 +02:00
										 |  |  |  |         } finally { | 
					
						
							|  |  |  |  |           this.updateUIConnectionState(); | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |         } | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |       } | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |       this.updateUIConnectionState(); | 
					
						
							|  |  |  |  |     } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |     async forgetPort() { | 
					
						
							|  |  |  |  |       this.stopAutoReconnect(); | 
					
						
							|  |  |  |  |       if (this.currentPort) { | 
					
						
							|  |  |  |  |         await this.currentPort.forgetDevice(); | 
					
						
							|  |  |  |  |         this.currentPort = null; | 
					
						
							|  |  |  |  |         this.setStatus('Device forgotten', 'info'); | 
					
						
							|  |  |  |  |       } else { | 
					
						
							|  |  |  |  |         this.setStatus('No device to forget', 'error'); | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |       } | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |       this.updateUIConnectionState(); | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |     } | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |     async forgetAllPorts() { | 
					
						
							|  |  |  |  |       this.stopAutoReconnect(); | 
					
						
							|  |  |  |  |       await this.forgetPort(); | 
					
						
							|  |  |  |  |       if (serial.isWebUsbSupported()) { | 
					
						
							|  |  |  |  |         let ports = await serial.getWebUsbSerialPorts(); | 
					
						
							|  |  |  |  |         for (const p of ports) { | 
					
						
							|  |  |  |  |           await p.forgetDevice(); | 
					
						
							|  |  |  |  |         } | 
					
						
							|  |  |  |  |       } | 
					
						
							|  |  |  |  |       this.updateUIConnectionState(); | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |     } | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |     setNewlineMode() { | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |       localStorage.setItem('newlineMode', uiNewlineModeSelect.value); | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |     } | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |     autoReconnectChanged() { | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |       if (uiAutoReconnectCheckbox.checked) { | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |         this.setStatus('Auto-reconnect enabled', 'info'); | 
					
						
							|  |  |  |  |         this.tryAutoReconnect(); | 
					
						
							|  |  |  |  |       } else { | 
					
						
							|  |  |  |  |         this.setStatus('Auto-reconnect disabled', 'info'); | 
					
						
							|  |  |  |  |         this.stopAutoReconnect(); | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |       } | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |       localStorage.setItem('autoReconnect', uiAutoReconnectCheckbox.checked); | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |     } | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  | 
 | 
					
						
							|  |  |  |  |     stopAutoReconnect() { | 
					
						
							|  |  |  |  |       if (this.reconnectTimeoutId !== null) { | 
					
						
							|  |  |  |  |         clearTimeout(this.reconnectTimeoutId); | 
					
						
							|  |  |  |  |         this.reconnectTimeoutId = null; | 
					
						
							|  |  |  |  |         this.setStatus('Auto-reconnect stopped.', 'info'); | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |       } | 
					
						
							|  |  |  |  |     } | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-29 15:51:54 +02:00
										 |  |  |  |     async autoReconnectTimeout() { | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |         this.reconnectTimeoutId = null; | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |         if (!uiAutoReconnectCheckbox.checked) { | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |           this.setStatus('Auto-reconnect stopped.', 'info'); | 
					
						
							|  |  |  |  |           return; | 
					
						
							|  |  |  |  |         } | 
					
						
							| 
									
										
										
										
											2025-07-29 15:51:54 +02:00
										 |  |  |  |         if (this.currentPort && !this.currentPort.isConnected) { | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |           try { | 
					
						
							|  |  |  |  |             await this.currentPort.connect(); | 
					
						
							| 
									
										
										
										
											2025-07-29 15:51:54 +02:00
										 |  |  |  |             this.setStatus('Reconnected successfully', 'info'); | 
					
						
							|  |  |  |  |           } catch (error) { | 
					
						
							|  |  |  |  |             this.setStatus(`Reconnect failed: ${error.message}`, 'error'); | 
					
						
							|  |  |  |  |             // Try again after a delay
 | 
					
						
							|  |  |  |  |             this.tryAutoReconnect(); | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |           } finally { | 
					
						
							|  |  |  |  |             this.updateUIConnectionState(); | 
					
						
							|  |  |  |  |           } | 
					
						
							|  |  |  |  |         } | 
					
						
							| 
									
										
										
										
											2025-07-29 15:51:54 +02:00
										 |  |  |  |     } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |     tryAutoReconnect() { | 
					
						
							|  |  |  |  |       this.updateUIConnectionState(); | 
					
						
							|  |  |  |  |       if (!uiAutoReconnectCheckbox.checked) return; | 
					
						
							|  |  |  |  |       if (this.reconnectTimeoutId !== null) return; // already trying
 | 
					
						
							|  |  |  |  |       this.setStatus('Attempting to auto-reconnect...', 'info'); | 
					
						
							|  |  |  |  |       this.reconnectTimeoutId = setTimeout(async () => { | 
					
						
							|  |  |  |  |         await this.autoReconnectTimeout(); | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |       }, 1000); | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |     } | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |     async handleCommandLineInput(e) { | 
					
						
							|  |  |  |  |       // Instant mode: send key immediately including special keys like Backspace, arrows, enter, etc.
 | 
					
						
							|  |  |  |  |       if (this.sendMode === 'instant') { | 
					
						
							|  |  |  |  |         e.preventDefault(); | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |         // Ignore only pure modifier keys without text representation
 | 
					
						
							|  |  |  |  |         if (e.key.length === 1 || | 
					
						
							|  |  |  |  |           e.key === 'Enter' || | 
					
						
							|  |  |  |  |           e.key === 'Backspace' || | 
					
						
							|  |  |  |  |           e.key === 'Tab' || | 
					
						
							|  |  |  |  |           e.key === 'Escape' || | 
					
						
							|  |  |  |  |           e.key === 'Delete' ) { | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |           let sendText = ''; | 
					
						
							|  |  |  |  |           switch (e.key) { | 
					
						
							|  |  |  |  |             case 'Enter': | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |               switch (uiNewlineModeSelect.value) { | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |                 case 'CR': sendText = '\r'; break; | 
					
						
							|  |  |  |  |                 case 'CRLF': sendText = '\r\n'; break; | 
					
						
							|  |  |  |  |                 default: sendText = '\n'; break; | 
					
						
							|  |  |  |  |               } | 
					
						
							|  |  |  |  |               break; | 
					
						
							|  |  |  |  |             case 'Backspace': | 
					
						
							|  |  |  |  |               // Usually no straightforward char to send for Backspace,
 | 
					
						
							|  |  |  |  |               // but often ASCII DEL '\x7F' or '\b' (0x08) is sent.
 | 
					
						
							|  |  |  |  |               sendText = '\x08'; // backspace
 | 
					
						
							|  |  |  |  |               break; | 
					
						
							|  |  |  |  |             case 'Tab': | 
					
						
							|  |  |  |  |               sendText = '\t'; | 
					
						
							|  |  |  |  |               break; | 
					
						
							|  |  |  |  |             case 'Escape': | 
					
						
							|  |  |  |  |               // Ignore or send ESC control char if needed
 | 
					
						
							|  |  |  |  |               sendText = '\x1B'; | 
					
						
							|  |  |  |  |               break; | 
					
						
							|  |  |  |  |             case 'Delete': | 
					
						
							|  |  |  |  |               sendText = '\x7F'; // DEL char
 | 
					
						
							|  |  |  |  |               break; | 
					
						
							|  |  |  |  |             default: | 
					
						
							|  |  |  |  |               sendText = e.key; | 
					
						
							|  |  |  |  |           } | 
					
						
							|  |  |  |  |           try { | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |             await this.currentPort.send(this.textEncoder.encode(sendText)); | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |           } catch (error) { | 
					
						
							|  |  |  |  |             this.setStatus(`Send error: ${error.message}`, 'error'); | 
					
						
							|  |  |  |  |             this.tryAutoReconnect(); | 
					
						
							|  |  |  |  |           } | 
					
						
							|  |  |  |  |         } | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |         return; | 
					
						
							|  |  |  |  |       } | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |       // Command mode: handle up/down arrow keys for history
 | 
					
						
							|  |  |  |  |       if (e.key === 'ArrowUp' || e.key === 'ArrowDown') { | 
					
						
							|  |  |  |  |         e.preventDefault(); | 
					
						
							|  |  |  |  |         if (this.commandHistory.length === 0) return; | 
					
						
							|  |  |  |  |         if (e.key === 'ArrowUp') { | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |           if (this.uiCommandHistoryIndex === -1) this.uiCommandHistoryIndex = this.commandHistory.length - 1; | 
					
						
							|  |  |  |  |           else if (this.uiCommandHistoryIndex > 0) this.uiCommandHistoryIndex--; | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |         } else if (e.key === 'ArrowDown') { | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |           if (this.uiCommandHistoryIndex !== -1) this.uiCommandHistoryIndex++; | 
					
						
							|  |  |  |  |           if (this.uiCommandHistoryIndex >= this.commandHistory.length) this.uiCommandHistoryIndex = -1; | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |         } | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |         uiCommandLineInput.value = this.uiCommandHistoryIndex === -1 ? '' : this.commandHistory[this.uiCommandHistoryIndex].text; | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |         return; | 
					
						
							|  |  |  |  |       } | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |       if (e.key !== 'Enter' || !this.currentPort.isConnected) return; | 
					
						
							|  |  |  |  |       e.preventDefault(); | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |       const text = uiCommandLineInput.value; | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |       if (!text) return; | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |       // Convert to Uint8Array with newline based on config
 | 
					
						
							|  |  |  |  |       let sendText = text; | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |       switch (uiNewlineModeSelect.value) { | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |         case 'CR': | 
					
						
							|  |  |  |  |           sendText += '\r'; | 
					
						
							|  |  |  |  |           break; | 
					
						
							|  |  |  |  |         case 'CRLF': | 
					
						
							|  |  |  |  |           sendText += '\r\n'; | 
					
						
							|  |  |  |  |           break; | 
					
						
							|  |  |  |  |         case 'ANY': | 
					
						
							|  |  |  |  |           sendText += '\n'; | 
					
						
							|  |  |  |  |           break; | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |       } | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |       const data = this.textEncoder.encode(sendText); | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |       try { | 
					
						
							|  |  |  |  |         await this.currentPort.send(data); | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |         this.uiCommandHistoryIndex = -1; | 
					
						
							|  |  |  |  |         let history_cmd_text = sendText.replace(/[\r\n]+$/, ''); | 
					
						
							|  |  |  |  |         let history_entry = new CommandHistoryEntry(history_cmd_text); | 
					
						
							| 
									
										
										
										
											2025-07-08 12:04:01 +02:00
										 |  |  |  |         this.appendNewCommandToHistory(history_entry); | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |         uiCommandLineInput.value = ''; | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |       } catch (error) { | 
					
						
							|  |  |  |  |         this.setStatus(`Send error: ${error.message}`, 'error'); | 
					
						
							|  |  |  |  |         this.tryAutoReconnect(); | 
					
						
							|  |  |  |  |       } | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |     } | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |     toggleSendMode() { | 
					
						
							|  |  |  |  |       if (this.sendMode === 'instant') { | 
					
						
							|  |  |  |  |         this.setSendMode('command'); | 
					
						
							|  |  |  |  |       } else { | 
					
						
							|  |  |  |  |         this.setSendMode('instant'); | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |       } | 
					
						
							|  |  |  |  |     } | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |     setSendMode(mode) { | 
					
						
							|  |  |  |  |       this.sendMode = mode; | 
					
						
							|  |  |  |  |       if (mode === 'instant') { | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |         uiSendModeBtn.classList.remove('send-mode-command'); | 
					
						
							|  |  |  |  |         uiSendModeBtn.classList.add('send-mode-instant'); | 
					
						
							|  |  |  |  |         uiSendModeBtn.textContent = 'Instant mode'; | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |       } else { | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |         uiSendModeBtn.classList.remove('send-mode-instant'); | 
					
						
							|  |  |  |  |         uiSendModeBtn.classList.add('send-mode-command'); | 
					
						
							|  |  |  |  |         uiSendModeBtn.textContent = 'Command mode'; | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |       } | 
					
						
							|  |  |  |  |       localStorage.setItem('sendMode', this.sendMode); | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |     } | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |     copyOutput() { | 
					
						
							|  |  |  |  |       let text = ''; | 
					
						
							|  |  |  |  |       for (const entry of this.receivedData) { | 
					
						
							|  |  |  |  |         text += entry.text; | 
					
						
							|  |  |  |  |         if (entry.terminated) { | 
					
						
							|  |  |  |  |           text += '\n'; | 
					
						
							|  |  |  |  |         } | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |       } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |       if (text) { | 
					
						
							|  |  |  |  |         navigator.clipboard.writeText(text).then(() => { | 
					
						
							|  |  |  |  |           this.setStatus('Output copied to clipboard', 'info'); | 
					
						
							|  |  |  |  |         }, () => { | 
					
						
							|  |  |  |  |           this.setStatus('Failed to copy output', 'error'); | 
					
						
							|  |  |  |  |         }); | 
					
						
							|  |  |  |  |       } else { | 
					
						
							|  |  |  |  |         this.setStatus('No output to copy', 'error'); | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |       } | 
					
						
							|  |  |  |  |     } | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |     downloadOutputCsv() { | 
					
						
							|  |  |  |  |       // save <iso_date_time>,<received_line>
 | 
					
						
							|  |  |  |  |       let csvContent = 'data:text/csv;charset=utf-8,'; | 
					
						
							|  |  |  |  |       for (const entry of this.receivedData) { | 
					
						
							| 
									
										
										
										
											2025-07-29 15:51:54 +02:00
										 |  |  |  |         let sanitizedText = entry.text.replace(/"/g, '""').replace(/[\r\n]+$/, ''); | 
					
						
							|  |  |  |  |         let line = new Date(entry.time).toISOString() + ',"' + sanitizedText + '"'; | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |         csvContent += line + '\n'; | 
					
						
							|  |  |  |  |       } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  |       const encodedUri = encodeURI(csvContent); | 
					
						
							|  |  |  |  |       const link = document.createElement('a'); | 
					
						
							|  |  |  |  |       link.setAttribute('href', encodedUri); | 
					
						
							| 
									
										
										
										
											2025-07-29 15:51:54 +02:00
										 |  |  |  |       const filename = new Date().toISOString().replace(/:/g, '-') + '_tinyusb_received_serial_data.csv'; | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |       link.setAttribute('download', filename); | 
					
						
							|  |  |  |  |       document.body.appendChild(link); | 
					
						
							|  |  |  |  |       link.click(); | 
					
						
							|  |  |  |  |       document.body.removeChild(link); | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |     } | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |     async resetAll() { | 
					
						
							|  |  |  |  |       await this.forgetAllPorts(); | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |       // Clear localStorage
 | 
					
						
							| 
									
										
										
										
											2025-07-08 12:16:32 +02:00
										 |  |  |  |       localStorage.clear(); | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |       // reload the page
 | 
					
						
							|  |  |  |  |       window.location.reload(); | 
					
						
							|  |  |  |  |     } | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  |   } | 
					
						
							| 
									
										
										
										
											2025-07-05 19:42:44 +02:00
										 |  |  |  | 
 | 
					
						
							|  |  |  |  |   const app = new Application(); | 
					
						
							|  |  |  |  | })() |