2017-12-18 22:04:17 +01:00
|
|
|
namespace pxsim {
|
|
|
|
|
2017-12-28 02:05:15 +01:00
|
|
|
export class MotorNode extends BaseNode {
|
2017-12-18 22:04:17 +01:00
|
|
|
isOutput = true;
|
2017-12-28 02:05:15 +01:00
|
|
|
private rotationsPerMilliSecond: number;
|
2017-12-18 22:04:17 +01:00
|
|
|
|
2017-12-28 02:05:15 +01:00
|
|
|
// current state
|
|
|
|
private angle: number = 0;
|
|
|
|
private tacho: number = 0;
|
|
|
|
private speed: number = 0;
|
2017-12-28 18:07:57 +01:00
|
|
|
private polarity: number = 1; // -1, 1 or -1
|
2017-12-18 22:04:17 +01:00
|
|
|
|
2017-12-28 02:05:15 +01:00
|
|
|
private started: boolean;
|
|
|
|
private speedCmd: DAL;
|
|
|
|
private speedCmdValues: number[];
|
|
|
|
private speedCmdTacho: number;
|
|
|
|
private speedCmdTime: number;
|
2018-01-03 01:20:00 +01:00
|
|
|
private _synchedMotor: MotorNode; // non-null if master motor
|
2017-12-18 22:04:17 +01:00
|
|
|
|
2017-12-28 02:05:15 +01:00
|
|
|
constructor(port: number, large: boolean) {
|
2017-12-18 22:04:17 +01:00
|
|
|
super(port);
|
2017-12-28 02:05:15 +01:00
|
|
|
this.setLarge(large);
|
2017-12-18 22:04:17 +01:00
|
|
|
}
|
|
|
|
|
2017-12-28 02:05:15 +01:00
|
|
|
getSpeed() {
|
2018-01-03 01:20:00 +01:00
|
|
|
return this.speed * (this.polarity == 0 ? -1 : 1);
|
2017-12-18 22:04:17 +01:00
|
|
|
}
|
|
|
|
|
2017-12-28 02:05:15 +01:00
|
|
|
getAngle() {
|
|
|
|
return this.angle;
|
2017-12-18 22:04:17 +01:00
|
|
|
}
|
|
|
|
|
2018-01-03 01:20:00 +01:00
|
|
|
// returns the slave motor if any
|
|
|
|
getSynchedMotor() {
|
|
|
|
return this._synchedMotor;
|
|
|
|
}
|
|
|
|
|
2017-12-28 02:05:15 +01:00
|
|
|
setSpeedCmd(cmd: DAL, values: number[]) {
|
|
|
|
this.speedCmd = cmd;
|
|
|
|
this.speedCmdValues = values;
|
|
|
|
this.speedCmdTacho = this.angle;
|
|
|
|
this.speedCmdTime = pxsim.U.now();
|
2018-01-03 01:20:00 +01:00
|
|
|
delete this._synchedMotor;
|
|
|
|
}
|
|
|
|
|
|
|
|
setSyncCmd(motor: MotorNode, cmd: DAL, values: number[]) {
|
|
|
|
this.setSpeedCmd(cmd, values);
|
|
|
|
this._synchedMotor = motor;
|
2017-12-18 22:04:17 +01:00
|
|
|
}
|
|
|
|
|
2017-12-28 02:05:15 +01:00
|
|
|
clearSpeedCmd() {
|
|
|
|
delete this.speedCmd;
|
2018-01-03 01:20:00 +01:00
|
|
|
delete this._synchedMotor;
|
2017-12-28 02:05:15 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
setLarge(large: boolean) {
|
|
|
|
this.id = large ? NodeType.LargeMotor : NodeType.MediumMotor;
|
2017-12-28 18:07:57 +01:00
|
|
|
// large 170 rpm (https://education.lego.com/en-us/products/ev3-large-servo-motor/45502)
|
2017-12-28 02:05:15 +01:00
|
|
|
this.rotationsPerMilliSecond = (large ? 170 : 250) / 60000;
|
2017-12-18 22:04:17 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
setPolarity(polarity: number) {
|
|
|
|
// Either 1 or 255 (reverse)
|
2017-12-28 18:07:57 +01:00
|
|
|
/*
|
|
|
|
-1 : Motor will run backward
|
|
|
|
0 : Motor will run opposite direction
|
|
|
|
1 : Motor will run forward
|
|
|
|
*/
|
|
|
|
this.polarity = polarity;
|
2017-12-18 22:04:17 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
reset() {
|
2017-12-28 02:05:15 +01:00
|
|
|
// not sure what reset does...
|
2017-12-18 22:04:17 +01:00
|
|
|
}
|
|
|
|
|
2017-12-28 02:05:15 +01:00
|
|
|
clearCount() {
|
|
|
|
this.tacho = 0;
|
|
|
|
this.angle = 0;
|
2017-12-18 22:04:17 +01:00
|
|
|
}
|
|
|
|
|
2017-12-28 02:05:15 +01:00
|
|
|
stop() {
|
|
|
|
this.started = false;
|
2018-01-03 09:27:05 +01:00
|
|
|
this.clearSpeedCmd();
|
2017-12-18 22:04:17 +01:00
|
|
|
}
|
2017-12-19 23:20:35 +01:00
|
|
|
|
2017-12-28 02:05:15 +01:00
|
|
|
start() {
|
|
|
|
this.started = true;
|
2017-12-19 23:20:35 +01:00
|
|
|
}
|
|
|
|
|
2017-12-20 01:03:26 +01:00
|
|
|
updateState(elapsed: number) {
|
2018-01-04 23:03:50 +01:00
|
|
|
//console.log(`motor: ${elapsed}ms - ${this.speed}% - ${this.angle}> - ${this.tacho}|`)
|
2017-12-28 18:07:57 +01:00
|
|
|
const interval = Math.min(20, elapsed);
|
|
|
|
let t = 0;
|
2018-01-03 01:20:00 +01:00
|
|
|
while (t < elapsed) {
|
2017-12-28 18:07:57 +01:00
|
|
|
let dt = interval;
|
|
|
|
if (t + dt > elapsed) dt = elapsed - t;
|
|
|
|
this.updateStateStep(dt);
|
|
|
|
t += dt;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private updateStateStep(elapsed: number) {
|
2017-12-28 02:05:15 +01:00
|
|
|
// compute new speed
|
|
|
|
switch (this.speedCmd) {
|
|
|
|
case DAL.opOutputSpeed:
|
|
|
|
case DAL.opOutputPower:
|
|
|
|
// assume power == speed
|
|
|
|
// TODO: PID
|
|
|
|
this.speed = this.speedCmdValues[0];
|
|
|
|
break;
|
|
|
|
case DAL.opOutputTimeSpeed:
|
|
|
|
case DAL.opOutputTimePower:
|
|
|
|
case DAL.opOutputStepPower:
|
2018-01-03 01:20:00 +01:00
|
|
|
case DAL.opOutputStepSpeed: {
|
2017-12-28 02:05:15 +01:00
|
|
|
// ramp up, run, ramp down, <brake> using time
|
|
|
|
const speed = this.speedCmdValues[0];
|
|
|
|
const step1 = this.speedCmdValues[1];
|
|
|
|
const step2 = this.speedCmdValues[2];
|
|
|
|
const step3 = this.speedCmdValues[3];
|
|
|
|
const brake = this.speedCmdValues[4];
|
2018-01-03 01:20:00 +01:00
|
|
|
const dstep = (this.speedCmd == DAL.opOutputTimePower || this.speedCmd == DAL.opOutputTimeSpeed)
|
2017-12-28 02:05:15 +01:00
|
|
|
? pxsim.U.now() - this.speedCmdTime
|
|
|
|
: this.tacho - this.speedCmdTacho;
|
|
|
|
if (dstep < step1) // rampup
|
|
|
|
this.speed = speed * dstep / step1;
|
|
|
|
else if (dstep < step1 + step2) // run
|
|
|
|
this.speed = speed;
|
|
|
|
else if (dstep < step1 + step2 + step3)
|
|
|
|
this.speed = speed * (step1 + step2 + step3 - dstep) / (step1 + step2 + step3);
|
|
|
|
else {
|
|
|
|
if (brake) this.speed = 0;
|
|
|
|
this.clearSpeedCmd();
|
|
|
|
}
|
|
|
|
break;
|
2018-01-03 01:20:00 +01:00
|
|
|
}
|
|
|
|
case DAL.opOutputStepSync:
|
|
|
|
case DAL.opOutputTimeSync: {
|
|
|
|
if (!this._synchedMotor) // handled in other motor code
|
|
|
|
break;
|
|
|
|
|
|
|
|
const otherMotor = this._synchedMotor;
|
|
|
|
const speed = this.speedCmdValues[0];
|
|
|
|
const turnRatio = this.speedCmdValues[1];
|
|
|
|
const stepsOrTime = this.speedCmdValues[2];
|
|
|
|
const brake = this.speedCmdValues[3];
|
|
|
|
const dstep = this.speedCmd == DAL.opOutputTimeSync
|
|
|
|
? pxsim.U.now() - this.speedCmdTime
|
|
|
|
: this.tacho - this.speedCmdTacho;
|
2018-01-03 09:27:05 +01:00
|
|
|
// 0 is special case, run infinite
|
|
|
|
if (!stepsOrTime || dstep < stepsOrTime)
|
2018-01-03 01:20:00 +01:00
|
|
|
this.speed = speed;
|
|
|
|
else {
|
|
|
|
if (brake) this.speed = 0;
|
|
|
|
this.clearSpeedCmd();
|
|
|
|
}
|
|
|
|
// send synched motor state
|
2018-01-03 09:27:05 +01:00
|
|
|
otherMotor.speed = Math.floor(this.speed * turnRatio / 100);
|
|
|
|
if (!this._synchedMotor)
|
|
|
|
otherMotor.clearSpeedCmd();
|
2018-01-03 01:20:00 +01:00
|
|
|
break;
|
|
|
|
}
|
2017-12-20 01:03:26 +01:00
|
|
|
}
|
2018-01-03 01:20:00 +01:00
|
|
|
this.speed = Math.round(this.speed); // integer only
|
2017-12-18 22:04:17 +01:00
|
|
|
|
2017-12-28 02:05:15 +01:00
|
|
|
// compute delta angle
|
2017-12-28 18:07:57 +01:00
|
|
|
const rotations = this.getSpeed() / 100 * this.rotationsPerMilliSecond * elapsed;
|
|
|
|
const deltaAngle = Math.round(rotations * 360);
|
2017-12-28 02:05:15 +01:00
|
|
|
if (deltaAngle) {
|
|
|
|
this.angle += deltaAngle;
|
|
|
|
this.tacho += Math.abs(deltaAngle);
|
|
|
|
this.setChangedState();
|
|
|
|
}
|
2017-12-18 22:04:17 +01:00
|
|
|
|
2017-12-28 02:05:15 +01:00
|
|
|
// if the motor was stopped or there are no speed commands,
|
|
|
|
// let it coast to speed 0
|
2017-12-28 18:07:57 +01:00
|
|
|
if (this.speed && !(this.started || this.speedCmd)) {
|
2017-12-28 02:05:15 +01:00
|
|
|
// decay speed 5% per tick
|
2017-12-28 18:07:57 +01:00
|
|
|
this.speed = Math.round(Math.max(0, Math.abs(this.speed) - 10) * Math.sign(this.speed));
|
2017-12-28 02:05:15 +01:00
|
|
|
}
|
2017-12-19 23:20:35 +01:00
|
|
|
}
|
2017-12-18 22:04:17 +01:00
|
|
|
}
|
|
|
|
}
|