|
@@ -0,0 +1,351 @@
|
|
|
|
|
+<!DOCTYPE html>
|
|
|
|
|
+<html lang="en">
|
|
|
|
|
+<head>
|
|
|
|
|
+ <meta charset="UTF-8">
|
|
|
|
|
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
|
|
|
+ <title>Sand Table Controller</title>
|
|
|
|
|
+ <link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;700&display=swap" rel="stylesheet">
|
|
|
|
|
+ <link rel="stylesheet" href="../static/style.css">
|
|
|
|
|
+</head>
|
|
|
|
|
+<body>
|
|
|
|
|
+ <h1>Sand Table Controller</h1>
|
|
|
|
|
+ <div class="container">
|
|
|
|
|
+ <!-- Left Column -->
|
|
|
|
|
+ <div class="left-column">
|
|
|
|
|
+ <div class="section">
|
|
|
|
|
+ <h2>Serial Connection</h2>
|
|
|
|
|
+ <label for="serial_ports">Available Ports:</label>
|
|
|
|
|
+ <select id="serial_ports"></select>
|
|
|
|
|
+ <div class="button-group">
|
|
|
|
|
+ <button onclick="connectSerial()">Connect</button>
|
|
|
|
|
+ <button onclick="disconnectSerial()">Disconnect</button>
|
|
|
|
|
+ <button onclick="restartSerial()">Restart</button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <p id="serial_status" class="status">Status: Not connected</p>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <div class="section">
|
|
|
|
|
+ <h2>Quick Actions</h2>
|
|
|
|
|
+ <div class="button-group">
|
|
|
|
|
+ <button onclick="sendHomeCommand()">Home Device</button>
|
|
|
|
|
+ <button onclick="moveToCenter()">Move to Center</button>
|
|
|
|
|
+ <button onclick="moveToPerimeter()">Move to Perimeter</button>
|
|
|
|
|
+ </br>
|
|
|
|
|
+ <button onclick="runClearIn()">Clear from In</button>
|
|
|
|
|
+ <button onclick="runClearOut()">Clear from Out</button>
|
|
|
|
|
+
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="section">
|
|
|
|
|
+ <h2>Upload new files</h2>
|
|
|
|
|
+ <div class="button-group">
|
|
|
|
|
+ <input type="file" id="upload_file">
|
|
|
|
|
+ <div class="button-group">
|
|
|
|
|
+ <button onclick="uploadThetaRho()">Upload</button>
|
|
|
|
|
+ <button id="delete_selected_button" class="delete-button" onclick="deleteSelectedFile()" disabled>Delete Selected File</button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- Right Column -->
|
|
|
|
|
+ <div class="right-column">
|
|
|
|
|
+ <div class="section">
|
|
|
|
|
+ <h2>Pattern Files</h2>
|
|
|
|
|
+ <input type="text" id="search_pattern" placeholder="Search files..." oninput="searchPatternFiles()">
|
|
|
|
|
+ <ul id="theta_rho_files"></ul>
|
|
|
|
|
+ <div class="button-group">
|
|
|
|
|
+ <button id="run_button" disabled>Run Selected File</button>
|
|
|
|
|
+ <button onclick="stopExecution()" class="delete-button">Stop</button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <div id="status_log">
|
|
|
|
|
+ <h2>Status Log</h2>
|
|
|
|
|
+ <!-- Messages will be appended here -->
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <script>
|
|
|
|
|
+ let selectedFile = null;
|
|
|
|
|
+
|
|
|
|
|
+ function logMessage(message) {
|
|
|
|
|
+ const log = document.getElementById('status_log');
|
|
|
|
|
+ const entry = document.createElement('p');
|
|
|
|
|
+ entry.textContent = message;
|
|
|
|
|
+ log.appendChild(entry);
|
|
|
|
|
+ log.scrollTop = log.scrollHeight; // Keep log scrolled to the bottom
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ async function loadThetaRhoFiles() {
|
|
|
|
|
+ logMessage('Loading Theta-Rho files...');
|
|
|
|
|
+ const response = await fetch('/list_theta_rho_files');
|
|
|
|
|
+ const files = await response.json();
|
|
|
|
|
+ const ul = document.getElementById('theta_rho_files');
|
|
|
|
|
+ ul.innerHTML = ''; // Clear current list
|
|
|
|
|
+
|
|
|
|
|
+ files.forEach(file => {
|
|
|
|
|
+ const li = document.createElement('li');
|
|
|
|
|
+ li.textContent = file;
|
|
|
|
|
+
|
|
|
|
|
+ // Highlight the selected file when clicked
|
|
|
|
|
+ li.onclick = () => selectFile(file, li);
|
|
|
|
|
+
|
|
|
|
|
+ ul.appendChild(li);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ logMessage('Theta-Rho files loaded successfully.');
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ function selectFile(file, listItem) {
|
|
|
|
|
+ selectedFile = file;
|
|
|
|
|
+
|
|
|
|
|
+ // Highlight the selected file
|
|
|
|
|
+ document.querySelectorAll('#theta_rho_files li').forEach(li => li.classList.remove('selected'));
|
|
|
|
|
+ listItem.classList.add('selected');
|
|
|
|
|
+
|
|
|
|
|
+ // Enable buttons
|
|
|
|
|
+ document.getElementById('run_button').disabled = false;
|
|
|
|
|
+ document.getElementById('delete_selected_button').disabled = false;
|
|
|
|
|
+
|
|
|
|
|
+ logMessage(`Selected file: ${file}`);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ async function uploadThetaRho() {
|
|
|
|
|
+ const fileInput = document.getElementById('upload_file');
|
|
|
|
|
+ const file = fileInput.files[0];
|
|
|
|
|
+ if (!file) {
|
|
|
|
|
+ logMessage('No file selected for upload.');
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ logMessage(`Uploading file: ${file.name}...`);
|
|
|
|
|
+ const formData = new FormData();
|
|
|
|
|
+ formData.append('file', file);
|
|
|
|
|
+
|
|
|
|
|
+ const response = await fetch('/upload_theta_rho', {
|
|
|
|
|
+ method: 'POST',
|
|
|
|
|
+ body: formData
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ const result = await response.json();
|
|
|
|
|
+ if (result.success) {
|
|
|
|
|
+ logMessage(`File uploaded successfully: ${file.name}`);
|
|
|
|
|
+ loadThetaRhoFiles();
|
|
|
|
|
+ } else {
|
|
|
|
|
+ logMessage(`Failed to upload file: ${file.name}`);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ async function deleteSelectedFile() {
|
|
|
|
|
+ if (!selectedFile) {
|
|
|
|
|
+ logMessage("No file selected for deletion.");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const userConfirmed = confirm(`Are you sure you want to delete the selected file "${selectedFile}"?`);
|
|
|
|
|
+ if (!userConfirmed) return;
|
|
|
|
|
+
|
|
|
|
|
+ logMessage(`Deleting file: ${selectedFile}...`);
|
|
|
|
|
+ const response = await fetch('/delete_theta_rho_file', {
|
|
|
|
|
+ method: 'POST',
|
|
|
|
|
+ headers: { 'Content-Type': 'application/json' },
|
|
|
|
|
+ body: JSON.stringify({ file_name: selectedFile }),
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ const result = await response.json();
|
|
|
|
|
+ if (result.success) {
|
|
|
|
|
+ const ul = document.getElementById('theta_rho_files');
|
|
|
|
|
+ const selectedItem = Array.from(ul.children).find(li => li.classList.contains('selected'));
|
|
|
|
|
+ if (selectedItem) selectedItem.remove();
|
|
|
|
|
+
|
|
|
|
|
+ selectedFile = null;
|
|
|
|
|
+ document.getElementById('run_button').disabled = true;
|
|
|
|
|
+ document.getElementById('delete_selected_button').disabled = true;
|
|
|
|
|
+
|
|
|
|
|
+ logMessage(`File deleted successfully: ${result.file_name}`);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ logMessage(`Failed to delete file: ${selectedFile}`);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ async function runThetaRho() {
|
|
|
|
|
+ if (!selectedFile) {
|
|
|
|
|
+ logMessage("No file selected to run.");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ logMessage(`Running file: ${selectedFile}...`);
|
|
|
|
|
+ const response = await fetch('/run_theta_rho', {
|
|
|
|
|
+ method: 'POST',
|
|
|
|
|
+ headers: { 'Content-Type': 'application/json' },
|
|
|
|
|
+ body: JSON.stringify({ file_name: selectedFile })
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ const result = await response.json();
|
|
|
|
|
+ if (result.success) {
|
|
|
|
|
+ logMessage(`File running: ${selectedFile}`);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ logMessage(`Failed to run file: ${selectedFile}`);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ async function stopExecution() {
|
|
|
|
|
+ logMessage('Stopping execution...');
|
|
|
|
|
+ const response = await fetch('/stop_execution', { method: 'POST' });
|
|
|
|
|
+ const result = await response.json();
|
|
|
|
|
+ if (result.success) {
|
|
|
|
|
+ logMessage('Execution stopped.');
|
|
|
|
|
+ } else {
|
|
|
|
|
+ logMessage('Failed to stop execution.');
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ async function loadSerialPorts() {
|
|
|
|
|
+ const response = await fetch('/list_serial_ports');
|
|
|
|
|
+ const ports = await response.json();
|
|
|
|
|
+ const select = document.getElementById('serial_ports');
|
|
|
|
|
+ select.innerHTML = '';
|
|
|
|
|
+ ports.forEach(port => {
|
|
|
|
|
+ const option = document.createElement('option');
|
|
|
|
|
+ option.value = port;
|
|
|
|
|
+ option.textContent = port;
|
|
|
|
|
+ select.appendChild(option);
|
|
|
|
|
+ });
|
|
|
|
|
+ logMessage('Serial ports loaded.');
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ async function connectSerial() {
|
|
|
|
|
+ const port = document.getElementById('serial_ports').value;
|
|
|
|
|
+ const response = await fetch('/connect_serial', {
|
|
|
|
|
+ method: 'POST',
|
|
|
|
|
+ headers: { 'Content-Type': 'application/json' },
|
|
|
|
|
+ body: JSON.stringify({ port })
|
|
|
|
|
+ });
|
|
|
|
|
+ const result = await response.json();
|
|
|
|
|
+ if (result.success) {
|
|
|
|
|
+ document.getElementById('serial_status').textContent = `Status: Connected to ${port}`;
|
|
|
|
|
+ logMessage(`Connected to serial port: ${port}`);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ logMessage(`Error connecting to serial port: ${result.error}`);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ async function disconnectSerial() {
|
|
|
|
|
+ const response = await fetch('/disconnect_serial', { method: 'POST' });
|
|
|
|
|
+ const result = await response.json();
|
|
|
|
|
+ if (result.success) {
|
|
|
|
|
+ document.getElementById('serial_status').textContent = 'Status: Disconnected';
|
|
|
|
|
+ logMessage('Serial port disconnected.');
|
|
|
|
|
+ } else {
|
|
|
|
|
+ logMessage(`Error disconnecting: ${result.error}`);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ async function restartSerial() {
|
|
|
|
|
+ const port = document.getElementById('serial_ports').value;
|
|
|
|
|
+ const response = await fetch('/restart_serial', {
|
|
|
|
|
+ method: 'POST',
|
|
|
|
|
+ headers: { 'Content-Type': 'application/json' },
|
|
|
|
|
+ body: JSON.stringify({ port })
|
|
|
|
|
+ });
|
|
|
|
|
+ const result = await response.json();
|
|
|
|
|
+ if (result.success) {
|
|
|
|
|
+ document.getElementById('serial_status').textContent = `Status: Restarted connection to ${port}`;
|
|
|
|
|
+ logMessage('Serial connection restarted.');
|
|
|
|
|
+ } else {
|
|
|
|
|
+ logMessage(`Error restarting serial connection: ${result.error}`);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ async function sendHomeCommand() {
|
|
|
|
|
+ const response = await fetch('/send_home', { method: 'POST' });
|
|
|
|
|
+ const result = await response.json();
|
|
|
|
|
+ if (result.success) {
|
|
|
|
|
+ logMessage('HOME command sent successfully.');
|
|
|
|
|
+ } else {
|
|
|
|
|
+ logMessage('Failed to send HOME command.');
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ async function runClearIn() {
|
|
|
|
|
+ await runFile('clear_from_in.thr');
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ async function runClearOut() {
|
|
|
|
|
+ await runFile('clear_from_out.thr');
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ async function runFile(fileName) {
|
|
|
|
|
+ const response = await fetch(`/run_theta_rho_file/${fileName}`, { method: 'POST' });
|
|
|
|
|
+ const result = await response.json();
|
|
|
|
|
+ if (result.success) {
|
|
|
|
|
+ logMessage(`Running file: ${fileName}`);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ logMessage(`Failed to run file: ${fileName}`);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ let allFiles = []; // Store all files for filtering
|
|
|
|
|
+
|
|
|
|
|
+ async function loadThetaRhoFiles() {
|
|
|
|
|
+ logMessage('Loading Theta-Rho files...');
|
|
|
|
|
+ const response = await fetch('/list_theta_rho_files');
|
|
|
|
|
+ const files = await response.json();
|
|
|
|
|
+ allFiles = files; // Store the full list of files
|
|
|
|
|
+ displayFiles(allFiles); // Initially display all files
|
|
|
|
|
+ logMessage('Theta-Rho files loaded successfully.');
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ function displayFiles(files) {
|
|
|
|
|
+ const ul = document.getElementById('theta_rho_files');
|
|
|
|
|
+ ul.innerHTML = ''; // Clear current list
|
|
|
|
|
+
|
|
|
|
|
+ files.forEach(file => {
|
|
|
|
|
+ const li = document.createElement('li');
|
|
|
|
|
+ li.textContent = file;
|
|
|
|
|
+
|
|
|
|
|
+ // Highlight the selected file when clicked
|
|
|
|
|
+ li.onclick = () => selectFile(file, li);
|
|
|
|
|
+
|
|
|
|
|
+ ul.appendChild(li);
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ function searchPatternFiles() {
|
|
|
|
|
+ const searchInput = document.getElementById('search_pattern').value.toLowerCase();
|
|
|
|
|
+ const filteredFiles = allFiles.filter(file => file.toLowerCase().includes(searchInput));
|
|
|
|
|
+ displayFiles(filteredFiles); // Display only matching files
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ async function moveToCenter() {
|
|
|
|
|
+ logMessage('Moving to center...');
|
|
|
|
|
+ const response = await fetch('/move_to_center', { method: 'POST' });
|
|
|
|
|
+ const result = await response.json();
|
|
|
|
|
+ if (result.success) {
|
|
|
|
|
+ logMessage('Moved to center successfully.');
|
|
|
|
|
+ } else {
|
|
|
|
|
+ logMessage(`Failed to move to center: ${result.error}`);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ async function moveToPerimeter() {
|
|
|
|
|
+ logMessage('Moving to perimeter...');
|
|
|
|
|
+ const response = await fetch('/move_to_perimeter', { method: 'POST' });
|
|
|
|
|
+ const result = await response.json();
|
|
|
|
|
+ if (result.success) {
|
|
|
|
|
+ logMessage('Moved to perimeter successfully.');
|
|
|
|
|
+ } else {
|
|
|
|
|
+ logMessage(`Failed to move to perimeter: ${result.error}`);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Initial load of serial ports and Theta-Rho files
|
|
|
|
|
+ loadSerialPorts();
|
|
|
|
|
+ loadThetaRhoFiles();
|
|
|
|
|
+
|
|
|
|
|
+ document.getElementById('run_button').onclick = runThetaRho;
|
|
|
|
|
+
|
|
|
|
|
+ </script>
|
|
|
|
|
+</body>
|
|
|
|
|
+</html>
|