boost-movehub
Version:
Connects with your Lego Boost Movehub vie Bluetooth Low Energy (BLE).
289 lines (264 loc) • 8.13 kB
JavaScript
const MovehubPorts = require("../MovehubPorts");
const Motor = require("../peripherals/Motor");
const { promiseTimeout } = require("../helpers");
// The wheels have a radius of 1.75cm, so the perimeter is 10.996cm (let's say 11cm)
const WHEEL_PERIMETER = 11;
const DEGREES_PER_CM = 360 / WHEEL_PERIMETER;
const EMIT_TO_SENSOR = {
color: MovehubPorts.PORT_C,
current: MovehubPorts.PORT_CURRENT,
distance: MovehubPorts.PORT_C,
tilt: MovehubPorts.PORT_TILT,
voltage: MovehubPorts.PORT_VOLTAGE
};
/**
* Interface to work with your R2D2 robot.
*/
module.exports = class R2D2 {
constructor(hub, options = {}) {
this.hub = hub;
this.logger = options.logger || {};
}
/**
* Register event handlers on specific topics.
*
* @param {string} what Type of event to listen to.
* @param {Function} cb Callback method to call when event is happening.
*
* @returns {Promise<void>}
*/
async on(what, cb) {
const portId = EMIT_TO_SENSOR[what];
if (!portId) {
this._log("warn", `Don't know on what port to listen for event ${what}`);
return;
}
const sensor = this.hub.ports.get(portId);
if (!sensor.subscriptionActive) {
await this._subscribeTo(sensor);
}
return sensor.on(what, cb);
}
get rgbLed() {
const led = this.hub.ports.get(MovehubPorts.PORT_LED);
return {
/**
* Sets color of RGB to given index color.
* @method R2D2.rgbLed.setColor
*
* @param {number} color The index of color to use. (One of `RgbLed.COLOR_*`)
*
* @returns {Promise<void>}
*/
setColor: c => {
this.hub.sendMessage(led.setColor(c));
return Promise.resolve();
}
};
}
get wheels() {
const motor = this.hub.ports.get(MovehubPorts.PORT_AB);
return {
/**
* Starts both wheels synchronous using given speed.
* @method R2D2.wheels.drive
*
* @param {number} speed Speed ranging from 0 to 100 (%).
*
* @returns {Promise<void>}
*/
drive: speed => {
this.hub.sendMessage(motor.combinedStartSpeed(speed, speed));
return Promise.resolve();
},
/**
* Starts both wheels synchronous using given speed until the traveled the given distance.
* @method R2D2.wheels.driveDistance
*
* @param {number} centimeters Number of centimeters until wheels stop.
* @param {number} speed Speed ranging from 0 to 100 (%).
*
* @returns {Promise<void>}
*/
driveDistance: (centimeters, speed) => {
const degrees = centimeters * DEGREES_PER_CM;
this.hub.sendMessage(
motor.combinedStartSpeedForDegrees(degrees, speed, speed)
);
// TODO real promise when finished
return Promise.resolve();
},
/**
* Starts both wheels synchronous using given speed for given amount of time.
* @method R2D2.wheels.driveTime
*
* @param {number} time Number of milliseconds until wheels stop.
* @param {number} speed Speed ranging from 0 to 100 (%).
*
* @returns {Promise<void>}
*/
driveTime: (time, speed) => {
this.hub.sendMessage(
motor.combinedStartSpeedForTime(time, speed, speed)
);
return promiseTimeout(time);
},
/**
* Stop both wheels.
* @method R2D2.wheels.stop
*
* @returns {Promise<void>}
*/
stop: () => {
this.hub.sendMessage(motor.combinedStop());
return Promise.resolve();
}
};
}
get chassis() {
const motor = this.hub.ports.get(MovehubPorts.PORT_AB);
const tiltSensor = this.hub.ports.get(MovehubPorts.PORT_TILT);
// TODO: add method that checks if chassis is currenlty open
return {
/**
* Resolves telling if the legs are behind the body of R2D2.
* @method R2D2.chassis.isOpen
*
* @returns {Promise<boolean>}
*/
isOpen: async () => {
if (!tiltSensor.subscriptionActive) {
await this._subscribeTo(tiltSensor);
}
const { pitch } = await tiltSensor.getValueAsync();
return pitch >= 179 && pitch <= 181;
},
/**
* Speeds up the wheels for a short amount of time, so the force will bring
* R2D2’s body in front of his legs.
* @method R2D2.chassis.open
*
* @returns {Promise<void>} Resolves when the movement is done.
*/
open: () => {
this.hub.sendMessage(
motor.combinedStartSpeedForDegrees(
135,
100,
100,
100,
Motor.END_STATE_BREAK,
Motor.PROFILE_DO_NOT_USE
)
);
return new Promise(resolve => {
motor.once("stop", resolve);
});
},
/**
* TODO: Brings the legs close to R2D2’s body.
* @method R2D2.chassis.close
*
* @returns {Promise<void>} Resolves when the movement is done.
*/
close: () => {
// TODO: this does not work yet
this.hub.sendMessage(
motor.combinedStartSpeedForDegrees(
-235,
100,
100,
100,
Motor.END_STATE_BREAK,
Motor.PROFILE_DO_NOT_USE
)
);
return new Promise(resolve => {
motor.once("stop", resolve);
});
}
};
}
get head() {
const head = this.hub.ports.get(MovehubPorts.PORT_D);
return {
subscribe: () => {
return this._subscribeTo(sensor);
},
unsubscribe: () => {
return this._unsubscribeFrom(sensor);
},
/**
* Starts turning the head.
* @method R2D2.head.turn
*
* @param {number} speed The speed ranging from -100 to 100 (%). (Clockwise or counter-clockwise)
*
* @returns {Promise<void>} Resolves immediately.
*/
turn: speed => {
this.hub.sendMessage(head.startSpeed(speed));
return Promise.resolve();
},
/**
* Stops turning the head.
* @method R2D2.head.stop
*
* @returns {Promise<void>} Resolves immediately.
*/
stop: () => {
this.hub.sendMessage(head.stop());
return Promise.resolve();
},
/**
* Starts turning the head for given amount of degrees.
* @method R2D2.head.turnDegrees
*
* @param {number} degrees The amount of degrees to turn. Can be positive or negative for clockwise and counter-clockwise movement.
* @param {number} speed The speed ranging from 0 to 100 (%).
*
* @returns {Promise<void>} Resolves after movement finishes.
*/
turnDegrees: (degrees, speed) => {
// We have to turn the motor more to get the head around 360 degrees
this.hub.sendMessage(
head.startSpeedForDegrees(Math.round(1.66 * degrees), speed)
);
return new Promise(resolve => {
head.once("stop", resolve);
});
},
/**
* Starts turning the head for given amount of milliseconds.
* @method R2D2.head.turnTime
*
* @param {number} time Milliseconds to turn
* @param {number} speed The speed ranging from 0 to 100 (%).
*
* @returns {Promise<void>} Resolves after movement finishes.
*/
turnTime: (time, speed) => {
this.hub.sendMessage(head.startSpeedForTime(time, speed));
return new Promise(resolve => {
head.once("stop", resolve);
});
}
};
}
_subscribeTo(peripheral) {
return new Promise(resolve => {
peripheral.once("subscribed", resolve);
this.hub.sendMessage(peripheral.subscribe());
});
}
_unsubscribeFrom(peripheral) {
return new Promise(resolve => {
peripheral.once("unsubscribed", resolve);
this.hub.sendMessage(peripheral.unsubscribe());
});
}
_log(type, ...message) {
this.logger[type] &&
this.logger[type]("[R2D2]", new Date().toISOString(), ...message);
}
};