UNPKG

dynamixel

Version:

Node.js library for controlling DYNAMIXEL servo motors via U2D2 interface with Protocol 2.0 support

778 lines (730 loc) 25.3 kB
/** * Motor Profile System for DYNAMIXEL devices * Provides predefined configurations for different motor models and use cases * Inspired by DynaNode's MotorProfile architecture */ export class MotorProfiles { constructor() { this.profiles = new Map(); this.customProfiles = new Map(); this.initializeDefaultProfiles(); } initializeDefaultProfiles() { // AX Series Profiles this.profiles.set('AX-12A', { modelNumber: 12, series: 'AX', specs: { stallTorque: 1.5, // kg·cm maxSpeed: 59, // RPM operatingVoltage: [9.0, 12.0], // V resolution: 1024, positionRange: [0, 1023], temperatureRange: [-5, 70], // Celsius weight: 54.6, // grams dimensions: [32, 50, 40] // mm [width, depth, height] }, defaultSettings: { torqueEnable: 1, goalPosition: 512, movingSpeed: 32, torqueLimit: 1023, alarmLED: 36, alarmShutdown: 36, returnDelay: 250, statusReturnLevel: 2 }, operatingModes: ['joint', 'wheel'], recommendedProfiles: { precision: { movingSpeed: 10, torqueLimit: 512, description: 'High precision, low speed positioning' }, balanced: { movingSpeed: 32, torqueLimit: 1023, description: 'Balanced speed and torque' }, fast: { movingSpeed: 100, torqueLimit: 1023, description: 'Fast positioning with full torque' } } }); this.profiles.set('MX-28', { modelNumber: 29, series: 'MX', specs: { stallTorque: 2.5, // kg·cm maxSpeed: 55, // RPM operatingVoltage: [10.0, 14.8], // V resolution: 4096, positionRange: [0, 4095], temperatureRange: [-5, 80], // Celsius weight: 77, // grams dimensions: [35.6, 50.6, 35.5] // mm }, defaultSettings: { torqueEnable: 1, goalPosition: 2048, goalVelocity: 50, goalPWM: 885, operatingMode: 3, // Position control returnDelay: 250, statusReturnLevel: 2 }, operatingModes: ['current', 'velocity', 'position', 'extended_position', 'current_position', 'pwm'], recommendedProfiles: { servo: { operatingMode: 3, // Position control goalVelocity: 30, description: 'Standard servo operation' }, wheel: { operatingMode: 1, // Velocity control goalVelocity: 50, description: 'Continuous rotation wheel mode' }, compliant: { operatingMode: 0, // Current control goalCurrent: 100, description: 'Force-controlled compliant operation' } } }); this.profiles.set('XM430-W350', { modelNumber: 1030, series: 'X', specs: { stallTorque: 4.1, // kg·cm maxSpeed: 46, // RPM operatingVoltage: [10.0, 14.8], // V resolution: 4096, positionRange: [0, 4095], temperatureRange: [-5, 80], // Celsius weight: 82, // grams dimensions: [28.5, 46.5, 34] // mm }, defaultSettings: { torqueEnable: 1, goalPosition: 2048, goalVelocity: 50, goalCurrent: 200, operatingMode: 3, // Position control returnDelay: 250, statusReturnLevel: 2, velocityLimit: 480, accelerationLimit: 32767 }, operatingModes: ['current', 'velocity', 'position', 'extended_position', 'current_position', 'pwm'], recommendedProfiles: { precision: { operatingMode: 3, goalVelocity: 20, accelerationLimit: 1000, description: 'High precision positioning' }, dynamic: { operatingMode: 3, goalVelocity: 100, accelerationLimit: 10000, description: 'Dynamic positioning with smooth acceleration' }, force_control: { operatingMode: 0, goalCurrent: 150, description: 'Force-controlled operation' } } }); // XC330-M288 Profile - New Addition this.profiles.set('XC330-M288', { modelNumber: 1300, series: 'XC', specs: { stallTorque: 0.93, // N·m at 5.0V (recommended voltage) maxSpeed: 81, // RPM at 5.0V operatingVoltage: [3.7, 6.0], // V (recommended 5.0V) recommendedVoltage: 5.0, // V resolution: 4096, positionRange: [0, 4095], temperatureRange: [-5, 70], // Celsius weight: 23, // grams dimensions: [20.0, 34.0, 26.0], // mm [width, height, depth] gearRatio: 288.35, standbyCurrent: 17, // mA maxCurrent: 1800 // mA at 5.0V }, defaultSettings: { id: 1, baudRate: 1, // 57600 bps (factory default) returnDelayTime: 250, // 0.5ms driveMode: 0, operatingMode: 3, // Position Control Mode homingOffset: 0, movingThreshold: 10, temperatureLimit: 70, maxVoltageLimit: 70, // 7.0V minVoltageLimit: 35, // 3.5V pwmLimit: 885, // 100% currentLimit: 2352, // 2.352A velocityLimit: 350, // 0.229 rev/min units maxPositionLimit: 4095, minPositionLimit: 0, torqueEnable: 0, led: 0, statusReturnLevel: 2, velocityIGain: 1600, velocityPGain: 50, positionDGain: 500, positionIGain: 0, positionPGain: 1100, feedforward2ndGain: 0, feedforward1stGain: 0, goalPosition: 2048, profileAcceleration: 0, profileVelocity: 0 }, operatingModes: [ 'current', // 0 - Current Control Mode 'velocity', // 1 - Velocity Control Mode 'position', // 3 - Position Control Mode (default) 'extended_position', // 4 - Extended Position Control Mode 'current_position', // 5 - Current-based Position Control Mode 'pwm' // 16 - PWM Control Mode ], controlTable: { // EEPROM Area modelNumber: { address: 0, size: 2, access: 'R', initialValue: 1300 }, modelInformation: { address: 2, size: 4, access: 'R' }, firmwareVersion: { address: 6, size: 1, access: 'R' }, id: { address: 7, size: 1, access: 'RW', initialValue: 1, range: [0, 252] }, baudRate: { address: 8, size: 1, access: 'RW', initialValue: 1, range: [0, 6] }, returnDelayTime: { address: 9, size: 1, access: 'RW', initialValue: 250, range: [0, 254] }, driveMode: { address: 10, size: 1, access: 'RW', initialValue: 0, range: [0, 13] }, operatingMode: { address: 11, size: 1, access: 'RW', initialValue: 3, range: [0, 16] }, secondaryId: { address: 12, size: 1, access: 'RW', initialValue: 255, range: [0, 252] }, protocolType: { address: 13, size: 1, access: 'RW', initialValue: 2, range: [2, 22] }, homingOffset: { address: 20, size: 4, access: 'RW', initialValue: 0, range: [-1044479, 1044479] }, movingThreshold: { address: 24, size: 4, access: 'RW', initialValue: 10, range: [0, 1023] }, temperatureLimit: { address: 31, size: 1, access: 'RW', initialValue: 70, range: [0, 100] }, maxVoltageLimit: { address: 32, size: 2, access: 'RW', initialValue: 70, range: [31, 70] }, minVoltageLimit: { address: 34, size: 2, access: 'RW', initialValue: 35, range: [31, 70] }, pwmLimit: { address: 36, size: 2, access: 'RW', initialValue: 885, range: [0, 885] }, currentLimit: { address: 38, size: 2, access: 'RW', initialValue: 2352, range: [0, 2352] }, velocityLimit: { address: 44, size: 4, access: 'RW', initialValue: 350, range: [0, 2047] }, maxPositionLimit: { address: 48, size: 4, access: 'RW', initialValue: 4095, range: [0, 4095] }, minPositionLimit: { address: 52, size: 4, access: 'RW', initialValue: 0, range: [0, 4095] }, startupConfiguration: { address: 60, size: 1, access: 'RW', initialValue: 0, range: [0, 3] }, pwmSlope: { address: 62, size: 1, access: 'RW', initialValue: 140, range: [1, 255] }, shutdown: { address: 63, size: 1, access: 'RW', initialValue: 52 }, // RAM Area torqueEnable: { address: 64, size: 1, access: 'RW', initialValue: 0, range: [0, 1] }, led: { address: 65, size: 1, access: 'RW', initialValue: 0, range: [0, 1] }, statusReturnLevel: { address: 68, size: 1, access: 'RW', initialValue: 2, range: [0, 2] }, registeredInstruction: { address: 69, size: 1, access: 'R', initialValue: 0, range: [0, 1] }, hardwareErrorStatus: { address: 70, size: 1, access: 'R', initialValue: 0 }, velocityIGain: { address: 76, size: 2, access: 'RW', initialValue: 1600, range: [0, 16383] }, velocityPGain: { address: 78, size: 2, access: 'RW', initialValue: 50, range: [0, 16383] }, positionDGain: { address: 80, size: 2, access: 'RW', initialValue: 500, range: [0, 16383] }, positionIGain: { address: 82, size: 2, access: 'RW', initialValue: 0, range: [0, 16383] }, positionPGain: { address: 84, size: 2, access: 'RW', initialValue: 1100, range: [0, 16383] }, feedforward2ndGain: { address: 88, size: 2, access: 'RW', initialValue: 0, range: [0, 16383] }, feedforward1stGain: { address: 90, size: 2, access: 'RW', initialValue: 0, range: [0, 16383] }, busWatchdog: { address: 98, size: 1, access: 'RW', initialValue: 0, range: [1, 127] }, goalPWM: { address: 100, size: 2, access: 'RW' }, goalCurrent: { address: 102, size: 2, access: 'RW' }, goalVelocity: { address: 104, size: 4, access: 'RW' }, profileAcceleration: { address: 108, size: 4, access: 'RW', initialValue: 0, range: [0, 32767] }, profileVelocity: { address: 112, size: 4, access: 'RW', initialValue: 0, range: [0, 32767] }, goalPosition: { address: 116, size: 4, access: 'RW' }, realtimeTick: { address: 120, size: 2, access: 'R', range: [0, 32767] }, moving: { address: 122, size: 1, access: 'R', initialValue: 0, range: [0, 1] }, movingStatus: { address: 123, size: 1, access: 'R', initialValue: 0 }, presentPWM: { address: 124, size: 2, access: 'R' }, presentCurrent: { address: 126, size: 2, access: 'R' }, presentVelocity: { address: 128, size: 4, access: 'R' }, presentPosition: { address: 132, size: 4, access: 'R' }, velocityTrajectory: { address: 136, size: 4, access: 'R' }, positionTrajectory: { address: 140, size: 4, access: 'R' }, presentInputVoltage: { address: 144, size: 2, access: 'R' }, presentTemperature: { address: 146, size: 1, access: 'R' }, backupReady: { address: 147, size: 1, access: 'R', range: [0, 1] } }, recommendedProfiles: { precision: { operatingMode: 3, // Position control goalVelocity: 15, // Slow speed for precision profileAcceleration: 500, profileVelocity: 30, positionPGain: 1500, // Higher P gain for precision positionIGain: 50, positionDGain: 800, description: 'High precision positioning with increased gains' }, balanced: { operatingMode: 3, // Position control goalVelocity: 40, // Moderate speed profileAcceleration: 1000, profileVelocity: 80, positionPGain: 1100, // Default gains positionIGain: 0, positionDGain: 500, description: 'Balanced speed and precision' }, fast: { operatingMode: 3, // Position control goalVelocity: 80, // Fast movement profileAcceleration: 2000, profileVelocity: 150, positionPGain: 800, // Lower P gain to reduce oscillation positionIGain: 0, positionDGain: 300, description: 'Fast positioning with smooth movement' }, wheel: { operatingMode: 1, // Velocity control goalVelocity: 50, profileAcceleration: 1500, velocityPGain: 60, velocityIGain: 2000, description: 'Continuous rotation for wheel applications' }, compliant: { operatingMode: 0, // Current control goalCurrent: 300, // Low torque for compliance currentLimit: 800, description: 'Force-controlled compliant operation' }, micro_servo: { operatingMode: 3, // Position control goalVelocity: 20, profileAcceleration: 300, profileVelocity: 40, positionPGain: 2000, // Very high precision for micro movements positionIGain: 100, positionDGain: 1000, description: 'Micro servo operation for small, precise movements' } }, conversions: { position: { unit: 'pulse', resolution: 4096, range: [0, 4095], degreesPerUnit: 0.088 // 360° / 4096 steps }, velocity: { unit: 'rev/min', scale: 0.229 // RPM per unit }, current: { unit: 'mA', scale: 1.0 // 1 unit = 1 mA }, voltage: { unit: 'V', scale: 0.1 // 1 unit = 0.1V }, temperature: { unit: '°C', scale: 1.0 // 1 unit = 1°C }, pwm: { unit: '%', scale: 0.113 // 1 unit = 0.113% } }, features: { rcProtocols: ['SBUS', 'iBUS', 'RC-PWM'], metalGears: true, bearing: true, contactlessEncoder: true, energySaving: true, profileControl: true, multiTurn: true // Extended position control mode } }); // Add more motor profiles... this.addRobotArmProfile(); this.addWheelRobotProfile(); this.addGripperProfile(); } addRobotArmProfile() { this.profiles.set('ROBOT_ARM_6DOF', { type: 'application_profile', description: '6DOF Robot Arm Configuration', joints: { base: { motorModel: 'XM430-W350', settings: { operatingMode: 3, goalVelocity: 30, accelerationLimit: 2000, positionPGain: 800, positionIGain: 0, positionDGain: 0 }, limits: { minPosition: 0, maxPosition: 4095, maxVelocity: 100 } }, shoulder: { motorModel: 'XM430-W350', settings: { operatingMode: 3, goalVelocity: 40, accelerationLimit: 3000, positionPGain: 900 } }, elbow: { motorModel: 'MX-28', settings: { operatingMode: 3, goalVelocity: 50, accelerationLimit: 4000 } }, wrist1: { motorModel: 'AX-12A', settings: { movingSpeed: 50 } }, wrist2: { motorModel: 'AX-12A', settings: { movingSpeed: 50 } }, wrist3: { motorModel: 'AX-12A', settings: { movingSpeed: 30 } } } }); } addWheelRobotProfile() { this.profiles.set('MOBILE_ROBOT_4WD', { type: 'application_profile', description: '4-Wheel Drive Mobile Robot', wheels: { frontLeft: { motorModel: 'XM430-W350', settings: { operatingMode: 1, // Velocity control goalVelocity: 0, accelerationLimit: 5000 } }, frontRight: { motorModel: 'XM430-W350', settings: { operatingMode: 1, goalVelocity: 0, accelerationLimit: 5000 } }, backLeft: { motorModel: 'XM430-W350', settings: { operatingMode: 1, goalVelocity: 0, accelerationLimit: 5000 } }, backRight: { motorModel: 'XM430-W350', settings: { operatingMode: 1, goalVelocity: 0, accelerationLimit: 5000 } } }, kinematics: { wheelbase: 200, // mm trackWidth: 150, // mm wheelRadius: 30 // mm } }); } addGripperProfile() { this.profiles.set('ADAPTIVE_GRIPPER', { type: 'application_profile', description: 'Adaptive Gripper with Force Control', fingers: { finger1: { motorModel: 'MX-28', settings: { operatingMode: 0, // Current control goalCurrent: 100, currentLimit: 200 } }, finger2: { motorModel: 'MX-28', settings: { operatingMode: 0, goalCurrent: 100, currentLimit: 200 } } }, graspingModes: { gentle: { goalCurrent: 50, description: 'Gentle grasp for delicate objects' }, firm: { goalCurrent: 150, description: 'Firm grasp for secure holding' }, maximum: { goalCurrent: 200, description: 'Maximum force grasp' } } }); } /** * Get profile for a motor model */ getProfile(modelName) { return this.profiles.get(modelName) || this.customProfiles.get(modelName); } /** * Get recommended settings for a motor model and use case */ getRecommendedSettings(modelName, useCase = 'balanced') { const profile = this.getProfile(modelName); if (!profile) return null; const baseSettings = { ...profile.defaultSettings }; const recommendedProfile = profile.recommendedProfiles?.[useCase]; if (recommendedProfile) { return { ...baseSettings, ...recommendedProfile }; } return baseSettings; } /** * Create a custom profile */ createCustomProfile(name, profileData) { this.customProfiles.set(name, { ...profileData, custom: true, createdAt: Date.now() }); } /** * Get all available profiles */ getAllProfiles() { const allProfiles = new Map(); // Add default profiles for (const [name, profile] of this.profiles) { allProfiles.set(name, { ...profile, type: profile.type || 'motor_profile' }); } // Add custom profiles for (const [name, profile] of this.customProfiles) { allProfiles.set(name, { ...profile, type: 'custom_profile' }); } return allProfiles; } /** * Get profiles by motor series */ getProfilesBySeries(series) { const profiles = []; for (const [name, profile] of this.profiles) { if (profile.series === series) { profiles.push({ name, ...profile }); } } return profiles; } /** * Get application profiles */ getApplicationProfiles() { const profiles = []; for (const [name, profile] of this.profiles) { if (profile.type === 'application_profile') { profiles.push({ name, ...profile }); } } return profiles; } /** * Validate profile compatibility */ validateProfile(motorModel, profileSettings) { const profile = this.getProfile(motorModel); if (!profile) return { valid: false, errors: ['Unknown motor model'] }; const errors = []; const specs = profile.specs; // Validate position range if (profileSettings.goalPosition !== undefined) { if (profileSettings.goalPosition < specs.positionRange[0] || profileSettings.goalPosition > specs.positionRange[1]) { errors.push(`Goal position ${profileSettings.goalPosition} outside valid range [${specs.positionRange[0]}, ${specs.positionRange[1]}]`); } } // Validate velocity if (profileSettings.goalVelocity !== undefined && specs.maxSpeed) { const maxVelUnits = this.rpmToVelocityUnits(specs.maxSpeed, profile); if (profileSettings.goalVelocity > maxVelUnits) { errors.push(`Goal velocity ${profileSettings.goalVelocity} exceeds maximum ${maxVelUnits}`); } } return { valid: errors.length === 0, errors, warnings: [] }; } /** * Convert RPM to velocity units for a specific motor */ rpmToVelocityUnits(rpm, profile) { // This varies by motor series - simplified calculation const resolution = profile.specs.resolution; return Math.round((rpm * resolution) / 60); } /** * Get optimal settings for multi-motor synchronization */ getSynchronizationSettings(motorModels) { const profiles = motorModels.map(model => this.getProfile(model)).filter(Boolean); if (profiles.length === 0) return null; // Find common denominator settings for synchronized operation const minMaxSpeed = Math.min(...profiles.map(p => p.specs.maxSpeed)); const commonReturnDelay = Math.max(...profiles.map(p => p.defaultSettings.returnDelay || 250)); return { recommendedVelocity: Math.round(minMaxSpeed * 0.7), // 70% of slowest motor returnDelay: commonReturnDelay, statusReturnLevel: 1, // Reduce traffic for sync recommendedUpdateRate: Math.max(50, commonReturnDelay * 2) // ms }; } /** * Export profile as JSON */ exportProfile(profileName) { const profile = this.getProfile(profileName); return profile ? JSON.stringify(profile, null, 2) : null; } /** * Import profile from JSON */ importProfile(name, jsonData) { try { const profileData = JSON.parse(jsonData); this.createCustomProfile(name, profileData); return { success: true }; } catch (error) { return { success: false, error: error.message }; } } /** * Get model number for a motor model name */ getModelNumber(modelName) { const profile = this.getProfile(modelName); return profile?.modelNumber || null; } /** * Find motor model by model number */ getModelByNumber(modelNumber) { for (const [name, profile] of this.profiles) { if (profile.modelNumber === modelNumber) { return name; } } return null; } /** * Get operating modes for a motor model */ getOperatingModes(modelName) { const profile = this.getProfile(modelName); return profile?.operatingModes || []; } /** * Get operating mode constants for a motor model */ getOperatingModeConstants(modelName) { const profile = this.getProfile(modelName); if (!profile?.operatingModes) return {}; const constants = {}; profile.operatingModes.forEach((mode, index) => { // Convert mode names to constants format const constantName = mode.toUpperCase().replace(/-/g, '_') + '_CONTROL'; // Map to actual operating mode values based on DYNAMIXEL protocol switch (mode) { case 'current': constants[constantName] = 0; break; case 'velocity': constants[constantName] = 1; break; case 'position': constants[constantName] = 3; break; case 'extended_position': constants[constantName] = 4; break; case 'current_position': constants[constantName] = 5; break; case 'pwm': constants[constantName] = 16; break; default: constants[constantName] = index; } }); return constants; } /** * Get default settings for a motor model */ getDefaultSettings(modelName) { const profile = this.getProfile(modelName); return profile?.defaultSettings ? { ...profile.defaultSettings } : null; } /** * Get specifications for a motor model */ getSpecs(modelName) { const profile = this.getProfile(modelName); return profile?.specs ? { ...profile.specs } : null; } /** * Get control table information for a motor model */ getControlTable(modelName) { const profile = this.getProfile(modelName); return profile?.controlTable ? { ...profile.controlTable } : null; } /** * Get control table address for a specific register */ getControlTableAddress(modelName, registerName) { const profile = this.getProfile(modelName); const ctEntry = profile?.controlTable?.[registerName]; return ctEntry?.address; } /** * Get conversion factors for a motor model */ getConversions(modelName) { const profile = this.getProfile(modelName); return profile?.conversions ? { ...profile.conversions } : null; } /** * Check if a motor model supports a feature */ hasFeature(modelName, featureName) { const profile = this.getProfile(modelName); return profile?.features?.[featureName] === true; } /** * Get all model numbers as a lookup map (replaces MODEL_NUMBERS from constants) */ getModelNumbers() { const modelNumbers = {}; for (const [name, profile] of this.profiles) { if (profile.modelNumber && profile.type !== 'application_profile') { // Convert model name to constant format (e.g., 'XC330-M288' -> 'XC330_M288') const constantName = name.replace(/-/g, '_').toUpperCase(); modelNumbers[constantName] = profile.modelNumber; } } return modelNumbers; } }