swarms
Version:
The ultimate node.js library for controlling Bitcraze Crazyflie 2.0 drones
384 lines • 14.2 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const constants_1 = require("../constants");
const crazyflie_1 = require("../crazyflie");
const packet_1 = require("../packet");
const uri_1 = require("../uri");
const utils_1 = require("../utils");
const usbstreams_1 = require("./usbstreams");
const events_1 = require("events");
const usb = require("usb");
class Crazyradio extends events_1.EventEmitter {
/**
* Class for controlling the Crazyradio
*/
constructor() {
super();
// Crazyradio options
this.options = exports.defaultOptions;
this.initialized = false;
// Used in `onInStreamData()` for keeping track of current console buffer
// so we can emit a 'console line' event which automagically emits between newline characters
this.consoleLine = '';
// How many milliseconds the interval should be. A ping will be sent no matter what on this interval.
// (for fallbackPingInterval)
this.pingInterval = 5;
// How many milliseconds after receiving a non-empty packet should we wait until sending another ping
// (for fallbackPingTimeout)
this.packetResponseTimeout = -1;
}
/**
* For doing all the asynchronous setup of the Crazyradio
*/
async init(options, device = Crazyradio.findRadios()[0]) {
if (this.initialized) {
return Promise.reject('Crazyradio already initialized!');
}
// Make sure there's device
if (!device) {
return Promise.reject('No Crazyradio dongle attached!');
}
this.device = device;
// Get firmware version of Crazyradio
this.version = parseFloat((this.device.deviceDescriptor.bcdDevice >> 8)
+ '.' +
(this.device.deviceDescriptor.bcdDevice & 0x0ff));
// Connect to dongle
this.device.open();
this.interface = this.device.interfaces[0];
this.interface.claim();
this.inEndpoint = this.interface.endpoints[0];
this.outEndpoint = this.interface.endpoints[1];
// Initialize USB streams
this.inStream = new usbstreams_1.InStream(this.inEndpoint);
this.outStream = new usbstreams_1.OutStream(this.outEndpoint);
this.inStream.on('data', this.onInStreamData.bind(this));
this.inStream.on('error', this.onInStreamError.bind(this));
this.initialized = true;
// Configure Crazyradio
try {
await this.configure(options);
}
catch (err) {
return Promise.reject(`Problem configuring Crazyradio: ${err}`);
}
}
/**
* Configure new options
*/
configure(options) {
// Default options
options = Object.assign({}, exports.defaultOptions, this.options, options);
return this.setRadioChannel(options.channel)
.then(() => this.setRadioAddress(options.address))
.then(() => this.setDataRate(options.dataRate))
.then(() => this.setRadioPower(options.radioPower))
.then(() => this.setAckRetryDelay(options.ard))
.then(() => this.setAckRetryCount(options.arc))
.then(() => this.setAckEnable(options.ackEnable))
.then(() => this.setContCarrier(options.contCarrier));
}
/**
* Close the dongle
*/
close() {
this.disconnect();
return new Promise((resolve, reject) => {
if (!this.interface) {
resolve();
this.initialized = false;
}
this.interface.release(true, err => {
if (err) {
reject(err);
return;
}
this.initialized = false;
resolve();
});
});
}
/**
* Tune into the correct parameters to connect to a Crazyflie uri and return a Crazyflie instance
*/
connect(uri) {
// Make sure intervals and timeouts are cleared first
this.disconnect();
return this.configure({
dataRate: uri.dataRate,
channel: uri.channel
})
.then(async () => {
// Set ping interval
clearInterval(this.fallbackPingInterval);
if (this.pingInterval >= 0) {
this.fallbackPingInterval = setInterval(() => {
this.ping();
}, this.pingInterval);
}
const drone = new crazyflie_1.Crazyflie(this);
await drone.init();
return drone;
});
}
/**
* Stop connecting to the drone
*/
disconnect() {
// Let the connected drone(s) do any other disconnect stuff
this.emit('disconnect');
// Stop pinging the drone
clearInterval(this.fallbackPingInterval);
clearTimeout(this.fallbackPingTimeout);
}
/**
* Scan for any nearby Crazyflies
*/
async findDrones() {
try {
const prevARC = this.options.arc;
await this.setAckRetryCount(1);
let drones = [];
for (const rate of utils_1.properEnumKeys(constants_1.DATA_RATES)) {
drones = drones.concat(await this.scanRange(constants_1.DATA_RATES[rate]));
}
await this.setAckRetryCount(prevARC);
return Promise.resolve(drones);
}
catch (err) {
return Promise.reject(err);
}
}
/**
* Scan for any drones on a specific data rate
*/
scanRange(dataRate) {
return this.setDataRate(dataRate)
.then(() => this.scanChannels())
.then((drones) => {
const uris = [];
for (const drone of drones) {
uris.push(new uri_1.Uri(dataRate, drone));
}
return uris;
});
}
/**
* Ping the Crazyflie
*/
ping() {
const packet = new packet_1.Packet();
packet.port = 15;
packet.write('int8', 0x01);
return this.sendPacket(packet);
}
/**
* Communicating with the Crazyflies
*/
sendPacket(packet) {
if (!this.initialized) {
return Promise.reject('Crazyradio not yet initialized! Did you remember to call `Crazyradio.init()` first?');
}
return new Promise((resolve, reject) => {
this.outStream.write(packet.export(), (err) => {
if (err) {
reject(err);
}
else {
resolve();
}
});
});
}
onInStreamData(data) {
const ackPack = new packet_1.Ack(data);
this.emit('all', ackPack);
// If ack pack lacks the feedback, it's a slack
if (!ackPack.ackReceived) {
this.emit('no ack', ackPack);
return;
}
switch (ackPack.port) {
case constants_1.PORTS.CONSOLE:
this.emit('console', ackPack);
// Below logic is for emitting the 'console line' event.
// This will emit output just like the 'console' event, but only between newline characters,
// so every emit is a new line
// Add new console data to the current line string
this.consoleLine += ackPack.data.toString();
// Divide up console line by newline characters
const lines = this.consoleLine.split(/\r?\n/);
// Loop through broken up parts either emitting them as a 'console line' event
// or saving it in the consoleLine for when there is a newline character in the future
for (let i = 0; i < lines.length; i++) {
if (i === lines.length - 1) {
this.consoleLine = lines[i];
break;
}
this.emit('console line', lines[i]);
}
break;
case constants_1.PORTS.PARAMETERS:
this.emit('parameters', ackPack);
break;
case constants_1.PORTS.COMMANDER:
this.emit('commander', ackPack);
break;
case constants_1.PORTS.LOGGING:
this.emit('logging', ackPack);
break;
case constants_1.PORTS.LINK_LAYER:
this.emit('link layer', ackPack);
break;
default:
this.emit('other', ackPack);
break;
}
// If the response packet wasn't empty, add a timeout to get another ping sooner
if (!ackPack.equals(packet_1.Ack.emptyPing)) {
clearTimeout(this.fallbackPingTimeout);
if (this.packetResponseTimeout >= 0) {
this.fallbackPingTimeout = setTimeout(() => {
this.ping();
}, this.packetResponseTimeout);
}
}
}
onInStreamError(err) {
this.emit('error', err);
}
/**
* Configuration functions
*/
setRadioChannel(channel) {
if (0 > channel || channel > 125) {
return Promise.reject(`Channel out of range! (${channel})`);
}
this.options.channel = channel;
return this.sendVendorSetup(constants_1.VENDOR_REQUESTS.SET_RADIO_CHANNEL, channel);
}
setRadioAddress(address) {
const buf = Buffer.from(utils_1.toHex(address), 'hex');
if (buf.length !== 5) {
return Promise.reject(`Address should be 5 bytes long! Not ${buf.length}!`);
}
this.options.address = address;
return this.sendVendorSetup(constants_1.VENDOR_REQUESTS.SET_RADIO_ADDRESS, 0, 0, buf);
}
setDataRate(rate) {
if (constants_1.GET_DATA_RATE(rate) === null) {
return Promise.reject(`Data rate out of range! (${rate})`);
}
this.options.dataRate = rate;
return this.sendVendorSetup(constants_1.VENDOR_REQUESTS.SET_DATA_RATE, rate);
}
setRadioPower(power) {
if (constants_1.GET_RADIO_POWER(power) === null) {
return Promise.reject(`Radio power out of range! (${power})`);
}
this.options.radioPower = power;
return this.sendVendorSetup(constants_1.VENDOR_REQUESTS.SET_RADIO_POWER, power);
}
setAckRetryDelay(delay) {
this.options.ard = delay;
return this.sendVendorSetup(constants_1.VENDOR_REQUESTS.SET_RADIO_ARD, delay);
}
setAckRetryDelayMicroseconds(microseconds) {
/*
* Auto Retransmit Delay in microseconds
* 0000 - Wait 250uS
* 0001 - Wait 500uS
* 0010 - Wait 750uS
* ........
* 1111 - Wait 4000uS
*/
let time = Math.floor(microseconds / 250);
// Time limits
if (time < 0) {
time = 0;
}
if (time > 0xF) {
time = 0xF;
}
return this.setAckRetryDelay(time);
}
setAckRetryDelayBytes(bytes) {
return this.setAckRetryDelay(0x80 | bytes);
}
setAckRetryCount(count) {
if (0 > count || count > 15) {
return Promise.reject(`Retry count out of range! (${count})`);
}
this.options.arc = count;
return this.sendVendorSetup(constants_1.VENDOR_REQUESTS.SET_RADIO_ARC, count);
}
setAckEnable(active) {
this.options.ackEnable = active;
return this.sendVendorSetup(constants_1.VENDOR_REQUESTS.ACK_ENABLE, (active ? 1 : 0));
}
setContCarrier(active) {
this.options.contCarrier = active;
return this.sendVendorSetup(constants_1.VENDOR_REQUESTS.SET_CONT_CARRIER, (active ? 1 : 0));
}
scanChannels(start = 0, stop = 125, packet = constants_1.BUFFERS.SOMETHING) {
return this.sendVendorSetup(constants_1.VENDOR_REQUESTS.SCAN_CHANNELS, start, stop, packet)
.then(() => this.getVendorSetup(constants_1.VENDOR_REQUESTS.SCAN_CHANNELS, 0, 0, 64));
}
sendVendorSetup(request, value, index = 0, data = constants_1.BUFFERS.NOTHING) {
if (!this.initialized) {
return Promise.reject('Crazyradio not yet initialized! Did you remember to call `Crazyradio.init()` first?');
}
return new Promise((resolve, reject) => {
this.device.controlTransfer(constants_1.BM_REQUEST_TYPE, request, value, index, data, err => {
if (err) {
reject(err);
}
else {
resolve();
}
});
});
}
getVendorSetup(request, value, index, length) {
if (!this.initialized) {
return Promise.reject('Crazyradio not yet initialized! Did you remember to call `Crazyradio.init()` first?');
}
return new Promise((resolve, reject) => {
this.device.controlTransfer(constants_1.BM_REQUEST_TYPE | usb.LIBUSB_ENDPOINT_IN, request, value, index, length, (err, buf) => {
if (err) {
reject(err);
}
else {
resolve(buf);
}
});
});
}
/**
* Find available Crazyradios plugged in via USB
*/
static findRadios(vid = constants_1.CRAZYRADIO.VID, pid = constants_1.CRAZYRADIO.PID) {
// Only return devices that match the specified product id and vendor id
return this.findUSBDevices()
.filter(device => (device.deviceDescriptor.idVendor === vid)
&& (device.deviceDescriptor.idProduct === pid));
}
/**
* Find ALL USB devices plugged into the Computer
*/
static findUSBDevices() {
return usb.getDeviceList();
}
}
exports.Crazyradio = Crazyradio;
exports.defaultOptions = {
channel: 2,
address: 0xE7E7E7E7E7,
dataRate: constants_1.DATA_RATES['2M'],
radioPower: constants_1.RADIO_POWERS['0dBm'],
ard: 0xA0,
arc: 3,
ackEnable: true,
contCarrier: false
};
//# sourceMappingURL=crazyradio.js.map