dynamixel
Version:
Node.js library for controlling DYNAMIXEL servo motors via U2D2 interface with Protocol 2.0 support
439 lines (370 loc) âĸ 15.2 kB
JavaScript
/**
* Electron Renderer Process Example
* Demonstrates using the DYNAMIXEL library with Web Serial API in Electron renderer
*
* This file should be loaded in your Electron renderer process (web page).
* Make sure to enable Web Serial API in your Electron main process.
*/
import { DynamixelController, WebSerialConnection } from '../index.js';
// Environment detection
function detectEnvironment() {
const isElectron = typeof window !== 'undefined' && window.process && window.process.type === 'renderer';
const isWebSerialSupported = WebSerialConnection.isSupported();
const userAgent = navigator.userAgent;
return {
isElectron,
isWebSerialSupported,
isChrome: userAgent.includes('Chrome'),
userAgent
};
}
// Main application class for Electron renderer
class ElectronDynamixelApp {
constructor() {
this.controller = null;
this.devices = [];
this.isConnected = false;
// UI elements (assuming you have these in your HTML)
this.setupUI();
}
setupUI() {
// Create UI elements programmatically or reference existing ones
this.createStatusPanel();
this.createControlPanel();
this.createDeviceList();
this.createConsoleOutput();
}
createStatusPanel() {
const statusPanel = document.createElement('div');
statusPanel.id = 'status-panel';
statusPanel.innerHTML = `
<h2>đ Connection Status</h2>
<div id="connection-status">Disconnected</div>
<div id="environment-info"></div>
<button id="connect-btn">Connect to DYNAMIXEL</button>
<button id="disconnect-btn" disabled>Disconnect</button>
`;
document.body.appendChild(statusPanel);
// Event listeners
document.getElementById('connect-btn').addEventListener('click', () => this.connect());
document.getElementById('disconnect-btn').addEventListener('click', () => this.disconnect());
this.updateEnvironmentInfo();
}
createControlPanel() {
const controlPanel = document.createElement('div');
controlPanel.id = 'control-panel';
controlPanel.innerHTML = `
<h2>đŽ Device Control</h2>
<div id="discovery-section">
<button id="quick-discovery-btn" disabled>Quick Discovery (IDs 1-20)</button>
<button id="full-discovery-btn" disabled>Full Discovery (IDs 1-252)</button>
<div id="discovery-progress"></div>
</div>
<div id="device-control">
<label for="device-select">Select Device:</label>
<select id="device-select" disabled>
<option value="">No devices found</option>
</select>
<div id="device-actions">
<button id="ping-device-btn" disabled>Ping Device</button>
<button id="led-on-btn" disabled>LED On</button>
<button id="led-off-btn" disabled>LED Off</button>
<button id="read-status-btn" disabled>Read Status</button>
</div>
</div>
`;
document.body.appendChild(controlPanel);
// Event listeners
document.getElementById('quick-discovery-btn').addEventListener('click', () => this.quickDiscovery());
document.getElementById('full-discovery-btn').addEventListener('click', () => this.fullDiscovery());
document.getElementById('ping-device-btn').addEventListener('click', () => this.pingSelectedDevice());
document.getElementById('led-on-btn').addEventListener('click', () => this.setLED(true));
document.getElementById('led-off-btn').addEventListener('click', () => this.setLED(false));
document.getElementById('read-status-btn').addEventListener('click', () => this.readDeviceStatus());
}
createDeviceList() {
const deviceList = document.createElement('div');
deviceList.id = 'device-list';
deviceList.innerHTML = `
<h2>đ Discovered Devices</h2>
<div id="devices-container">No devices discovered yet</div>
`;
document.body.appendChild(deviceList);
}
createConsoleOutput() {
const consoleOutput = document.createElement('div');
consoleOutput.id = 'console-output';
consoleOutput.innerHTML = `
<h2>đ Console Output</h2>
<div id="console-content" style="height: 200px; overflow-y: scroll; border: 1px solid #ccc; padding: 10px; font-family: monospace; font-size: 12px;"></div>
<button id="clear-console-btn">Clear Console</button>
`;
document.body.appendChild(consoleOutput);
document.getElementById('clear-console-btn').addEventListener('click', () => this.clearConsole());
// Redirect console.log to our console output
this.setupConsoleRedirect();
}
setupConsoleRedirect() {
const originalLog = console.log;
const originalError = console.error;
const consoleContent = document.getElementById('console-content');
console.log = (...args) => {
originalLog(...args);
this.addToConsole('info', args.join(' '));
};
console.error = (...args) => {
originalError(...args);
this.addToConsole('error', args.join(' '));
};
}
addToConsole(type, message) {
const consoleContent = document.getElementById('console-content');
const timestamp = new Date().toLocaleTimeString();
const color = type === 'error' ? 'red' : 'black';
consoleContent.innerHTML += `<div style="color: ${color};">[${timestamp}] ${message}</div>`;
consoleContent.scrollTop = consoleContent.scrollHeight;
}
clearConsole() {
document.getElementById('console-content').innerHTML = '';
}
updateEnvironmentInfo() {
const env = detectEnvironment();
const envInfo = document.getElementById('environment-info');
let statusHtml = `
<div><strong>Environment:</strong> ${env.isElectron ? 'Electron Renderer' : 'Web Browser'}</div>
<div><strong>Web Serial Support:</strong> ${env.isWebSerialSupported ? 'â
Available' : 'â Not Available'}</div>
<div><strong>Chrome-based:</strong> ${env.isChrome ? 'Yes' : 'No'}</div>
`;
if (!env.isWebSerialSupported) {
statusHtml += `<div style="color: red;"><strong>â ī¸ Web Serial API not supported!</strong></div>`;
}
envInfo.innerHTML = statusHtml;
}
async connect() {
try {
console.log('đ Attempting to connect...');
// Force Web Serial connection in renderer process
this.controller = new DynamixelController({
connectionType: 'webserial',
timeout: 1000
});
// Set up event listeners
this.setupControllerEvents();
// Connect (this will trigger the browser's serial port selection dialog)
const success = await this.controller.connect();
if (success) {
console.log('â
Connected successfully!');
this.isConnected = true;
this.updateConnectionStatus();
} else {
throw new Error('Connection failed');
}
} catch (error) {
console.error('â Connection failed:', error.message);
if (error.message.includes('user cancelled')) {
console.log('âšī¸ User cancelled port selection');
} else if (error.message.includes('SecurityError')) {
console.error('đ Security error - check browser permissions for Web Serial API');
} else if (error.message.includes('NotFoundError')) {
console.error('đ No compatible devices found');
}
}
}
async disconnect() {
if (this.controller) {
await this.controller.disconnect();
this.controller = null;
this.isConnected = false;
this.devices = [];
this.updateConnectionStatus();
this.updateDeviceList();
}
}
setupControllerEvents() {
this.controller.on('connected', () => {
console.log('đ Controller connected!');
this.updateConnectionStatus();
});
this.controller.on('disconnected', () => {
console.log('đą Controller disconnected');
this.isConnected = false;
this.updateConnectionStatus();
});
this.controller.on('deviceFound', (deviceInfo) => {
console.log(`đ Found device: ID ${deviceInfo.id}, Model: ${deviceInfo.modelNumber}`);
});
this.controller.on('discoveryComplete', (devices) => {
console.log(`â
Discovery complete! Found ${devices.length} device(s)`);
this.devices = devices;
this.updateDeviceList();
});
this.controller.on('error', (error) => {
console.error('â Controller error:', error.message);
});
}
updateConnectionStatus() {
const statusDiv = document.getElementById('connection-status');
const connectBtn = document.getElementById('connect-btn');
const disconnectBtn = document.getElementById('disconnect-btn');
const discoveryBtns = document.querySelectorAll('#discovery-section button');
const deviceSelect = document.getElementById('device-select');
const deviceBtns = document.querySelectorAll('#device-actions button');
if (this.isConnected) {
statusDiv.innerHTML = 'â
Connected';
statusDiv.style.color = 'green';
connectBtn.disabled = true;
disconnectBtn.disabled = false;
discoveryBtns.forEach(btn => btn.disabled = false);
} else {
statusDiv.innerHTML = 'â Disconnected';
statusDiv.style.color = 'red';
connectBtn.disabled = false;
disconnectBtn.disabled = true;
discoveryBtns.forEach(btn => btn.disabled = true);
deviceSelect.disabled = true;
deviceBtns.forEach(btn => btn.disabled = true);
}
}
async quickDiscovery() {
this.updateDiscoveryProgress('Starting quick discovery...');
try {
const devices = await this.controller.quickDiscovery((current, total, id) => {
this.updateDiscoveryProgress(`Scanning ID ${id}... (${current}/${total})`);
});
this.updateDiscoveryProgress(`Quick discovery complete: ${devices.length} devices found`);
} catch (error) {
console.error('â Discovery failed:', error.message);
this.updateDiscoveryProgress('Discovery failed');
}
}
async fullDiscovery() {
this.updateDiscoveryProgress('Starting full discovery (this may take a while)...');
try {
const devices = await this.controller.fullDiscovery((current, total, id) => {
if (current % 10 === 0) {
this.updateDiscoveryProgress(`Scanning... (${current}/${total})`);
}
});
this.updateDiscoveryProgress(`Full discovery complete: ${devices.length} devices found`);
} catch (error) {
console.error('â Discovery failed:', error.message);
this.updateDiscoveryProgress('Discovery failed');
}
}
updateDiscoveryProgress(message) {
document.getElementById('discovery-progress').innerHTML = message;
}
updateDeviceList() {
const container = document.getElementById('devices-container');
const deviceSelect = document.getElementById('device-select');
const deviceBtns = document.querySelectorAll('#device-actions button');
if (this.devices.length === 0) {
container.innerHTML = 'No devices discovered yet';
deviceSelect.innerHTML = '<option value="">No devices found</option>';
deviceSelect.disabled = true;
deviceBtns.forEach(btn => btn.disabled = true);
return;
}
// Update device list display
let html = '<table style="width: 100%; border-collapse: collapse;">';
html += '<tr><th>ID</th><th>Model</th><th>Firmware</th></tr>';
this.devices.forEach(device => {
const modelName = DynamixelController.getModelName(device.modelNumber);
html += `<tr>
<td style="border: 1px solid #ccc; padding: 5px;">${device.id}</td>
<td style="border: 1px solid #ccc; padding: 5px;">${modelName}</td>
<td style="border: 1px solid #ccc; padding: 5px;">${device.firmwareVersion}</td>
</tr>`;
});
html += '</table>';
container.innerHTML = html;
// Update device select dropdown
deviceSelect.innerHTML = this.devices.map(device =>
`<option value="${device.id}">ID ${device.id} - ${DynamixelController.getModelName(device.modelNumber)}</option>`
).join('');
deviceSelect.disabled = false;
deviceBtns.forEach(btn => btn.disabled = false);
}
getSelectedDevice() {
const deviceSelect = document.getElementById('device-select');
const selectedId = parseInt(deviceSelect.value);
return this.controller.getDevice(selectedId);
}
async pingSelectedDevice() {
const device = this.getSelectedDevice();
if (!device) return;
try {
console.log(`đĄ Pinging device ${device.id}...`);
const result = await device.ping();
console.log(`â
Ping successful: ${JSON.stringify(result)}`);
} catch (error) {
console.error(`â Ping failed: ${error.message}`);
}
}
async setLED(on) {
const device = this.getSelectedDevice();
if (!device) return;
try {
console.log(`đĄ Setting LED ${on ? 'ON' : 'OFF'} for device ${device.id}...`);
await device.setLED(on);
console.log(`â
LED ${on ? 'ON' : 'OFF'} successful`);
} catch (error) {
console.error(`â LED control failed: ${error.message}`);
}
}
async readDeviceStatus() {
const device = this.getSelectedDevice();
if (!device) return;
try {
console.log(`đ Reading status from device ${device.id}...`);
const [temperature, voltage, position, torqueEnabled] = await Promise.all([
device.getPresentTemperature(),
device.getPresentVoltage(),
device.getPresentPosition(),
device.getTorqueEnable()
]);
const status = {
temperature: `${temperature}°C`,
voltage: `${device.voltageToVolts(voltage).toFixed(1)}V`,
position: `${position} (${device.positionToDegrees(position).toFixed(1)}°)`,
torqueEnabled: torqueEnabled ? 'Enabled' : 'Disabled'
};
console.log(`â
Device status:`, status);
// Show status in a nice format
alert(`Device ${device.id} Status:\n\n` +
`Temperature: ${status.temperature}\n` +
`Voltage: ${status.voltage}\n` +
`Position: ${status.position}\n` +
`Torque: ${status.torqueEnabled}`);
} catch (error) {
console.error(`â Status read failed: ${error.message}`);
}
}
}
// Initialize the app when DOM is ready
document.addEventListener('DOMContentLoaded', () => {
console.log('đ Initializing Electron DYNAMIXEL app...');
// Add some basic styling
document.head.innerHTML += `
<style>
body { font-family: Arial, sans-serif; margin: 20px; }
h2 { color: #333; margin-bottom: 10px; }
button { margin: 5px; padding: 8px 16px; }
button:disabled { opacity: 0.5; }
select { margin: 5px; padding: 5px; }
div[id$="-panel"], div[id$="-list"], div[id$="-output"] {
border: 1px solid #ddd;
padding: 15px;
margin: 10px 0;
border-radius: 5px;
}
table { border-collapse: collapse; }
th, td { border: 1px solid #ccc; padding: 8px; text-align: left; }
th { background-color: #f5f5f5; }
</style>
`;
const app = new ElectronDynamixelApp();
// Make app globally available for debugging
window.dynamixelApp = app;
console.log('â
App initialized. Click "Connect to DYNAMIXEL" to begin.');
});