boost-movehub
Version:
Connects with your Lego Boost Movehub vie Bluetooth Low Energy (BLE).
338 lines (318 loc) • 12.2 kB
JavaScript
const Peripheral = require("./Peripheral");
const PortOutput = require("../messages/PortOutput");
const PortOutputCommandFeedback = require("../messages/PortOutputCommandFeedback");
const { int16ToArray, int32ToArray } = require("../helpers");
/**
* Defines methods for internal and external Methods of a Movehub.
*/
class Motor extends Peripheral {
/**
* @param {Number} ioType Peripheral Device Type ID (Either `Peripheral.DEV_MOTOR`, `Peripheral.DEV_MOTOR_EXTERNAL_TACHO` or `Peripheral.DEV_MOTOR_INTERNAL_TACHO`)
* @param {Number} portId Port ID this peripheral is connected to
* @param {object} [options.ioMembers] If this has severeal members, it is a virtual device
* @param {object} [options.logger]
*/
constructor(ioType, portId, options = undefined) {
super(ioType, portId, options);
this.displayName = "Motor";
this.status = Motor.STOPPED;
this.defaultMode = Motor.MODE_MOTOR;
}
/**
* Just starts/stops the motor.
* Sends message as defined in https://lego.github.io/lego-ble-wireless-protocol-docs/index.html#output-sub-command-startpower-power
* FIXME: 0 gives a piezo sound, so I guess that is not totally correct
*
* @param {number} dutyCycle [-100..-1] Percentage CCW, [1..100] Percentage CW, [0] to stop
*/
startPower(dutyCycle) {
if (this.isVirtualDevice) {
throw new Error("Virtual Device cannot start Power on only one motor.");
}
return PortOutput.buildWriteDirectModeData(
this.portId,
PortOutput.SC_FLAGS.EXECUTE_IMMEDIATE |
PortOutput.SC_FLAGS.COMMAND_FEEDBACK,
Motor.SUB_CMD_START_POWER,
[dutyCycle]
);
}
/**
* Just starts/stops the motor.
* Sends message as defined in https://lego.github.io/lego-ble-wireless-protocol-docs/index.html#output-sub-command-startspeed-speed-maxpower-useprofile-0x07
* FIXME: 0 gives a piezo sound, so I guess that is not totally correct
*
* @param {number} dutyCycle [-100..-1] Percentage CCW, [1..100] Percentage CW, [0] to stop
* @param {number} [maxSpeed] [0..100]%
* @param {number} useProfile Bitlist containing profiles to use (Select from `Motor.PROFILE_*`, defaults to `Motor.PROFILE_ACCELERATION | Motor.PROFILE_DEACCELERATION`)
*/
startSpeed(
dutyCycle,
maxSpeed = 100,
useProfile = Motor.PROFILE_ACCELERATION | Motor.PROFILE_DEACCELERATION
) {
if (this.isVirtualDevice) {
throw new Error("Virtual Device cannot start Power on only one motor.");
}
return PortOutput.build(
this.portId,
PortOutput.SC_FLAGS.EXECUTE_IMMEDIATE |
PortOutput.SC_FLAGS.COMMAND_FEEDBACK,
Motor.SUB_CMD_START_SPEED,
[dutyCycle, maxSpeed, useProfile]
);
}
/**
* Starts the motor for given amount of time.
* Sends message as defined in https://lego.github.io/lego-ble-wireless-protocol-docs/index.html#output-sub-command-startspeedfortime-time-speed-maxpower-endstate-useprofile-0x09
*
* @param {number} time [0..2^15] milliseconds
* @param {number} dutyCycle [-100..-1] Percentage CCW, [1..100] Percentage CW, [0] to stop
* @param {number} maxSpeed [0..100]%
* @param {number} endState One of `Motor.END_STATE_*`
* @param {number} useProfile Bitlist containing profiles to use (Select from `Motor.PROFILE_*`, defaults to `Motor.PROFILE_ACCELERATION | Motor.PROFILE_DEACCELERATION`)
*/
startSpeedForTime(
time,
dutyCycle,
maxSpeed = 100,
endState = Motor.END_STATE_FLOAT,
useProfile = Motor.PROFILE_ACCELERATION | Motor.PROFILE_DEACCELERATION
) {
if (this.isVirtualDevice) {
throw new Error("Virtual Device cannot start Power on only one motor.");
}
return PortOutput.build(
this.portId,
PortOutput.SC_FLAGS.EXECUTE_IMMEDIATE |
PortOutput.SC_FLAGS.COMMAND_FEEDBACK,
Motor.SUB_CMD_START_SPEED_FOR_TIME,
[...int16ToArray(time), dutyCycle, maxSpeed, endState, useProfile]
);
}
/**
* Turns the motor for given number of degrees
* Sends message as defined in https://lego.github.io/lego-ble-wireless-protocol-docs/index.html#output-sub-command-startspeedfordegrees-degrees-speed-maxpower-endstate-useprofile-0x0b
*
* @param {number} degrees [0..2^31] milliseconds
* @param {number} dutyCycle [-100..-1] Percentage CCW, [1..100] Percentage CW, [0] to stop
* @param {number} maxSpeed [0..100]%
* @param {number} endState One of `Motor.END_STATE_*`
* @param {number} useProfile Bitlist containing profiles to use (Select from `Motor.PROFILE_*`, defaults to `Motor.PROFILE_ACCELERATION | Motor.PROFILE_DEACCELERATION`)
*/
startSpeedForDegrees(
degrees,
dutyCycle,
maxSpeed = 100,
endState = Motor.END_STATE_BREAK,
useProfile = Motor.PROFILE_ACCELERATION | Motor.PROFILE_DEACCELERATION
) {
if (this.isVirtualDevice) {
throw new Error("Virtual Device cannot start Power on only one motor.");
}
if (degrees < 0) {
degrees = -degrees;
dutyCycle = -dutyCycle;
}
return PortOutput.build(
this.portId,
PortOutput.SC_FLAGS.EXECUTE_IMMEDIATE |
PortOutput.SC_FLAGS.COMMAND_FEEDBACK,
Motor.SUB_CMD_START_SPEED_FOR_DEGREES,
[...int32ToArray(degrees), dutyCycle, maxSpeed, endState, useProfile]
);
}
/**
* Creates stops message for motor.
*/
stop() {
return this.startSpeed(0, 0);
}
/**
* Just starts/stops the motor.
* Sends message as defined in https://lego.github.io/lego-ble-wireless-protocol-docs/index.html#output-sub-command-startspeed-speed-maxpower-useprofile-0x07
* FIXME: 0 gives a piezo sound, so I guess that is not totally correct
*
* @param {number} dutyCycleL [-100..-1] Percentage CCW, [1..100] Percentage CW, [0] to stop for primary motor
* @param {number} dutyCycleR [-100..-1] Percentage CCW, [1..100] Percentage CW, [0] to stop for secondary motor
* @param {number} maxSpeed [0..100]%
* @param {number} useProfile Bitlist containing profiles to use (Select from `Motor.PROFILE_*`, defaults to `Motor.PROFILE_ACCELERATION | Motor.PROFILE_DEACCELERATION`)
*/
combinedStartSpeed(
dutyCycleL,
dutyCycleR,
maxSpeed = 100,
useProfile = Motor.PROFILE_ACCELERATION | Motor.PROFILE_DEACCELERATION
) {
if (!this.isVirtualDevice) {
throw new Error("Non-virtual Device cannot start Power on two motors.");
}
return PortOutput.build(
this.portId,
PortOutput.SC_FLAGS.EXECUTE_IMMEDIATE |
PortOutput.SC_FLAGS.COMMAND_FEEDBACK,
Motor.SUB_CMD_START_SPEED_COMBINED,
[dutyCycleL, dutyCycleR, maxSpeed, useProfile]
);
}
/**
* Just starts/stops two motor in synchronized mode.
* Sends message as defined in https://lego.github.io/lego-ble-wireless-protocol-docs/index.html#output-sub-command-startpower-power
*
* TODO: This does not work yet
*
* @param {number} dutyCycleL [-100..-1] Percentage CCW, [1..100] Percentage CW, [0] to stop of primary motor
* @param {number} dutyCycleR [-100..-1] Percentage CCW, [1..100] Percentage CW, [0] to stop of secondary motor
*/
combinedStartPower(dutyCycleL, dutyCycleR) {
if (!this.isVirtualDevice) {
throw new Error("Non-virtual Device cannot start Power on two motors.");
}
return PortOutput.buildWriteDirectModeData(
this.portId,
PortOutput.SC_FLAGS.EXECUTE_IMMEDIATE |
PortOutput.SC_FLAGS.COMMAND_FEEDBACK,
Motor.SUB_CMD_START_POWER_COMBINED,
[dutyCycleL, dutyCycleR]
);
}
/**
* Starts two motors for given amount of time.
* Sends message as defined in https://lego.github.io/lego-ble-wireless-protocol-docs/index.html#output-sub-command-startspeedfortime-time-speed-maxpower-endstate-useprofile-0x0a
*
* TODO: Does not work yet
*
* @param {number} time [0..2^15] milliseconds
* @param {number} dutyCycleL [-100..-1] Percentage CCW, [1..100] Percentage CW, [0] to stop
* @param {number} dutyCycleR [-100..-1] Percentage CCW, [1..100] Percentage CW, [0] to stop
* @param {number} maxSpeed [0..100]%
* @param {number} endState One of `Motor.END_STATE_*`
* @param {number} useProfile Bitlist containing profiles to use (Select from `Motor.PROFILE_*`, defaults to `Motor.PROFILE_ACCELERATION | Motor.PROFILE_DEACCELERATION`)
*/
combinedStartSpeedForTime(
time,
dutyCycleL,
dutyCycleR,
maxSpeed = 100,
endState = Motor.END_STATE_FLOAT,
useProfile = Motor.PROFILE_ACCELERATION | Motor.PROFILE_DEACCELERATION
) {
if (!this.isVirtualDevice) {
throw new Error("Non-virtual Device cannot start Power on two motors.");
}
return PortOutput.build(
this.portId,
PortOutput.SC_FLAGS.EXECUTE_IMMEDIATE |
PortOutput.SC_FLAGS.COMMAND_FEEDBACK,
Motor.SUB_CMD_START_SPEED_FOR_TIME_COMBINED,
[
...int16ToArray(time),
dutyCycleL,
dutyCycleR,
maxSpeed,
endState,
useProfile
]
);
}
/**
* Turns two motors in sync for given number of degrees
* Sends message as defined in https://lego.github.io/lego-ble-wireless-protocol-docs/index.html#output-sub-command-startspeedfordegrees-degrees-speedl-speedr-maxpower-endstate-useprofile-0x0c
*
* @param {number} degrees [0..2^31] milliseconds
* @param {number} dutyCycleL [-100..-1] Percentage CCW, [1..100] Percentage CW, [0] to stop
* @param {number} dutyCycleR [-100..-1] Percentage CCW, [1..100] Percentage CW, [0] to stop
* @param {number} maxSpeed [0..100]%
* @param {number} endState One of `Motor.END_STATE_*`
* @param {number} useProfile Bitlist containing profiles to use (Select from `Motor.PROFILE_*`, defaults to `Motor.PROFILE_ACCELERATION | Motor.PROFILE_DEACCELERATION`)
*/
combinedStartSpeedForDegrees(
degrees,
dutyCycleL,
dutyCycleR,
maxSpeed = 100,
endState = Motor.END_STATE_BREAK,
useProfile = Motor.PROFILE_ACCELERATION | Motor.PROFILE_DEACCELERATION
) {
if (!this.isVirtualDevice) {
throw new Error("Non-virtual Device cannot start Power on two motors.");
}
if (degrees < 0) {
degrees = -degrees;
dutyCycleL = -dutyCycleL;
dutyCycleR = -dutyCycleR;
}
return PortOutput.build(
this.portId,
PortOutput.SC_FLAGS.EXECUTE_IMMEDIATE |
PortOutput.SC_FLAGS.COMMAND_FEEDBACK,
Motor.SUB_CMD_START_SPEED_FOR_DEGREES_COMBINED,
[
...int32ToArray(degrees),
dutyCycleL,
dutyCycleR,
maxSpeed,
endState,
useProfile
]
);
}
/**
* Creates stops message for motor.
*/
combinedStop() {
return this.combinedStartSpeed(0, 0);
}
/**
* Receives and processes message with value from sensor.
*
* @param {PortValueSingleMessage} msg
*/
receiveValue(msg) {
this._log("info", `received value`, msg.value, "on port", msg.portId);
}
/**
* Called with feedback from Hub.
*
* @param {number} value One of `PortOutputCommandFeedback.ACTION_*`
*/
receiveCommandFeedback(value) {
this._log("debug", "Motor feedback", value);
switch (value) {
case PortOutputCommandFeedback.ACTION_START:
this.status = Motor.RUNNING;
/**
* Fires when Motor starts running.
* @event Motor#start
*/
this.emit("start");
break;
case PortOutputCommandFeedback.ACTION_STOP:
this.status = Motor.STOPPED;
/**
* Fires when Motor finished running.
* @event Motor#stop
*/
this.emit("stop");
break;
}
}
}
Motor.SUB_CMD_START_POWER = 0x01;
Motor.SUB_CMD_START_POWER_COMBINED = 0x02;
Motor.SUB_CMD_START_SPEED = 0x07;
Motor.SUB_CMD_START_SPEED_COMBINED = 0x08;
Motor.SUB_CMD_START_SPEED_FOR_TIME = 0x09;
Motor.SUB_CMD_START_SPEED_FOR_TIME_COMBINED = 0x0a;
Motor.SUB_CMD_START_SPEED_FOR_DEGREES = 0x0b;
Motor.SUB_CMD_START_SPEED_FOR_DEGREES_COMBINED = 0x0c;
Motor.END_STATE_FLOAT = 0;
Motor.END_STATE_HOLD = 126;
Motor.END_STATE_BREAK = 127;
Motor.PROFILE_DO_NOT_USE = 0;
Motor.PROFILE_ACCELERATION = 0b01;
Motor.PROFILE_DEACCELERATION = 0b10;
Motor.RUNNING = "running";
Motor.STOPPED = "stopped";
Motor.MODE_MOTOR = 2;
module.exports = Motor;