UNPKG

movemaster-robot

Version:

A module to control a Mitsubishi Movemaster EX RV-M1 robotic arm via USB

341 lines (338 loc) 9.54 kB
// src/Robot.ts import { SerialPort } from "serialport"; // src/utils.ts var fmt = (value) => { return value.toFixed(1); }; var delay = (ms) => { return new Promise((resolve) => setTimeout(resolve, ms)); }; // src/defaults.ts var portDefaults = { baudRate: 9600, dataBits: 7, stopBits: 2, parity: "even", rts: true, dtr: true, handshake: "xOnXOff" }; // src/Robot.ts var Robot = class { port = null; position = null; toolIsOpen = false; toolLength = -1; // Default tool length speed = -1; portIsOpen = false; async connect(comPortName) { return new Promise((resolve, _error) => { this.port = new SerialPort({ path: comPortName.trim(), ...portDefaults }); this.port.on("open", () => { console.log(`Connected to port: ${comPortName}`); resolve(); }); this.port.on("data", (data) => { console.log("Data received:", data.toString()); }); this.port.on("error", (err) => { console.error("Error on port:", err); }); }); } isConnected() { return !!this.port?.isOpen; } checkPortOpen() { if (!this.port || !this.port.isOpen) { throw new Error("Port is not open"); } } async sendCommandNoAnswer(command) { this.checkPortOpen(); console.log(`Sending command: ${command}`); this.port.write(command + "\r\n"); await delay(100); } sendCommandWithAnswer(command) { return new Promise((resolve, reject) => { this.checkPortOpen(); console.log(`Sending command with answer: ${command}`); let stringBuffer = ""; this.port.write(command + "\r\n", (err) => { if (err) { console.error("Error writing to port:", err); return reject(err); } }); this.port.on("data", (data) => { stringBuffer += data.toString(); if (stringBuffer.endsWith("\r\n")) { const response = stringBuffer.trim(); console.log("Response received:", response); resolve(response); stringBuffer = ""; } }); }); } async checkRobotErrorCode() { this.checkPortOpen(); const res = await this.sendCommandWithAnswer("ER"); console.log("Error code response:", res); const errorCode = parseInt(res, 10); if (Number.isNaN(errorCode)) { console.error("Invalid error code received:", res); return { ok: false, error: 99 /* UNKNOWN_ERROR */ }; } switch (errorCode) { case 0: console.log("No error detected."); return { ok: true }; case 1: console.error("Hardware error: 1"); return { ok: false, error: 1 /* HARDWARE_ERROR */ }; case 2: console.error("Position or format error: 2"); return { ok: false, error: 2 /* COMMAND_ERROR */ }; } return { ok: false, error: 99 /* UNKNOWN_ERROR */ }; } async moveTo(position, interpolatePoints = 0) { this.checkPortOpen(); const { x, y, z, p, r } = position; if (interpolatePoints === 0) { await this.sendCommandNoAnswer( `MP ${fmt(x)}, ${fmt(y)}, ${fmt(z)}, ${fmt(p)}, ${fmt(r)}` ); } else { await this.sendCommandNoAnswer("PC 1"); await this.sendCommandNoAnswer( `PD 1, ${fmt(x)}, ${fmt(y)}, ${fmt(z)}, ${fmt(p)}, ${fmt(r)}` ); await this.sendCommandNoAnswer( `MS 1, ${interpolatePoints}, ${this.toolIsOpen ? "C" : "O"}` ); } this.position = { x, y, z, p, r }; } /** * Moves the robot arm to the given absolute x-y-z coordinates using current P and R values */ async moveToXYZ(x, y, z, interpolatePoints = 0) { if (!this.position) { throw new Error("Actual position not set"); } return this.moveTo( { x, y, z, p: this.position.p, r: this.position.r }, interpolatePoints ); } async getActualPosition(forceUpdateByHardware = true) { if (!forceUpdateByHardware && this.position) { return { ...this.position }; } const response = await this.sendCommandWithAnswer("WH"); const parts = response.split(",").map((part) => { let cleaned = part.trim().replace("+", ""); if (cleaned.startsWith(".")) { cleaned = `0${cleaned}`; } return parseFloat(cleaned); }); if (parts.length < 5) { throw new Error("Invalid position response format"); } const position = { x: parts[0], y: parts[1], z: parts[2], p: parts[3], r: parts[4] }; this.position = position; return { ...position }; } async setGripper(open) { this.checkPortOpen(); if (open) { await this.sendCommandNoAnswer("GO"); } else { await this.sendCommandNoAnswer("GC"); } this.toolIsOpen = open; } async setSpeed(speed) { this.checkPortOpen(); const roundedSpeed = Math.floor(speed); if (roundedSpeed < 0 || roundedSpeed > 9) { return Promise.reject(new Error("Speed must be between 0 and 9")); } this.speed = roundedSpeed; await this.sendCommandNoAnswer(`SP ${roundedSpeed}`); } async setToolLength(length) { this.checkPortOpen(); this.toolLength = length; await this.sendCommandNoAnswer(`TL ${Math.floor(length)}`); } async withCheck(result) { const cbResult = await result; const errResult = await this.checkRobotErrorCode(); if (errResult.ok) { return cbResult; } else { throw new Error(`Command errored: ${errResult.error}`); } } async movePath(points) { const positionOffset = 1; this.checkPortOpen(); for (let i = 0; i < points.length; i++) { const point = points[i]; await this.sendCommandNoAnswer( `PD ${i + positionOffset}, ${fmt(point.x)}, ${fmt(point.y)}, ${fmt( point.z )}, ${fmt(point.p)}, ${fmt(point.r)}` ); await delay(100); } await this.sendCommandNoAnswer( `MC ${positionOffset}, ${points.length - 1 + positionOffset}` ); } /** * Moves all axes to zero position */ async moveToHomePosition() { this.checkPortOpen(); await this.sendCommandNoAnswer("OG"); } /** * Moves the robot to its mechanical origin position * Must be performed immediately after power on */ async nest() { this.checkPortOpen(); await this.sendCommandNoAnswer("NT"); } /** * Resets the control box */ async reset() { this.checkPortOpen(); await this.sendCommandNoAnswer("RS"); } /** * Defines the pressure of the robot arm gripper * @param startingGripForce Starting gripping force (0-15) * @param retainedGrippingForce Retained gripping force (0-15) * @param startGrippingForceRetentionTime Start gripping force retention time (0-99) */ async setGripPressure(startingGripForce, retainedGrippingForce, startGrippingForceRetentionTime) { this.checkPortOpen(); if (startingGripForce < 0 || startingGripForce > 15) { throw new Error("Starting grip force must be between 0 and 15"); } if (retainedGrippingForce < 0 || retainedGrippingForce > 15) { throw new Error("Retained gripping force must be between 0 and 15"); } if (startGrippingForceRetentionTime < 0 || startGrippingForceRetentionTime > 99) { throw new Error( "Start gripping force retention time must be between 0 and 99" ); } await this.sendCommandNoAnswer( `GP ${startingGripForce}, ${retainedGrippingForce}, ${startGrippingForceRetentionTime}` ); } /** * Rotates the axes relative to the actual position */ async rotateAxis(x, y, z, p, r) { this.checkPortOpen(); await this.sendCommandNoAnswer( `MJ ${fmt(x)}, ${fmt(y)}, ${fmt(z)}, ${fmt(p)}, ${fmt(r)}` ); await this.getActualPosition(true); } async moveDelta(x, y, z, pOrInterpolatePoints, r, interpolatePoints) { if (!this.position) { throw new Error("Actual position not set"); } let p, actualInterpolatePoints; if (r === void 0 && interpolatePoints === void 0) { p = this.position.p; r = this.position.r; actualInterpolatePoints = pOrInterpolatePoints ?? 0; } else { p = pOrInterpolatePoints ?? this.position.p; r = r ?? this.position.r; actualInterpolatePoints = interpolatePoints ?? 0; } return this.moveTo( { x: this.position.x + x, y: this.position.y + y, z: this.position.z + z, p: this.position.p + p, r: this.position.r + r }, actualInterpolatePoints ); } /** * Utility function to clean up R value based on X and Y coordinates */ cleanUpRValue(x, y, rTarget) { return rTarget + Math.atan2(x, y) * 180 / Math.PI; } /** * Gets if the tool gripper is closed */ getGripperClosed() { return !this.toolIsOpen; } disconnect() { if (this.port) { this.port.close((err) => { if (err) { console.error("Error closing port:", err); } else { console.log("Port closed successfully."); } }); this.port = null; } else { console.log("No port to close."); } } getPosition() { return this.position; } getToolLength() { return this.toolLength; } getSpeed() { return this.speed; } async listPorts() { const ports = await SerialPort.list(); return ports.map((port) => port.path); } }; var Robot_default = Robot; export { Robot_default as Robot }; //# sourceMappingURL=index.mjs.map