movehub-async
Version:
Asynchronous methods for the Lego Boost Move Hub package
324 lines (299 loc) • 11.6 kB
JavaScript
const { Boost, Hub } = require('movehub/movehub');
const CALLBACK_TIMEOUT_MS = 1000 / 3;
const METRIC_MODIFIER = 28.5;
const IMPERIAL_MODIFIER = METRIC_MODIFIER / 4;
const TURN_MODIFIER = 2.56;
const DRIVE_SPEED = 25;
const TURN_SPEED = 20;
const DEFAULT_STOP_DISTANCE = 105;
const DEFAULT_CLEAR_DISTANCE = 120;
const waitForValueToSet = function(valueName, compareFunc = (valueName) => this[valueName], timeoutMs = 0) {
if (compareFunc.bind(this)(valueName)) return Promise.resolve(this[valueName]);
return new Promise((resolve, reject) => {
setTimeout(async () => resolve(await waitForValueToSet.bind(this)(valueName, compareFunc, timeoutMs)), timeoutMs + 100);
});
};
/**
* Disconnect Hub
* @method Hub#disconnectAsync
* @returns {Promise<boolean>} disconnection successful
*/
Hub.prototype.disconnectAsync = function() {
this.disconnect();
return waitForValueToSet.bind(this)('hubDisconnected');
};
/**
* Execute this method after new instance of Hub is created
* @method Hub#afterInitialization
*/
Hub.prototype.afterInitialization = function() {
this.hubDisconnected = null;
this.ports = {
A: { angle: 0 },
B: { angle: 0 },
AB: { angle: 0 },
C: { angle: 0 },
D: { angle: 0 },
LED: { angle: 0 }
};
this.useMetric = true;
this.modifier = 1;
this.on('rotation', rotation => this.ports[rotation.port].angle = rotation.angle);
this.on('disconnect', () => this.hubDisconnected = true);
this.on('distance', distance => this.distance = distance);
};
/**
* Control the LED on the Move Hub
* @method Hub#ledAsync
* @param {boolean|number|string} color
* If set to boolean `false` the LED is switched off, if set to `true` the LED will be white.
* Possible string values: `off`, `pink`, `purple`, `blue`, `lightblue`, `cyan`, `green`, `yellow`, `orange`, `red`,
* `white`
* @returns {Promise}
*/
Hub.prototype.ledAsync = function(color) {
return new Promise((resolve, reject) => {
this.led(color, () => {
// Callback is executed when command is sent and it will take some time before MoveHub executes the command
setTimeout(resolve, CALLBACK_TIMEOUT_MS);
});
});
}
/**
* Run a motor for specific time
* @method Hub#motorTimeAsync
* @param {string|number} port possible string values: `A`, `B`, `AB`, `C`, `D`.
* @param {number} seconds
* @param {number} [dutyCycle=100] motor power percentage from `-100` to `100`. If a negative value is given rotation
* is counterclockwise.
* @param {boolean} [wait=false] will promise wait unitll motorTime run time has elapsed
* @returns {Promise}
*/
Hub.prototype.motorTimeAsync = function(port, seconds, dutyCycle = 100, wait = false) {
return new Promise((resolve, reject) => {
this.motorTime(port, seconds, dutyCycle, () => {
setTimeout(resolve, wait ? CALLBACK_TIMEOUT_MS + seconds * 1000 : CALLBACK_TIMEOUT_MS);
});
});
}
/**
* Run both motors (A and B) for specific time
* @method Hub#motorTimeMultiAsync
* @param {number} seconds
* @param {number} [dutyCycleA=100] motor power percentage from `-100` to `100`. If a negative value is given rotation
* is counterclockwise.
* @param {number} [dutyCycleB=100] motor power percentage from `-100` to `100`. If a negative value is given rotation
* is counterclockwise.
* @param {boolean} [wait=false] will promise wait unitll motorTime run time has elapsed
* @returns {Promise}
*/
Hub.prototype.motorTimeMultiAsync = function(seconds, dutyCycleA = 100, dutyCycleB = 100, wait = false) {
return new Promise((resolve, reject) => {
this.motorTimeMulti(seconds, dutyCycleA, dutyCycleB, () => {
setTimeout(resolve, wait ? CALLBACK_TIMEOUT_MS + seconds * 1000 : CALLBACK_TIMEOUT_MS);
});
});
}
/**
* Turn a motor by specific angle
* @method Hub#motorAngleAsync
* @param {string|number} port possible string values: `A`, `B`, `AB`, `C`, `D`.
* @param {number} angle - degrees to turn from `0` to `2147483647`
* @param {number} [dutyCycle=100] motor power percentage from `-100` to `100`. If a negative value is given
* rotation is counterclockwise.
* @param {boolean} [wait=false] will promise wait unitll motorAngle has turned
* @returns {Promise}
*/
Hub.prototype.motorAngleAsync = function(port, angle, dutyCycle = 100, wait = false) {
return new Promise((resolve, reject) => {
this.motorAngle(port, angle, dutyCycle, async () => {
if (wait) {
let beforeTurn;
do {
beforeTurn = this.ports[port].angle;
await new Promise(res => setTimeout(res, CALLBACK_TIMEOUT_MS))
} while(this.ports[port].angle !== beforeTurn)
resolve();
} else {
setTimeout(resolve, CALLBACK_TIMEOUT_MS);
}
});
});
}
/**
* Turn both motors (A and B) by specific angle
* @method Hub#motorAngleMultiAsync
* @param {number} angle degrees to turn from `0` to `2147483647`
* @param {number} [dutyCycleA=100] motor power percentage from `-100` to `100`. If a negative value is given
* rotation is counterclockwise.
* @param {number} [dutyCycleB=100] motor power percentage from `-100` to `100`. If a negative value is given
* rotation is counterclockwise.
* @param {boolean} [wait=false] will promise wait unitll motorAngle has turned
* @returns {Promise}
*/
Hub.prototype.motorAngleMultiAsync = function(angle, dutyCycleA = 100, dutyCycleB = 100, wait = false) {
return new Promise((resolve, reject) => {
this.motorAngleMulti(angle, dutyCycleA, dutyCycleB, async () => {
if (wait) {
let beforeTurn;
do {
beforeTurn = this.ports['AB'].angle;
await new Promise(res => setTimeout(res, CALLBACK_TIMEOUT_MS))
} while(this.ports['AB'].angle !== beforeTurn)
resolve();
} else {
setTimeout(resolve, CALLBACK_TIMEOUT_MS);
}
});
});
}
/**
* Use metric units (default)
* @method Hub#useMetricUnits
*/
Hub.prototype.useMetricUnits = function() {
this.useMetric = true;
}
/**
* Use imperial units
* @method Hub#useImperialUnits
*/
Hub.prototype.useImperialUnits = function() {
this.useMetric = false;
}
/**
* Set friction modifier
* @method Hub#setFrictionModifier
* @param {number} modifier friction modifier
*/
Hub.prototype.setFrictionModifier = function(modifier) {
this.modifier = modifier;
}
/**
* Drive specified distance
* @method Hub#drive
* @param {number} distance distance in centimeters (default) or inches. Positive is forward and negative is backward.
* @param {boolean} [wait=true] will promise wait untill the drive has completed.
* @returns {Promise}
*/
Hub.prototype.drive = function(distance, wait = true) {
const angle = Math.abs(distance) * ((this.useMetric ? METRIC_MODIFIER : IMPERIAL_MODIFIER) * this.modifier);
const dutyCycleA = DRIVE_SPEED * (distance > 0 ? 1 : -1);
const dutyCycleB = DRIVE_SPEED * (distance > 0 ? 1 : -1);
return this.motorAngleMultiAsync(angle, dutyCycleA, dutyCycleB, wait);
}
/**
* Turn robot specified degrees
* @method Hub#turn
* @param {number} degrees degrees to turn. Negative is to the left and positive to the right.
* @param {boolean} [wait=true] will promise wait untill the turn has completed.
* @returns {Promise}
*/
Hub.prototype.turn = function(degrees, wait = true) {
const angle = Math.abs(degrees) * TURN_MODIFIER;
const dutyCycleA = TURN_SPEED * (degrees > 0 ? 1 : -1);
const dutyCycleB = TURN_SPEED * (degrees > 0 ? -1 : 1);
return this.motorAngleMultiAsync(angle, dutyCycleA, dutyCycleB, wait);
}
/**
* Drive untill sensor shows object in defined distance
* @method Hub#driveUntil
* @param {number} [distance=0] distance in centimeters (default) or inches when to stop. Distance sensor is not very sensitive or accurate.
* By default will stop when sensor notices wall for the first time. Sensor distance values are usualy between 110-50.
* @param {boolean} [wait=true] will promise wait untill the bot will stop.
* @returns {Promise}
*/
Hub.prototype.driveUntil = async function(distance = 0, wait = true) {
const distanceCheck = distance !== 0 ? (this.useMetric ? distance : distance * 2.54) : DEFAULT_STOP_DISTANCE;
this.motorTimeMulti(60, DRIVE_SPEED, DRIVE_SPEED);
if (wait) {
await waitForValueToSet.bind(this)('distance', () => distanceCheck >= this.distance);
await this.motorAngleMultiAsync(0);
}
else {
return waitForValueToSet.bind(this)('distance', () => distanceCheck >= this.distance).then(_ => this.motorAngleMulti(0, 0, 0));
}
}
/**
* Turn until there is no object in sensors sight
* @method Hub#turnUntil
* @param {number} [direction=1] direction to turn to. 1 (or any positive) is to the right and 0 (or any negative) is to the left.
* @param {boolean} [wait=true] will promise wait untill the bot will stop.
* @returns {Promise}
*/
Hub.prototype.turnUntil = async function(direction = 1, wait = true) {
const directionModifier = direction > 0 ? 1 : -1;
this.turn(360 * directionModifier, false);
if (wait) {
await waitForValueToSet.bind(this)('distance', () => this.distance >= DEFAULT_CLEAR_DISTANCE);
await this.turn(0, false)
}
else {
return waitForValueToSet.bind(this)('distance', () => this.distance >= DEFAULT_CLEAR_DISTANCE).then(_ => this.turn(0, false));
}
}
/**
* Get BLE status when BLE is ready to be used
* @method Boost#bleReadyAsync
* @returns {Promise<boolean>} ble status `true`/`false` when ble is ready
*/
Boost.prototype.bleReadyAsync = function() {
return new Promise(async (resolve, reject) => {
var ready = await waitForValueToSet.bind(this)('bleReadyStatus');
if (ready)
resolve(ready);
else
reject(ready);
});
};
/**
* Get Hub details when hub is found
* @method Boost#hubFoundAsync
* @returns {Promise<{uudi: string, address:string, localName: string}>} Hub details
*/
Boost.prototype.hubFoundAsync = function() {
return waitForValueToSet.bind(this)('hubDetails');
};
/**
* @method Boost#connectAsync
* @param hubDetails {object} MAC Address of the Hub
* @param hubDetails.uuid {string}
* @param hubDetails.address{string}
* @param hubDetails.localName {string}
* @returns {Promise<Hub>} Hub object
*/
Boost.prototype.connectAsync = function(hubDetails) {
return new Promise((resolve, reject) => {
this.connect(hubDetails.address, async (err, hub) => {
if (err) {
reject(err);
} else {
hub.afterInitialization();
await waitForValueToSet.bind(hub)('connected');
resolve(hub);
}
});
});
};
/**
* Connect to a MoveHub and get Hub instance
* @method Boost#getHubAsync
* @returns {Promise<Hub>} Hub object
*/
Boost.prototype.getHubAsync = async function() {
await this.bleReadyAsync();
const connectDetails = await this.hubFoundAsync();
return await this.connectAsync(connectDetails);
};
/**
* Execute this method after new instance is created
* @method Boost#afterInitialization
*/
Boost.prototype.afterInitialization = function() {
this.bleReadyStatus = null;
this.hubDetails = null;
this.on('ble-ready', status => (this.bleReadyStatus = status));
this.on('hub-found', hubDetails => (this.hubDetails = hubDetails));
};
module.exports.Boost = Boost;
module.exports.Hub = Hub;