basic robot config class holds motors and assignments, curves are generated on load (all simulated only so far)

node_mode
realrobots 2025-10-13 20:15:12 +08:00
parent 55b2c178e6
commit 94daeafb79
5 changed files with 144 additions and 42 deletions

View File

@ -42,15 +42,15 @@ export class CurveEditor {
this.panStart = { x: 0, y: 0 }; this.panStart = { x: 0, y: 0 };
// Default curve // Default curve
this.setCurves([ // this.setCurves([
{ // {
startPoint: { x: this.valueToX(0), y: this.valueToY(0) }, // startPoint: { x: this.valueToX(0), y: this.valueToY(0) },
startPointHandle: { x: this.valueToX(timelineLength / 2), y: this.valueToY(0.5) }, // startPointHandle: { x: this.valueToX(timelineLength / 2), y: this.valueToY(0.5) },
endPointHandle: { x: this.valueToX(timelineLength / 2), y: this.valueToY(-0.5) }, // endPointHandle: { x: this.valueToX(timelineLength / 2), y: this.valueToY(-0.5) },
endPoint: { x: this.valueToX(timelineLength), y: this.valueToY(0) } // endPoint: { x: this.valueToX(timelineLength), y: this.valueToY(0) }
} // }
]); // ]);
this.curveSets[this.selectedMotorID] = this.curves; // this.curveSets[this.selectedMotorID] = this.curves;
// let othercurves = [ // let othercurves = [
// { // {
@ -73,13 +73,25 @@ export class CurveEditor {
// this.curveSets[7] = othercurves; // this.curveSets[7] = othercurves;
this.setSelectedMotor(4); //this.setSelectedMotor(4);
this.initEvents(); this.initEvents();
this.draw(); this.draw();
console.log(this.curveSets); console.log(this.curveSets);
} }
addChannel(motorID){
this.setCurves([
{
startPoint: { x: this.valueToX(0), y: this.valueToY(0) },
startPointHandle: { x: this.valueToX(this.timelineLength / 2), y: this.valueToY(0.5) },
endPointHandle: { x: this.valueToX(this.timelineLength / 2), y: this.valueToY(-0.5) },
endPoint: { x: this.valueToX(this.timelineLength), y: this.valueToY(0) }
}
]);
this.curveSets[motorID] = this.curves;
}
setLength(endTime) { setLength(endTime) {
this.timelineLength = endTime / this.pixelsPerSecond; this.timelineLength = endTime / this.pixelsPerSecond;
console.log("new endtime: " + endTime); console.log("new endtime: " + endTime);
@ -685,7 +697,7 @@ export class CurveEditor {
this.dragControlPoint(curve, 'endPointHandle', curve.endPointHandle.x, curve.endPointHandle.y, index); this.dragControlPoint(curve, 'endPointHandle', curve.endPointHandle.x, curve.endPointHandle.y, index);
const nextCurve = this.curves[index + 1]; const nextCurve = this.curves[index + 1];
console.log(curve.endPoint.y); //console.log(curve.endPoint.y);
if (nextCurve) { if (nextCurve) {
nextCurve.startPoint.x = curve.endPoint.x; nextCurve.startPoint.x = curve.endPoint.x;
nextCurve.startPoint.y = curve.endPoint.y; nextCurve.startPoint.y = curve.endPoint.y;

View File

@ -1,35 +1,42 @@
export class ServoMotor { export class ServoMotor {
constructor(payload) { constructor(arg1, arg2, arg3) {
this.CHANNEL = payload[0]; if (Array.isArray(arg1)) {
this.ID = payload[1]; // Full payload constructor
const payload = arg1;
this.MODEL = getModelType(payload[3], payload[2]); // minor, major this.CHANNEL = payload[0];
this.ID = payload[1];
this.MODEL = getModelType(payload[3], payload[2]);
this.MIN_ANGLE_LIMIT = (payload[4] << 8) | payload[5]; this.MIN_ANGLE_LIMIT = (payload[4] << 8) | payload[5];
this.MAX_ANGLE_LIMIT = (payload[6] << 8) | payload[7]; this.MAX_ANGLE_LIMIT = (payload[6] << 8) | payload[7];
this.POSITION = (payload[8] << 8) | payload[9]; this.POSITION = (payload[8] << 8) | payload[9];
this.CW_DEAD_ZONE = payload[10];
this.CCW_DEAD_ZONE = payload[11];
this.OFFSET = (payload[12] << 8) | payload[13];
this.MODE = payload[14];
this.TORQUE_ENABLE = payload[15];
this.ACCELERATION = payload[16];
this.GOAL_POSITION = (payload[17] << 8) | payload[18];
this.GOAL_TIME = (payload[19] << 8) | payload[20];
this.GOAL_SPEED = (payload[21] << 8) | payload[22];
this.LOCK = payload[23];
this.CW_DEAD_ZONE = payload[10]; const rawSpeed = (payload[24] << 8) | payload[25];
this.CCW_DEAD_ZONE = payload[11]; this.CURRENT_SPEED = rawSpeed > 0x7FFF ? rawSpeed - 0x10000 : rawSpeed;
this.OFFSET = (payload[12] << 8) | payload[13];
this.MODE = payload[14];
this.TORQUE_ENABLE = payload[15];
this.ACCELERATION = payload[16];
this.GOAL_POSITION = (payload[17] << 8) | payload[18]; this.CURRENT_LOAD = (payload[26] << 8) | payload[27];
this.GOAL_TIME = (payload[19] << 8) | payload[20]; this.TEMPERATURE = payload[28];
this.GOAL_SPEED = (payload[21] << 8) | payload[22]; this.MOVING = payload[29];
this.LOCK = payload[23]; this.CURRENT_CURRENT = (payload[30] << 8) | payload[31];
this.VOLTAGE = payload[32];
const rawSpeed = (payload[24] << 8) | payload[25]; } else {
this.CURRENT_SPEED = rawSpeed > 0x7FFF ? rawSpeed - 0x10000 : rawSpeed; // Simplified constructor
this.CHANNEL = arg1;
this.CURRENT_LOAD = (payload[26] << 8) | payload[27]; this.ID = arg2;
this.TEMPERATURE = payload[28]; this.MODEL = arg3 || 'Unknown Model';
this.MOVING = payload[29];
this.CURRENT_CURRENT = (payload[30] << 8) | payload[31];
this.VOLTAGE = payload[32];
} }
}
} }

View File

@ -38,7 +38,7 @@
<!-- Tab content --> <!-- Tab content -->
<div class="tab-content" id="myTabContent"> <div class="tab-content" id="myTabContent">
<div class="tab-pane fade show active" id="motors" role="tabpanel" aria-labelledby="motors-tab"> <div class="tab-pane fade" id="motors" role="tabpanel" aria-labelledby="motors-tab">
@ -154,7 +154,7 @@
<div class="tab-pane fade" id="animation" role="tabpanel" aria-labelledby="animation-tab"> <div class="tab-pane fade show active" id="animation" role="tabpanel" aria-labelledby="animation-tab">
<canvas id="curveCanvas" width="900" height="600"></canvas> <canvas id="curveCanvas" width="900" height="600"></canvas>
<div style="margin-top: 10px; text-align: center;"> <div style="margin-top: 10px; text-align: center;">
<input type="range" id="timeSlider" min="0" step="1" style="width: 80%;"> <input type="range" id="timeSlider" min="0" step="1" style="width: 80%;">

46
robot.js Normal file
View File

@ -0,0 +1,46 @@
import { ServoMotor } from './feetechDefinitions.js';
export class Robot {
constructor(name, firmwareVersionId) {
this.name = name;
this.firmwareVersionId = firmwareVersionId;
// Map of position ID → ServoMotor
this.positionMap = new Map();
}
// Assign a motor to a position
assignMotor(positionId, motor) {
if (!(motor instanceof ServoMotor)) {
throw new Error('Assigned motor must be a ServoMotor instance');
}
this.positionMap.set(positionId, motor);
}
// Get motor assigned to a position
getMotor(positionId) {
return this.positionMap.get(positionId);
}
// Remove motor from a position
removeMotor(positionId) {
this.positionMap.delete(positionId);
}
// Get all position assignments
getAllAssignments() {
const assignments = [];
for (const [position, motor] of this.positionMap.entries()) {
assignments.push({ position, motor });
}
return assignments;
}
// Optional: Find position by motor ID
findPositionByMotorId(id) {
for (const [position, motor] of this.positionMap.entries()) {
if (motor.ID === id) return position;
}
return null;
}
}

View File

@ -1,10 +1,14 @@
import { SerialManager } from './serial.js'; import { SerialManager } from './serial.js';
import { ServoMotor, getModelType, writeData } from './feetechDefinitions.js'; import { ServoMotor, getModelType, writeData } from './feetechDefinitions.js';
import { CurveEditor } from './curveEditor.js'; import { CurveEditor } from './curveEditor.js';
import { Robot } from './robot.js';
window.onload = () => { window.onload = () => {
const serial = new SerialManager(); const serial = new SerialManager();
const servoMotors = [[], []]; // index 0 = channel 0, index 1 = channel 1 const servoMotors = [[], []]; // index 0 = channel 0, index 1 = channel 1
const statusText = document.getElementById('statusText'); const statusText = document.getElementById('statusText');
@ -25,6 +29,9 @@ window.onload = () => {
let selectedDial = null; let selectedDial = null;
let draggingKeyframe = null; // { dialIndex, originalFrame } let draggingKeyframe = null; // { dialIndex, originalFrame }
let isDragging = false; let isDragging = false;
const dialColors = ['red', 'green', 'blue', 'orange', 'purple']; const dialColors = ['red', 'green', 'blue', 'orange', 'purple'];
const dials = []; const dials = [];
@ -47,6 +54,36 @@ window.onload = () => {
let selectedFile = null; let selectedFile = null;
let connectedRobot = GenerateTestRobot();
console.log(connectedRobot);
for (const [position, motor] of connectedRobot.positionMap.entries()) {
console.log(`Assigning ${position} motor with ID ${motor.ID}`);
curveEditor.addChannel(motor.ID);
}
curveEditor.setSelectedMotor(10);
// TODO: Info should all be loaded on connect from handshake packet
function GenerateTestRobot() {
const robot = new Robot('Atlas', 'FW-2.0.1');
// Create motors manually
let testMotors = [];
let positions = ["eyelids", "headtilt", "neckrotate", "rightshoulder", "rightforearm"];
for (let i = 0; i < 5; i++) {
let motor = new ServoMotor(0, 10 + i, "SCS009")
robot.assignMotor(positions[i], motor);
}
// Retrieve motor by position
//console.log(robot.getMotor('eyelids'));
// List all assignments
console.log(robot.getAllAssignments());
return robot;
}
function clearFileList() { function clearFileList() {
fileListElement.innerHTML = ''; fileListElement.innerHTML = '';
selectedFile = null; selectedFile = null;
@ -561,14 +598,14 @@ window.onload = () => {
view.setUint16(offset, curveCount, true); offset += 2; view.setUint16(offset, curveCount, true); offset += 2;
// 🔹 Curve segments // 🔹 Curve segments
curveSegments.forEach(seg => { curveSegments.forEach(seg => {
view.setUint8(offset++, seg.motorID); view.setUint8(offset++, seg.motorID);
view.setUint16(offset, seg.startTime, true); offset += 2; view.setUint16(offset, seg.startTime, true); offset += 2;
view.setUint16(offset, seg.endTime, true); offset += 2; view.setUint16(offset, seg.endTime, true); offset += 2;
view.setInt16(offset, curveEditor.yToExportRange(seg.startPointY), true); offset += 2; view.setInt16(offset, curveEditor.yToExportRange(seg.startPointY), true); offset += 2;
console.log(curveEditor.yToExportRange(seg.startPointY)); console.log(curveEditor.yToExportRange(seg.startPointY));
view.setUint16(offset, seg.startHandleX, true); offset += 2; view.setUint16(offset, seg.startHandleX, true); offset += 2;
view.setInt16(offset, curveEditor.yToExportRange(seg.startHandleY), true); offset += 2; view.setInt16(offset, curveEditor.yToExportRange(seg.startHandleY), true); offset += 2;
view.setUint16(offset, seg.endHandleX, true); offset += 2; view.setUint16(offset, seg.endHandleX, true); offset += 2;