implemented minimal config (motors) updating

node_mode
Jake 2025-10-31 01:43:11 +08:00
parent 0fa8e04493
commit 9036014e21
4 changed files with 232 additions and 115 deletions

View File

@ -1,45 +1,45 @@
export class ServoMotor { export class ServoMotor {
constructor(arg1, arg2, arg3, arg4) { constructor(arg1, arg2, arg3, arg4) {
if (Array.isArray(arg1)) { if (Array.isArray(arg1)) {
// Full payload constructor // Full payload constructor
const payload = arg1; const payload = arg1;
this.CHANNEL = payload[0]; this.CHANNEL = payload[0];
this.ID = payload[1]; this.ID = payload[1];
this.MODEL = getModelType(payload[3], payload[2]); 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.CW_DEAD_ZONE = payload[10];
this.CCW_DEAD_ZONE = payload[11]; this.CCW_DEAD_ZONE = payload[11];
this.OFFSET = (payload[12] << 8) | payload[13]; this.OFFSET = (payload[12] << 8) | payload[13];
this.MODE = payload[14]; this.MODE = payload[14];
this.TORQUE_ENABLE = payload[15]; this.TORQUE_ENABLE = payload[15];
this.ACCELERATION = payload[16]; this.ACCELERATION = payload[16];
this.GOAL_POSITION = (payload[17] << 8) | payload[18]; this.GOAL_POSITION = (payload[17] << 8) | payload[18];
this.GOAL_TIME = (payload[19] << 8) | payload[20]; this.GOAL_TIME = (payload[19] << 8) | payload[20];
this.GOAL_SPEED = (payload[21] << 8) | payload[22]; this.GOAL_SPEED = (payload[21] << 8) | payload[22];
this.LOCK = payload[23]; this.LOCK = payload[23];
const rawSpeed = (payload[24] << 8) | payload[25]; const rawSpeed = (payload[24] << 8) | payload[25];
this.CURRENT_SPEED = rawSpeed > 0x7FFF ? rawSpeed - 0x10000 : rawSpeed; this.CURRENT_SPEED = rawSpeed > 0x7FFF ? rawSpeed - 0x10000 : rawSpeed;
this.CURRENT_LOAD = (payload[26] << 8) | payload[27]; this.CURRENT_LOAD = (payload[26] << 8) | payload[27];
this.TEMPERATURE = payload[28]; this.TEMPERATURE = payload[28];
this.MOVING = payload[29]; this.MOVING = payload[29];
this.CURRENT_CURRENT = (payload[30] << 8) | payload[31]; this.CURRENT_CURRENT = (payload[30] << 8) | payload[31];
this.VOLTAGE = payload[32]; this.VOLTAGE = payload[32];
} else { } else {
// Simplified constructor // Simplified constructor
this.CHANNEL = arg1; this.CHANNEL = arg1;
this.ID = arg2; this.ID = arg2;
this.MODEL = arg3 || 'Unknown Model'; this.MODEL = arg3 || 'Unknown Model';
this.NAME = arg4 || "UNKNOWN"; this.NAME = arg4 || "UNKNOWN";
}
} }
}
} }
@ -112,71 +112,80 @@ const DataMap = Object.freeze({
export function getModelType(major, minor) { export function getModelType(major, minor) {
const modelList = new Map([
[combine(5, 0), "SCSXX"],
[combine(5, 4), "SCS009"],
[combine(5, 8), "SCS2332"],
[combine(5, 12), "SCS45"],
[combine(5, 15), "SCS15"],
[combine(5, 16), "SCS315"],
[combine(5, 25), "SCS115"],
[combine(5, 35), "SCS215"],
[combine(5, 40), "SCS40"],
[combine(5, 60), "SCS6560"],
[combine(5, 240), "SCDZZ"],
[combine(6, 0), "SMXX-360M"],
[combine(6, 3), "SM30-360M"],
[combine(6, 8), "SM60-360M"],
[combine(6, 12), "SM80-360M"],
[combine(6, 16), "SM100-360M"],
[combine(6, 20), "SM150-360M"],
[combine(6, 24), "SM85-360M"],
[combine(6, 26), "SM60-360M"],
[combine(8, 0), "SM30BL"],
[combine(8, 1), "SM30BL"],
[combine(8, 2), "SM30BL"],
[combine(8, 3), "SM30BL"],
[combine(8, 4), "SM30BL"],
[combine(8, 5), "SM30BL"],
[combine(8, 6), "SM30BL"],
[combine(8, 7), "SM30BL"],
[combine(8, 8), "SM30BL"],
[combine(8, 9), "SM30BL"],
[combine(8, 10), "SM30BL"],
[combine(8, 11), "SM30BL"],
[combine(8, 12), "SM30BL"],
[combine(8, 13), "SM30BL"],
[combine(8, 14), "SM30BL"],
[combine(8, 15), "SM30BL"],
[combine(8, 16), "SM30BL"],
[combine(8, 17), "SM30BL"],
[combine(8, 18), "SM30BL"],
[combine(8, 19), "SM30BL"],
[combine(8, 25), "SM29BL(LJ)"],
[combine(8, 29), "SM29BL(FT)"],
[combine(8, 30), "SM30BL(FT)"],
[combine(8, 20), "SM30BL(LJ)"],
[combine(8, 40), "SM40BLHV"],
[combine(8, 42), "SM45BLHV"],
[combine(8, 44), "SM85BLHV"],
[combine(8, 120), "SM120BLHV"],
[combine(8, 220), "SM200BLHV"],
[combine(9, 0), "STSXX"],
[combine(9, 2), "STS3032"],
[combine(9, 3), "STS3215"],
[combine(9, 4), "STS3040"],
[combine(9, 5), "STS3020"],
[combine(9, 6), "STS3046"],
[combine(9, 20), "SCSXX-2"],
[combine(9, 15), "SCS15-2"],
[combine(9, 35), "SCS225"],
[combine(9, 40), "SCS40-2"]
]);
const id = combine(major, minor); const id = combine(major, minor);
return modelList.get(id) || "Unknown Model"; return modelList.get(id) || "Unknown Model";
} }
const modelList = new Map([
[combine(5, 0), "SCSXX"],
[combine(5, 4), "SCS009"],
[combine(5, 8), "SCS2332"],
[combine(5, 12), "SCS45"],
[combine(5, 15), "SCS15"],
[combine(5, 16), "SCS315"],
[combine(5, 25), "SCS115"],
[combine(5, 35), "SCS215"],
[combine(5, 40), "SCS40"],
[combine(5, 60), "SCS6560"],
[combine(5, 240), "SCDZZ"],
[combine(6, 0), "SMXX-360M"],
[combine(6, 3), "SM30-360M"],
[combine(6, 8), "SM60-360M"],
[combine(6, 12), "SM80-360M"],
[combine(6, 16), "SM100-360M"],
[combine(6, 20), "SM150-360M"],
[combine(6, 24), "SM85-360M"],
[combine(6, 26), "SM60-360M"],
[combine(8, 0), "SM30BL"],
[combine(8, 1), "SM30BL"],
[combine(8, 2), "SM30BL"],
[combine(8, 3), "SM30BL"],
[combine(8, 4), "SM30BL"],
[combine(8, 5), "SM30BL"],
[combine(8, 6), "SM30BL"],
[combine(8, 7), "SM30BL"],
[combine(8, 8), "SM30BL"],
[combine(8, 9), "SM30BL"],
[combine(8, 10), "SM30BL"],
[combine(8, 11), "SM30BL"],
[combine(8, 12), "SM30BL"],
[combine(8, 13), "SM30BL"],
[combine(8, 14), "SM30BL"],
[combine(8, 15), "SM30BL"],
[combine(8, 16), "SM30BL"],
[combine(8, 17), "SM30BL"],
[combine(8, 18), "SM30BL"],
[combine(8, 19), "SM30BL"],
[combine(8, 25), "SM29BL(LJ)"],
[combine(8, 29), "SM29BL(FT)"],
[combine(8, 30), "SM30BL(FT)"],
[combine(8, 20), "SM30BL(LJ)"],
[combine(8, 40), "SM40BLHV"],
[combine(8, 42), "SM45BLHV"],
[combine(8, 44), "SM85BLHV"],
[combine(8, 120), "SM120BLHV"],
[combine(8, 220), "SM200BLHV"],
[combine(9, 0), "STSXX"],
[combine(9, 2), "STS3032"],
[combine(9, 3), "STS3215"],
[combine(9, 4), "STS3040"],
[combine(9, 5), "STS3020"],
[combine(9, 6), "STS3046"],
[combine(9, 20), "SCSXX-2"],
[combine(9, 15), "SCS15-2"],
[combine(9, 35), "SCS225"],
[combine(9, 40), "SCS40-2"]
]);
export const reverseModelMap = new Map();
for (const [combined, model] of modelList.entries()) {
const minor = combined >> 8;
const major = combined & 0xFF;
reverseModelMap.set(model, { major, minor });
}
function combine(major, minor) { function combine(major, minor) {
return (minor << 8) | major; return (minor << 8) | major;
} }

View File

@ -50,16 +50,19 @@
<div class="channel-box mb-5"> <div class="channel-box mb-5">
<label class="form-label">Channel 0</label> <label class="form-label">Channel 0</label>
<div class="row mb-2"> <div class="row mb-2">
<div class="col-9"> <!-- <div class="col-9">
<select class="form-select"> <select class="form-select">
<option selected>SCS</option> <option selected>SCS</option>
<option>STS</option> <option>STS</option>
<option>SM</option> <option>SM</option>
</select> </select>
</div> </div> -->
<div class="col-3"> <div class="col-3">
<button id="btn_scan_channel_0" class="btn btn-primary w-100">Scan</button> <button id="btn_scan_channel_0" class="btn btn-primary w-100">Scan</button>
</div> </div>
<div class="col-3">
<button id="btn_apply_config_channel_0" class="btn btn-primary w-100">Apply To Config</button>
</div>
</div> </div>
<table id="channel0-motor-table" class="table table-bordered"> <table id="channel0-motor-table" class="table table-bordered">
<thead> <thead>
@ -85,6 +88,7 @@
<th data-key="MOVING">MOVING</th> <th data-key="MOVING">MOVING</th>
<th data-key="CURRENT_CURRENT">CURRENT</th> <th data-key="CURRENT_CURRENT">CURRENT</th>
<th data-key="VOLTAGE">VOLTAGE</th> <th data-key="VOLTAGE">VOLTAGE</th>
<th data-key="NAME">NAME</th>
</tr> </tr>
</thead> </thead>
@ -97,16 +101,19 @@
<div class="channel-box mb-5"> <div class="channel-box mb-5">
<label class="form-label">Channel 1</label> <label class="form-label">Channel 1</label>
<div class="row mb-2"> <div class="row mb-2">
<div class="col-9"> <!-- <div class="col-9">
<select class="form-select"> <select class="form-select">
<option selected>SCS</option> <option selected>SCS</option>
<option>STS</option> <option>STS</option>
<option>SM</option> <option>SM</option>
</select> </select>
</div> </div> -->
<div class="col-3"> <div class="col-3">
<button id="btn_scan_channel_1" class="btn btn-primary w-100">Scan</button> <button id="btn_scan_channel_1" class="btn btn-primary w-100">Scan</button>
</div> </div>
<div class="col-3">
<button id="btn_apply_config_channel_1" class="btn btn-primary w-100">Apply</button>
</div>
</div> </div>
<table id="channel1-motor-table" class="table table-bordered"> <table id="channel1-motor-table" class="table table-bordered">
<thead> <thead>
@ -132,6 +139,7 @@
<th data-key="MOVING">MOVING</th> <th data-key="MOVING">MOVING</th>
<th data-key="CURRENT_CURRENT">CURRENT</th> <th data-key="CURRENT_CURRENT">CURRENT</th>
<th data-key="VOLTAGE">VOLTAGE</th> <th data-key="VOLTAGE">VOLTAGE</th>
<th data-key="NAME">NAME</th>
</tr> </tr>
</thead> </thead>

120
script.js
View File

@ -1,5 +1,5 @@
import { SerialManager } from './serial.js'; import { SerialManager } from './serial.js';
import { ServoMotor, getModelType, writeData } from './feetechDefinitions.js'; import { ServoMotor, getModelType, reverseModelMap, writeData } from './feetechDefinitions.js';
import { CurveEditor } from './curveEditor.js'; import { CurveEditor } from './curveEditor.js';
import { Robot } from './robot.js'; import { Robot } from './robot.js';
import { NodeEditor } from './nodeeditor/NodeEditor.js'; import { NodeEditor } from './nodeeditor/NodeEditor.js';
@ -249,7 +249,7 @@ window.onload = () => {
}; };
function clearDials(){ function clearDials() {
const dialArea = document.getElementById('dialArea'); const dialArea = document.getElementById('dialArea');
dialArea.innerHTML = ''; // Remove all child elements dialArea.innerHTML = ''; // Remove all child elements
dials = []; dials = [];
@ -330,7 +330,7 @@ window.onload = () => {
for (let ch = 0; ch < dials.length; ch++) { for (let ch = 0; ch < dials.length; ch++) {
const value = dials[ch].value; const value = dials[ch].value;
motorPayloads.push({ motorId: ch, position: value }); motorPayloads.push({ motorId: dials[ch].motorID, position: value });
} }
const buffer = new ArrayBuffer(motorPayloads.length * 3); const buffer = new ArrayBuffer(motorPayloads.length * 3);
@ -343,6 +343,8 @@ window.onload = () => {
}); });
const payload = new Uint8Array(buffer); const payload = new Uint8Array(buffer);
console.log("SENDING POSITIONTS");
console.log(payload);
serial.sendSetPositions(payload); serial.sendSetPositions(payload);
} }
} }
@ -732,6 +734,92 @@ window.onload = () => {
document.getElementById('btn_apply_config_channel_0').onclick = async () => {
const table = document.querySelector('#channel0-motor-table tbody');
const rows = table.querySelectorAll('tr');
const motors = [];
rows.forEach(row => {
const cells = row.querySelectorAll('td');
const motor = {
MODEL: reverseModelMap.get(cells[0].textContent.trim()),
ID: parseInt(cells[1].textContent.trim()),
// MIN_ANGLE_LIMIT: parseInt(cells[2].textContent.trim()),
// MAX_ANGLE_LIMIT: parseInt(cells[3].textContent.trim()),
// POSITION: parseInt(cells[4].textContent.trim()),
// CW_DEAD_ZONE: parseInt(cells[5].textContent.trim()),
// CCW_DEAD_ZONE: parseInt(cells[6].textContent.trim()),
// OFFSET: parseInt(cells[7].textContent.trim()),
// MODE: parseInt(cells[8].textContent.trim()),
// TORQUE_ENABLE: parseInt(cells[9].textContent.trim()),
// ACCELERATION: parseInt(cells[10].textContent.trim()),
// GOAL_POSITION: parseInt(cells[11].textContent.trim()),
// GOAL_TIME: parseInt(cells[12].textContent.trim()),
// GOAL_SPEED: parseInt(cells[13].textContent.trim()),
// LOCK: parseInt(cells[14].textContent.trim()),
// CURRENT_SPEED: parseInt(cells[15].textContent.trim()),
// CURRENT_LOAD: parseInt(cells[16].textContent.trim()),
// TEMPERATURE: parseInt(cells[17].textContent.trim()),
// MOVING: parseInt(cells[18].textContent.trim()),
// CURRENT_CURRENT: parseInt(cells[19].textContent.trim()),
// VOLTAGE: parseInt(cells[20].textContent.trim()),
NAME: cells[21].textContent.trim()
};
motors.push(motor);
});
console.log("Compiled motor list:", motors);
await serial.sendConfigUpdate(encodeMotorConfig(motors));
// You can now use this list for saving, sending, or applying config
};
function encodeMotorConfig(motors) {
const robotName = "Mr Roboto";
const firmwareVersion = 1;
const bufferSize = 1024; // adjust as needed
const buffer = new ArrayBuffer(bufferSize);
const view = new DataView(buffer);
let offset = 0;
const encoder = new TextEncoder();
const nameBytes = encoder.encode(robotName);
const nameLength = Math.min(nameBytes.length, 255); // max 255 bytes
// 🔹 Encode robotName (length + bytes)
view.setUint8(offset++, nameLength);
for (let i = 0; i < nameLength; i++) {
view.setUint8(offset++, nameBytes[i]);
}
// 🔹 Encode firmwareVersion (2 bytes)
view.setUint16(offset, firmwareVersion, true); offset += 2;
// 🔹 Encode motor count (1 byte)
view.setUint8(offset++, motors.length);
// 🔹 Encode motor entries
motors.forEach(motor => {
const { major, minor } = motor.MODEL;
const modelValue = (minor << 8) | major;
view.setUint16(offset, modelValue, true); offset += 2; // MODEL
view.setUint16(offset, motor.ID, true); offset += 2; // ID
const motorNameBytes = encoder.encode(motor.NAME);
const motorNameLength = Math.min(motorNameBytes.length, 255);
view.setUint8(offset++, motorNameLength);
for (let i = 0; i < motorNameLength; i++) {
view.setUint8(offset++, motorNameBytes[i]);
}
});
return new Uint8Array(buffer.slice(0, offset));
}
@ -771,7 +859,8 @@ window.onload = () => {
motor.TEMPERATURE, motor.TEMPERATURE,
motor.MOVING, motor.MOVING,
motor.CURRENT_CURRENT, motor.CURRENT_CURRENT,
motor.VOLTAGE motor.VOLTAGE,
motor.NAME
]; ];
const modelType = motor.MODEL.startsWith('SCS') ? 'SCS' : const modelType = motor.MODEL.startsWith('SCS') ? 'SCS' :
@ -792,18 +881,23 @@ window.onload = () => {
td.setAttribute('contenteditable', 'true'); td.setAttribute('contenteditable', 'true');
td.setAttribute('data-type', 'number'); td.setAttribute('data-type', 'number');
if (index === 1) { if (index === 21) {
td.setAttribute('data-min', '0'); td.setAttribute('data-type', 'text');
td.setAttribute('data-max', '255');
} else if (index === 9) { // TORQUE ENABLE
td.setAttribute('data-min', '0');
td.setAttribute('data-max', '1');
} else if (index === 14) { // EEPROM LOCK
td.setAttribute('data-min', '0');
td.setAttribute('data-max', '1');
} else { } else {
td.setAttribute('data-type', 'number');
td.setAttribute('data-min', rangeMin.toString()); td.setAttribute('data-min', rangeMin.toString());
td.setAttribute('data-max', rangeMax.toString()); td.setAttribute('data-max', rangeMax.toString());
if (index === 1) {
td.setAttribute('data-min', '0');
td.setAttribute('data-max', '255');
} else if (index === 9) {
td.setAttribute('data-min', '0');
td.setAttribute('data-max', '1');
} else if (index === 14) {
td.setAttribute('data-min', '0');
td.setAttribute('data-max', '1');
}
} }
td.addEventListener('input', function () { td.addEventListener('input', function () {

View File

@ -14,6 +14,7 @@ const CMD_PLAY_FILE = 0x08;
const CMD_SCAN_CHANNEL = 0x09; const CMD_SCAN_CHANNEL = 0x09;
const CMD_WRITE_DATA = 0x10; const CMD_WRITE_DATA = 0x10;
const CMD_READ_DATA = 0x11; const CMD_READ_DATA = 0x11;
const CMD_WRITE_CONFIG_UPDATE = 0x12;
const CMD_START_POSITION_STREAM = 0x14; const CMD_START_POSITION_STREAM = 0x14;
const POSITION_STREAM = 0x15; const POSITION_STREAM = 0x15;
@ -96,6 +97,11 @@ export class SerialManager {
await this.send(CMD_WRITE_DATA, payload); await this.send(CMD_WRITE_DATA, payload);
} }
async sendConfigUpdate(payload) {
console.log(payload);
await this.send(CMD_WRITE_CONFIG_UPDATE, payload);
}
async requestPositionStreaming(stream) { //stream true/false async requestPositionStreaming(stream) { //stream true/false
await this.send(CMD_START_POSITION_STREAM, new Uint8Array([stream])); await this.send(CMD_START_POSITION_STREAM, new Uint8Array([stream]));
} }