Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8" /> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0" /> | |
| <title>Feetech Servo Test</title> | |
| <style> | |
| body { | |
| font-family: sans-serif; | |
| line-height: 1.6; | |
| padding: 20px; | |
| } | |
| .container { | |
| max-width: 800px; | |
| margin: auto; | |
| } | |
| .section { | |
| border: 1px solid #ccc; | |
| padding: 15px; | |
| margin-bottom: 20px; | |
| border-radius: 5px; | |
| } | |
| h2 { | |
| margin-top: 0; | |
| } | |
| label { | |
| display: inline-block; | |
| min-width: 100px; | |
| margin-bottom: 5px; | |
| } | |
| input[type="number"], | |
| input[type="text"] { | |
| width: 100px; | |
| padding: 5px; | |
| margin-right: 10px; | |
| margin-bottom: 10px; | |
| } | |
| button { | |
| padding: 8px 15px; | |
| margin-right: 10px; | |
| cursor: pointer; | |
| } | |
| pre { | |
| background-color: #f4f4f4; | |
| padding: 10px; | |
| border: 1px solid #ddd; | |
| border-radius: 3px; | |
| white-space: pre-wrap; | |
| word-wrap: break-word; | |
| } | |
| .status { | |
| font-weight: bold; | |
| } | |
| .success { | |
| color: green; | |
| } | |
| .error { | |
| color: red; | |
| } | |
| .log-area { | |
| margin-top: 10px; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="container"> | |
| <h1>Feetech Servo Test Page</h1> | |
| <details class="section"> | |
| <summary>Key Concepts</summary> | |
| <p>Understanding these parameters is crucial for controlling Feetech servos:</p> | |
| <ul> | |
| <li> | |
| <strong>Mode:</strong> Determines the servo's primary function. | |
| <ul> | |
| <li> | |
| <code>Mode 0</code>: Position/Servo Mode. The servo moves to and holds a specific | |
| angular position. | |
| </li> | |
| <li> | |
| <code>Mode 1</code>: Wheel/Speed Mode. The servo rotates continuously at a specified | |
| speed and direction, like a motor. | |
| </li> | |
| </ul> | |
| Changing the mode requires unlocking, writing the mode value (0 or 1), and locking the | |
| configuration. | |
| </li> | |
| <li> | |
| <strong>Position:</strong> In Position Mode (Mode 0), this value represents the target | |
| or current angular position of the servo's output shaft. | |
| <ul> | |
| <li> | |
| Range: Typically <code>0</code> to <code>4095</code> (representing a 12-bit | |
| resolution). | |
| </li> | |
| <li> | |
| Meaning: Corresponds to the servo's rotational range (e.g., 0-360 degrees or 0-270 | |
| degrees, depending on the specific servo model). <code>0</code> is one end of the | |
| range, <code>4095</code> is the other. | |
| </li> | |
| </ul> | |
| </li> | |
| <li> | |
| <strong>Speed (Wheel Mode):</strong> In Wheel Mode (Mode 1), this value controls the | |
| rotational speed and direction. | |
| <ul> | |
| <li> | |
| Range: Typically <code>-2500</code> to <code>+2500</code>. (Note: Some documentation | |
| might mention -1023 to +1023, but the SDK example uses a wider range). | |
| </li> | |
| <li> | |
| Meaning: <code>0</code> stops the wheel. Positive values rotate in one direction | |
| (e.g., clockwise), negative values rotate in the opposite direction (e.g., | |
| counter-clockwise). The magnitude determines the speed (larger absolute value means | |
| faster rotation). | |
| </li> | |
| <li>Control Address: <code>ADDR_SCS_GOAL_SPEED</code> (Register 46/47).</li> | |
| </ul> | |
| </li> | |
| <li> | |
| <strong>Acceleration:</strong> Controls how quickly the servo changes speed to reach its | |
| target position (in Position Mode) or target speed (in Wheel Mode). | |
| <ul> | |
| <li>Range: Typically <code>0</code> to <code>254</code>.</li> | |
| <li> | |
| Meaning: Defines the rate of change of speed. The unit is 100 steps/s². | |
| <code>0</code> usually means instantaneous acceleration (or minimal delay). Higher | |
| values result in slower, smoother acceleration and deceleration. For example, a | |
| value of <code>10</code> means the speed changes by 10 * 100 = 1000 steps per | |
| second, per second. This helps reduce jerky movements and mechanical stress. | |
| </li> | |
| <li>Control Address: <code>ADDR_SCS_GOAL_ACC</code> (Register 41).</li> | |
| </ul> | |
| </li> | |
| <li> | |
| <strong>Baud Rate:</strong> The speed of communication between the controller and the | |
| servo. It must match on both ends. Servos often support multiple baud rates, selectable | |
| via an index: | |
| <ul> | |
| <li>Index 0: 1,000,000 bps</li> | |
| <li>Index 1: 500,000 bps</li> | |
| <li>Index 2: 250,000 bps</li> | |
| <li>Index 3: 128,000 bps</li> | |
| <li>Index 4: 115,200 bps</li> | |
| <li>Index 5: 76,800 bps</li> | |
| <li>Index 6: 57,600 bps</li> | |
| <li>Index 7: 38,400 bps</li> | |
| </ul> | |
| </li> | |
| </ul> | |
| </details> | |
| <div class="section"> | |
| <h2>Connection</h2> | |
| <button id="connectBtn">Connect</button> | |
| <button id="disconnectBtn">Disconnect</button> | |
| <p>Status: <span id="connectionStatus" class="status error">Disconnected</span></p> | |
| <label for="baudRate">Baud Rate:</label> | |
| <input type="number" id="baudRate" value="1000000" /> | |
| <label for="protocolEnd">Protocol End (0=STS/SMS, 1=SCS):</label> | |
| <input type="number" id="protocolEnd" value="0" min="0" max="1" /> | |
| </div> | |
| <div class="section"> | |
| <h2>Scan Servos</h2> | |
| <label for="scanStartId">Start ID:</label> | |
| <input type="number" id="scanStartId" value="1" min="1" max="252" /> | |
| <label for="scanEndId">End ID:</label> | |
| <input type="number" id="scanEndId" value="15" min="1" max="252" /> | |
| <button id="scanServosBtn">Scan</button> | |
| <p>Scan Results:</p> | |
| <pre id="scanResultsOutput" style="max-height: 200px; overflow-y: auto"></pre> | |
| <!-- Added element for results --> | |
| </div> | |
| <div class="section"> | |
| <h2>Single Servo Control</h2> | |
| <label for="servoId">Servo ID:</label> | |
| <input type="number" id="servoId" value="1" min="1" max="252" /><br /> | |
| <label for="idWrite">Change servo ID:</label> | |
| <input type="number" id="idWrite" value="1" min="1" max="252" /> | |
| <button id="writeIdBtn">Write</button><br /> | |
| <label for="baudRead">Read Baud Rate:</label> | |
| <button id="readBaudBtn">Read</button> | |
| <span id="readBaudResult"></span><br /> | |
| <label for="baudWrite">Write Baud Rate Index:</label> | |
| <input type="number" id="baudWrite" value="6" min="0" max="7" /> | |
| <!-- Assuming index 0-7 --> | |
| <button id="writeBaudBtn">Write</button><br /> | |
| <label for="positionRead">Read Position:</label> | |
| <button id="readPosBtn">Read</button> | |
| <span id="readPosResult"></span><br /> | |
| <label for="positionWrite">Write Position:</label> | |
| <input type="number" id="positionWrite" value="1000" min="0" max="4095" /> | |
| <button id="writePosBtn">Write</button><br /> | |
| <label for="torqueEnable">Torque:</label> | |
| <button id="torqueEnableBtn">Enable</button> | |
| <button id="torqueDisableBtn">Disable</button><br /> | |
| <label for="accelerationWrite">Write Acceleration:</label> | |
| <input type="number" id="accelerationWrite" value="50" min="0" max="254" /> | |
| <button id="writeAccBtn">Write</button><br /> | |
| <label for="wheelMode">Wheel Mode:</label> | |
| <button id="setWheelModeBtn">Set Wheel Mode</button> | |
| <button id="removeWheelModeBtn">Set Position Mode</button><br /> | |
| <label for="wheelSpeedWrite">Write Wheel Speed:</label> | |
| <input type="number" id="wheelSpeedWrite" value="0" min="-2500" max="2500" /> | |
| <button id="writeWheelSpeedBtn">Write Speed</button> | |
| </div> | |
| <div class="section"> | |
| <h2>Sync Operations</h2> | |
| <label for="syncReadIds">Sync Read IDs (csv):</label> | |
| <input type="text" id="syncReadIds" value="1,2,3" style="width: 150px" /> | |
| <button id="syncReadBtn">Sync Read Positions</button><br /> | |
| <label for="syncWriteData">Sync Write (id:pos,...):</label> | |
| <input type="text" id="syncWriteData" value="1:1500,2:2500" style="width: 200px" /> | |
| <button id="syncWriteBtn">Sync Write Positions</button><br /> | |
| <label for="syncWriteSpeedData">Sync Write Speed (id:speed,...):</label> | |
| <input type="text" id="syncWriteSpeedData" value="1:500,2:-1000" style="width: 200px" /> | |
| <button id="syncWriteSpeedBtn">Sync Write Speeds</button> | |
| <!-- New Button --> | |
| </div> | |
| <div class="section"> | |
| <h2>Log Output</h2> | |
| <pre id="logOutput"></pre> | |
| </div> | |
| </div> | |
| <script type="module"> | |
| // Import the scsServoSDK object from index.mjs | |
| import { scsServoSDK } from "./index.mjs"; | |
| // No longer need COMM_SUCCESS etc. here as errors are thrown | |
| const connectBtn = document.getElementById("connectBtn"); | |
| const disconnectBtn = document.getElementById("disconnectBtn"); | |
| const connectionStatus = document.getElementById("connectionStatus"); | |
| const baudRateInput = document.getElementById("baudRate"); | |
| const protocolEndInput = document.getElementById("protocolEnd"); | |
| const servoIdInput = document.getElementById("servoId"); | |
| const readIdBtn = document.getElementById("readIdBtn"); // New | |
| const readIdResult = document.getElementById("readIdResult"); // New | |
| const idWriteInput = document.getElementById("idWrite"); // New | |
| const writeIdBtn = document.getElementById("writeIdBtn"); // New | |
| const readBaudBtn = document.getElementById("readBaudBtn"); // New | |
| const readBaudResult = document.getElementById("readBaudResult"); // New | |
| const baudWriteInput = document.getElementById("baudWrite"); // New | |
| const writeBaudBtn = document.getElementById("writeBaudBtn"); // New | |
| const readPosBtn = document.getElementById("readPosBtn"); | |
| const readPosResult = document.getElementById("readPosResult"); | |
| const positionWriteInput = document.getElementById("positionWrite"); | |
| const writePosBtn = document.getElementById("writePosBtn"); | |
| const torqueEnableBtn = document.getElementById("torqueEnableBtn"); | |
| const torqueDisableBtn = document.getElementById("torqueDisableBtn"); | |
| const accelerationWriteInput = document.getElementById("accelerationWrite"); | |
| const writeAccBtn = document.getElementById("writeAccBtn"); | |
| const setWheelModeBtn = document.getElementById("setWheelModeBtn"); | |
| const removeWheelModeBtn = document.getElementById("removeWheelModeBtn"); // Get reference to the new button | |
| const wheelSpeedWriteInput = document.getElementById("wheelSpeedWrite"); | |
| const writeWheelSpeedBtn = document.getElementById("writeWheelSpeedBtn"); | |
| const syncReadIdsInput = document.getElementById("syncReadIds"); | |
| const syncReadBtn = document.getElementById("syncReadBtn"); | |
| const syncWriteDataInput = document.getElementById("syncWriteData"); | |
| const syncWriteBtn = document.getElementById("syncWriteBtn"); | |
| const syncWriteSpeedDataInput = document.getElementById("syncWriteSpeedData"); // New Input | |
| const syncWriteSpeedBtn = document.getElementById("syncWriteSpeedBtn"); // New Button | |
| const scanServosBtn = document.getElementById("scanServosBtn"); // Get reference to the scan button | |
| const scanStartIdInput = document.getElementById("scanStartId"); // Get reference to start ID input | |
| const scanEndIdInput = document.getElementById("scanEndId"); // Get reference to end ID input | |
| const scanResultsOutput = document.getElementById("scanResultsOutput"); // Get reference to the new results area | |
| const logOutput = document.getElementById("logOutput"); | |
| let isConnected = false; | |
| function log(message) { | |
| console.log(message); | |
| const timestamp = new Date().toLocaleTimeString(); | |
| logOutput.textContent = `[${timestamp}] ${message}\n` + logOutput.textContent; | |
| // Limit log size | |
| const lines = logOutput.textContent.split("\n"); // Use '\n' instead of literal newline | |
| if (lines.length > 50) { | |
| logOutput.textContent = lines.slice(0, 50).join("\n"); // Use '\n' instead of literal newline | |
| } | |
| } | |
| function updateConnectionStatus(connected, message) { | |
| isConnected = connected; | |
| connectionStatus.textContent = message || (connected ? "Connected" : "Disconnected"); | |
| connectionStatus.className = `status ${connected ? "success" : "error"}`; | |
| log(`Connection status: ${connectionStatus.textContent}`); | |
| } | |
| connectBtn.onclick = async () => { | |
| log("Attempting to connect..."); | |
| try { | |
| const baudRate = parseInt(baudRateInput.value, 10); | |
| const protocolEnd = parseInt(protocolEndInput.value, 10); | |
| // Use scsServoSDK - throws on error | |
| await scsServoSDK.connect({ baudRate, protocolEnd }); | |
| updateConnectionStatus(true, "Connected"); | |
| } catch (err) { | |
| updateConnectionStatus(false, `Connection error: ${err.message}`); | |
| console.error(err); | |
| } | |
| }; | |
| disconnectBtn.onclick = async () => { | |
| log("Attempting to disconnect..."); | |
| try { | |
| // Use scsServoSDK - throws on error | |
| await scsServoSDK.disconnect(); | |
| updateConnectionStatus(false, "Disconnected"); // Success means disconnected | |
| } catch (err) { | |
| // Assuming disconnect might fail if already disconnected or other issues | |
| updateConnectionStatus(false, `Disconnection error: ${err.message}`); | |
| console.error(err); | |
| } | |
| }; | |
| writeIdBtn.onclick = async () => { | |
| // New handler | |
| if (!isConnected) { | |
| log("Error: Not connected"); | |
| return; | |
| } | |
| const currentId = parseInt(servoIdInput.value, 10); | |
| const newId = parseInt(idWriteInput.value, 10); | |
| if (isNaN(newId) || newId < 1 || newId > 252) { | |
| log(`Error: Invalid new ID ${newId}. Must be between 1 and 252.`); | |
| return; | |
| } | |
| log(`Writing new ID ${newId} to servo ${currentId}...`); | |
| try { | |
| // Use scsServoSDK - throws on error | |
| await scsServoSDK.setServoId(currentId, newId); | |
| log(`Successfully wrote new ID ${newId} to servo (was ${currentId}).`); | |
| // IMPORTANT: Update the main ID input to reflect the change | |
| servoIdInput.value = newId; | |
| log(`Servo ID input field updated to ${newId}.`); | |
| } catch (err) { | |
| log(`Error writing ID for servo ${currentId}: ${err.message}`); | |
| console.error(err); | |
| } | |
| }; | |
| readBaudBtn.onclick = async () => { | |
| // New handler | |
| if (!isConnected) { | |
| log("Error: Not connected"); | |
| return; | |
| } | |
| const id = parseInt(servoIdInput.value, 10); | |
| log(`Reading Baud Rate Index for servo ${id}...`); | |
| readBaudResult.textContent = "Reading..."; | |
| try { | |
| // Use scsServoSDK - returns value directly or throws | |
| const baudRateIndex = await scsServoSDK.readBaudRate(id); | |
| readBaudResult.textContent = `Baud Index: ${baudRateIndex}`; | |
| log(`Servo ${id} Baud Rate Index: ${baudRateIndex}`); | |
| } catch (err) { | |
| readBaudResult.textContent = `Error: ${err.message}`; | |
| log(`Error reading Baud Rate Index for servo ${id}: ${err.message}`); | |
| console.error(err); | |
| } | |
| }; | |
| writeBaudBtn.onclick = async () => { | |
| // New handler | |
| if (!isConnected) { | |
| log("Error: Not connected"); | |
| return; | |
| } | |
| const id = parseInt(servoIdInput.value, 10); | |
| const newBaudIndex = parseInt(baudWriteInput.value, 10); | |
| if (isNaN(newBaudIndex) || newBaudIndex < 0 || newBaudIndex > 7) { | |
| // Adjust max index if needed | |
| log(`Error: Invalid new Baud Rate Index ${newBaudIndex}. Check valid range.`); | |
| return; | |
| } | |
| log(`Writing new Baud Rate Index ${newBaudIndex} to servo ${id}...`); | |
| try { | |
| // Use scsServoSDK - throws on error | |
| await scsServoSDK.setBaudRate(id, newBaudIndex); | |
| log(`Successfully wrote new Baud Rate Index ${newBaudIndex} to servo ${id}.`); | |
| log( | |
| `IMPORTANT: You may need to disconnect and reconnect with the new baud rate if it differs from the current connection baud rate.` | |
| ); | |
| } catch (err) { | |
| log(`Error writing Baud Rate Index for servo ${id}: ${err.message}`); | |
| console.error(err); | |
| } | |
| }; | |
| readPosBtn.onclick = async () => { | |
| if (!isConnected) { | |
| log("Error: Not connected"); | |
| return; | |
| } | |
| const id = parseInt(servoIdInput.value, 10); | |
| log(`Reading position for servo ${id}...`); | |
| readPosResult.textContent = "Reading..."; | |
| try { | |
| // Use scsServoSDK - returns value directly or throws | |
| const position = await scsServoSDK.readPosition(id); | |
| readPosResult.textContent = `Position: ${position}`; | |
| log(`Servo ${id} position: ${position}`); | |
| } catch (err) { | |
| readPosResult.textContent = `Error: ${err.message}`; | |
| log(`Error reading position for servo ${id}: ${err.message}`); | |
| console.error(err); | |
| } | |
| }; | |
| writePosBtn.onclick = async () => { | |
| if (!isConnected) { | |
| log("Error: Not connected"); | |
| return; | |
| } | |
| const id = parseInt(servoIdInput.value, 10); | |
| const pos = parseInt(positionWriteInput.value, 10); | |
| log(`Writing position ${pos} to servo ${id}...`); | |
| try { | |
| // Use scsServoSDK - throws on error | |
| await scsServoSDK.writePosition(id, pos); | |
| log(`Successfully wrote position ${pos} to servo ${id}.`); | |
| } catch (err) { | |
| log(`Error writing position for servo ${id}: ${err.message}`); | |
| console.error(err); | |
| } | |
| }; | |
| torqueEnableBtn.onclick = async () => { | |
| if (!isConnected) { | |
| log("Error: Not connected"); | |
| return; | |
| } | |
| const id = parseInt(servoIdInput.value, 10); | |
| log(`Enabling torque for servo ${id}...`); | |
| try { | |
| // Use scsServoSDK - throws on error | |
| await scsServoSDK.writeTorqueEnable(id, true); | |
| log(`Successfully enabled torque for servo ${id}.`); | |
| } catch (err) { | |
| log(`Error enabling torque for servo ${id}: ${err.message}`); | |
| console.error(err); | |
| } | |
| }; | |
| torqueDisableBtn.onclick = async () => { | |
| if (!isConnected) { | |
| log("Error: Not connected"); | |
| return; | |
| } | |
| const id = parseInt(servoIdInput.value, 10); | |
| log(`Disabling torque for servo ${id}...`); | |
| try { | |
| // Use scsServoSDK - throws on error | |
| await scsServoSDK.writeTorqueEnable(id, false); | |
| log(`Successfully disabled torque for servo ${id}.`); | |
| } catch (err) { | |
| log(`Error disabling torque for servo ${id}: ${err.message}`); | |
| console.error(err); | |
| } | |
| }; | |
| writeAccBtn.onclick = async () => { | |
| if (!isConnected) { | |
| log("Error: Not connected"); | |
| return; | |
| } | |
| const id = parseInt(servoIdInput.value, 10); | |
| const acc = parseInt(accelerationWriteInput.value, 10); | |
| log(`Writing acceleration ${acc} to servo ${id}...`); | |
| try { | |
| // Use scsServoSDK - throws on error | |
| await scsServoSDK.writeAcceleration(id, acc); | |
| log(`Successfully wrote acceleration ${acc} to servo ${id}.`); | |
| } catch (err) { | |
| log(`Error writing acceleration for servo ${id}: ${err.message}`); | |
| console.error(err); | |
| } | |
| }; | |
| setWheelModeBtn.onclick = async () => { | |
| if (!isConnected) { | |
| log("Error: Not connected"); | |
| return; | |
| } | |
| const id = parseInt(servoIdInput.value, 10); | |
| log(`Setting servo ${id} to wheel mode...`); | |
| try { | |
| // Use scsServoSDK - throws on error | |
| await scsServoSDK.setWheelMode(id); | |
| log(`Successfully set servo ${id} to wheel mode.`); | |
| } catch (err) { | |
| log(`Error setting wheel mode for servo ${id}: ${err.message}`); | |
| console.error(err); | |
| } | |
| }; | |
| // Add event listener for the new button | |
| removeWheelModeBtn.onclick = async () => { | |
| if (!isConnected) { | |
| log("Error: Not connected"); | |
| return; | |
| } | |
| const id = parseInt(servoIdInput.value, 10); | |
| log(`Setting servo ${id} back to position mode...`); | |
| try { | |
| // Use scsServoSDK - throws on error | |
| await scsServoSDK.setPositionMode(id); | |
| log(`Successfully set servo ${id} back to position mode.`); | |
| } catch (err) { | |
| log(`Error setting position mode for servo ${id}: ${err.message}`); | |
| console.error(err); | |
| } | |
| }; | |
| writeWheelSpeedBtn.onclick = async () => { | |
| if (!isConnected) { | |
| log("Error: Not connected"); | |
| return; | |
| } | |
| const id = parseInt(servoIdInput.value, 10); | |
| const speed = parseInt(wheelSpeedWriteInput.value, 10); | |
| log(`Writing wheel speed ${speed} to servo ${id}...`); | |
| try { | |
| // Use scsServoSDK - throws on error | |
| await scsServoSDK.writeWheelSpeed(id, speed); | |
| log(`Successfully wrote wheel speed ${speed} to servo ${id}.`); | |
| } catch (err) { | |
| log(`Error writing wheel speed for servo ${id}: ${err.message}`); | |
| console.error(err); | |
| } | |
| }; | |
| syncReadBtn.onclick = async () => { | |
| if (!isConnected) { | |
| log("Error: Not connected"); | |
| return; | |
| } | |
| const idsString = syncReadIdsInput.value; | |
| const ids = idsString | |
| .split(",") | |
| .map((s) => parseInt(s.trim(), 10)) | |
| .filter((id) => !isNaN(id) && id > 0 && id < 253); | |
| if (ids.length === 0) { | |
| log("Sync Read: No valid servo IDs provided."); | |
| return; | |
| } | |
| log(`Sync reading positions for servos: ${ids.join(", ")}...`); | |
| try { | |
| // Use scsServoSDK - returns Map or throws | |
| const positions = await scsServoSDK.syncReadPositions(ids); | |
| let logMsg = "Sync Read Successful:\n"; | |
| positions.forEach((pos, id) => { | |
| logMsg += ` Servo ${id}: Position=${pos}\n`; | |
| }); | |
| log(logMsg.trim()); | |
| } catch (err) { | |
| log(`Sync Read Failed: ${err.message}`); | |
| console.error(err); | |
| } | |
| }; | |
| syncWriteBtn.onclick = async () => { | |
| if (!isConnected) { | |
| log("Error: Not connected"); | |
| return; | |
| } | |
| const dataString = syncWriteDataInput.value; | |
| const positionMap = new Map(); | |
| const pairs = dataString.split(","); | |
| let validData = false; | |
| pairs.forEach((pair) => { | |
| const parts = pair.split(":"); | |
| if (parts.length === 2) { | |
| const id = parseInt(parts[0].trim(), 10); | |
| const pos = parseInt(parts[1].trim(), 10); | |
| // Position validation (0-4095) | |
| if (!isNaN(id) && id > 0 && id < 253 && !isNaN(pos) && pos >= 0 && pos <= 4095) { | |
| positionMap.set(id, pos); | |
| validData = true; | |
| } else { | |
| log( | |
| `Sync Write Position: Invalid data pair "${pair}". ID (1-252), Pos (0-4095). Skipping.` | |
| ); | |
| } | |
| } else { | |
| log(`Sync Write Position: Invalid format "${pair}". Skipping.`); | |
| } | |
| }); | |
| if (!validData) { | |
| log("Sync Write Position: No valid servo position data provided."); | |
| return; | |
| } | |
| log( | |
| `Sync writing positions: ${Array.from(positionMap.entries()) | |
| .map(([id, pos]) => `${id}:${pos}`) | |
| .join(", ")}...` | |
| ); | |
| try { | |
| // Use scsServoSDK - throws on error | |
| await scsServoSDK.syncWritePositions(positionMap); | |
| log(`Sync write position command sent successfully.`); | |
| } catch (err) { | |
| log(`Sync Write Position Failed: ${err.message}`); | |
| console.error(err); | |
| } | |
| }; | |
| // New handler for Sync Write Speed | |
| syncWriteSpeedBtn.onclick = async () => { | |
| if (!isConnected) { | |
| log("Error: Not connected"); | |
| return; | |
| } | |
| const dataString = syncWriteSpeedDataInput.value; | |
| const speedMap = new Map(); | |
| const pairs = dataString.split(","); | |
| let validData = false; | |
| pairs.forEach((pair) => { | |
| const parts = pair.split(":"); | |
| if (parts.length === 2) { | |
| const id = parseInt(parts[0].trim(), 10); | |
| const speed = parseInt(parts[1].trim(), 10); | |
| // Speed validation (-10000 to 10000) | |
| if ( | |
| !isNaN(id) && | |
| id > 0 && | |
| id < 253 && | |
| !isNaN(speed) && | |
| speed >= -10000 && | |
| speed <= 10000 | |
| ) { | |
| speedMap.set(id, speed); | |
| validData = true; | |
| } else { | |
| log( | |
| `Sync Write Speed: Invalid data pair "${pair}". ID (1-252), Speed (-10000 to 10000). Skipping.` | |
| ); | |
| } | |
| } else { | |
| log(`Sync Write Speed: Invalid format "${pair}". Skipping.`); | |
| } | |
| }); | |
| if (!validData) { | |
| log("Sync Write Speed: No valid servo speed data provided."); | |
| return; | |
| } | |
| log( | |
| `Sync writing speeds: ${Array.from(speedMap.entries()) | |
| .map(([id, speed]) => `${id}:${speed}`) | |
| .join(", ")}...` | |
| ); | |
| try { | |
| // Use scsServoSDK - throws on error | |
| await scsServoSDK.syncWriteWheelSpeed(speedMap); | |
| log(`Sync write speed command sent successfully.`); | |
| } catch (err) { | |
| log(`Sync Write Speed Failed: ${err.message}`); | |
| console.error(err); | |
| } | |
| }; | |
| scanServosBtn.onclick = async () => { | |
| if (!isConnected) { | |
| log("Error: Not connected"); | |
| return; | |
| } | |
| const startId = parseInt(scanStartIdInput.value, 10); | |
| const endId = parseInt(scanEndIdInput.value, 10); | |
| if (isNaN(startId) || isNaN(endId) || startId < 1 || endId > 252 || startId > endId) { | |
| const errorMsg = | |
| "Error: Invalid scan ID range. Please enter values between 1 and 252, with Start ID <= End ID."; | |
| log(errorMsg); | |
| scanResultsOutput.textContent = errorMsg; // Show error in results area too | |
| return; | |
| } | |
| const startMsg = `Starting servo scan (IDs ${startId}-${endId})...`; | |
| log(startMsg); | |
| scanResultsOutput.textContent = startMsg + "\n"; // Clear and start results area | |
| scanServosBtn.disabled = true; // Disable button during scan | |
| let foundCount = 0; | |
| for (let id = startId; id <= endId; id++) { | |
| let resultMsg = `Scanning ID ${id}... `; | |
| try { | |
| // Attempt to read position. If it succeeds, the servo exists. | |
| // If it throws, the servo likely doesn't exist or there's another issue. | |
| const position = await scsServoSDK.readPosition(id); | |
| foundCount++; | |
| // Servo found, now try to read mode and baud rate | |
| let mode = "ReadError"; | |
| let baudRateIndex = "ReadError"; | |
| try { | |
| mode = await scsServoSDK.readMode(id); | |
| } catch (modeErr) { | |
| log(` Servo ${id}: Error reading mode: ${modeErr.message}`); | |
| } | |
| try { | |
| baudRateIndex = await scsServoSDK.readBaudRate(id); | |
| } catch (baudErr) { | |
| log(` Servo ${id}: Error reading baud rate: ${baudErr.message}`); | |
| } | |
| resultMsg += `FOUND: Pos=${position}, Mode=${mode}, BaudIdx=${baudRateIndex}`; | |
| log( | |
| ` Servo ${id} FOUND: Position=${position}, Mode=${mode}, BaudIndex=${baudRateIndex}` | |
| ); | |
| } catch (err) { | |
| // Check if the error message indicates a timeout or non-response, which is expected for non-existent IDs | |
| // This check might need refinement based on the exact error messages thrown by readPosition | |
| if ( | |
| err.message.includes("timeout") || | |
| err.message.includes("No response") || | |
| err.message.includes("failed: RX") | |
| ) { | |
| resultMsg += `No response`; | |
| // log(` Servo ${id}: No response`); // Optional: reduce log noise | |
| } else { | |
| // Log other unexpected errors | |
| resultMsg += `Error: ${err.message}`; | |
| log(` Servo ${id}: Error during scan: ${err.message}`); | |
| console.error(`Error scanning servo ${id}:`, err); | |
| } | |
| } | |
| scanResultsOutput.textContent += resultMsg + "\n"; // Append result to the results area | |
| scanResultsOutput.scrollTop = scanResultsOutput.scrollHeight; // Auto-scroll | |
| // Optional small delay between scans if needed | |
| // await new Promise(resolve => setTimeout(resolve, 10)); | |
| } | |
| const finishMsg = `Servo scan finished. Found ${foundCount} servo(s).`; | |
| log(finishMsg); | |
| scanResultsOutput.textContent += finishMsg + "\n"; // Add finish message to results area | |
| scanResultsOutput.scrollTop = scanResultsOutput.scrollHeight; // Auto-scroll | |
| scanServosBtn.disabled = false; // Re-enable button | |
| }; | |
| // Initial log | |
| log("Test page loaded. Please connect to a servo controller."); | |
| </script> | |
| </body> | |
| </html> | |