UNPKG

swarms

Version:

The ultimate node.js library for controlling Bitcraze Crazyflie 2.0 drones

384 lines 14.2 kB
"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