UNPKG

aerogel

Version:

CrazyFlie control software

401 lines (324 loc) 8.92 kB
var _ = require('lodash'), assert = require('assert'), domain = require('domain'), events = require('events'), usb = require('usb'), util = require('util'), P = require('p-promise'), Crazypacket = require('./crazypacket'), Protocol = require('./protocol'), usbstreams = require('./usbstreams'), WritableUSBStream = usbstreams.WritableUSBStream, ReadableUSBStream = usbstreams.ReadableUSBStream ; // a libusb constant var TYPE_VENDOR = (0x02 << 5); var LIBUSB_REQUEST_GET_STATUS = 0x0; // radio commands var SET_RADIO_CHANNEL = 0x01; var SET_RADIO_ADDRESS = 0x02; var SET_DATA_RATE = 0x03; var SET_RADIO_POWER = 0x04; var SET_RADIO_ARD = 0x05; var SET_RADIO_ARC = 0x06; var ACK_ENABLE = 0x10; var SET_CONT_CARRIER = 0x20; var SCAN_CHANNELS = 0x21; var LAUNCH_BOOTLOADER = 0xFF; var P_M18DBM = 0; var P_M12DBM = 1; var P_M6DBM = 2; var P_0DBM = 3; function findCrazyradios() { // Return a list of radios attached to the host var devices = usb.getDeviceList(); return _.filter(devices, function(d) { return ((d.deviceDescriptor.idVendor == 0x1915) && (d.deviceDescriptor.idProduct == 0x7777)); }); } var Crazyradio = module.exports = function Crazyradio() { events.EventEmitter.call(this); this.device = undefined; this.interface = undefined; this.version = 0; this.arc = -1; this.address = undefined; this.outstream = null; this.instream = null; this.channel = 2; this.datarate = 2; this.pingTimer = null; this.pingInterval = null; }; util.inherits(Crazyradio, events.EventEmitter); Crazyradio.DATARATE = { '250KPS': 0, '1MPS': 1, '2MPS': 2 }; Crazyradio.prototype.inputStream = function() { if (!this.instream) this.instream = new ReadableUSBStream(this.inEndpoint); return this.instream; }; Crazyradio.prototype.outputStream = function() { if (!this.outstream) this.outstream = new WritableUSBStream(this.outEndpoint); return this.outstream; }; Crazyradio.prototype.setupRadio = function(device, channel, datarate) { if (device) this.device = device; else { var possible = findCrazyradios(); if (possible.length > 0) this.device = possible[0]; else throw(new Error('no Crazyradio dongle attached')); } this.channel = (_.isUndefined(channel) ? 2 : channel); this.datarate = (_.isUndefined(datarate) ? 2 : datarate); // console.log('setting up radio at ', channel, datarate); this.device.open(); this.interface = this.device.interfaces[0]; this.interface.claim(); this.inEndpoint = this.interface.endpoints[0]; this.outEndpoint = this.interface.endpoints[1]; this.version = parseFloat(util.format('%d.%d', this.device.deviceDescriptor.bcdDevice >> 8, this.device.deviceDescriptor.bcdDevice & 0x0ff)); if (this.version < 0.4) throw(new Error('this driver requires at least version 0.4 of the radio firmware; ' + this.version)); var input = this.inputStream(); input.addListener('readable', this.onReadable.bind(this)); input.addListener('error', this.onInputError.bind(this)); // Last-ditch heartbeat to get data from the copter every second. this.pingInterval = setInterval(this.ping.bind(this), 1000); return this.reset(); }; Crazyradio.prototype.reset = function() { var self = this; this.arc = -1; this.address = new Buffer(5); this.address.fill(0xE7); return this.setChannel(self.channel) .then(self.setDataRate(self.datarate)) .then(self.setContCarrier(false)) .then(self.setAddress(self.address)) .then(self.setPower(P_0DBM)) .then(self.setAckRetryCount(3)) .then(self.setARDBytes(32)); }; Crazyradio.prototype.close = function() { var self = this, deferred = P.defer(); if (this.pingTimer) clearTimeout(this.pingTimer); this.interface.release(function(err) { if (err) deferred.reject(err); this.device = null; this.interface = null; this.inEndpoint = null; this.outEndpoint = null; deferred.resolve('OK'); }); return deferred.promise; }; var EMPTY_BUFFER = new Buffer(0); Crazyradio.prototype.setChannel = function(channel) { return this.usbSendVendor(SET_RADIO_CHANNEL, channel, 0, EMPTY_BUFFER); }; Crazyradio.prototype.setAddress = function(address) { return this.usbSendVendor(SET_RADIO_ADDRESS, 0, 0, address); }; Crazyradio.prototype.setDataRate = function(rate) { return this.usbSendVendor(SET_DATA_RATE, rate, 0, EMPTY_BUFFER); }; Crazyradio.prototype.setPower = function(level) { return this.usbSendVendor(SET_RADIO_POWER, level, 0, EMPTY_BUFFER); }; Crazyradio.prototype.setAckRetryCount = function(retries) { this.arc = retries; return this.usbSendVendor(SET_RADIO_ARC, retries, 0, EMPTY_BUFFER); }; Crazyradio.prototype.setAckRetryDelay = function(delay) { /* Auto Retransmit Delay in microseconds 0000 - Wait 250uS 0001 - Wait 500uS 0010 - Wait 750uS ........ 1111 - Wait 4000uS */ var t = Math.floor(delay/250); if (t > 0xF) t = 0xF; return this.usbSendVendor(SET_RADIO_ARD, t, 0, EMPTY_BUFFER); }; Crazyradio.prototype.setARDBytes = function(nbytes) { return this.usbSendVendor(SET_RADIO_ARD, 0x80 | nbytes, 0, EMPTY_BUFFER); }; Crazyradio.prototype.setContCarrier = function(active) { return this.usbSendVendor(SET_CONT_CARRIER, (active ? 1 : 0), 0, EMPTY_BUFFER); }; Crazyradio.prototype.scanChannels = function(start, stop, packet) { var self = this; return self.usbSendVendor(SCAN_CHANNELS, start, stop, packet) .then(function(res) { return self.usbReceiveVendor(SCAN_CHANNELS, 0, 0, 64); }) .fail(function(err) { console.log(err); }); }; Crazyradio.prototype.statusRequest = function() { var self = this, deferred = P.defer(); this.device.controlTransfer(usb.LIBUSB_ENDPOINT_IN, 0x06, 0, 0, 128, function(err, data) { if (err) return deferred.reject(err); deferred.resolve(data); }); return deferred.promise; }; // USB control transfer conveniences Crazyradio.prototype.usbSendVendor = function(request, value, index, data) { var self = this, deferred = P.defer(); this.device.controlTransfer(TYPE_VENDOR, request, value, index, data, function(err, indata) { if (err) { console.log('usbSendVendor', err); console.log(TYPE_VENDOR, request, value, index, data); return deferred.reject(err); } // console.log('successful usbSendVendor', request); deferred.resolve('OK'); }); return deferred.promise; }; Crazyradio.prototype.usbReceiveVendor = function(request, value, index, length) { var self = this, deferred = P.defer(); this.device.controlTransfer(TYPE_VENDOR | usb.LIBUSB_ENDPOINT_IN, request, value, index, length, function(err, data) { if (err) { console.log('usbReceiveVendor error:', err); return deferred.reject(err); } deferred.resolve(data); }); return deferred.promise; }; // -------------------------------------------- Crazyradio.prototype.ping = function() { // 0xf0 0x01 0x01 0xf2 var self = this; var p = new Crazypacket(); p.header = 0xF0; p.writeByte(0x01).writeByte(0x01).writeByte(0xf2).endPacket(); this.write(p.data); }; Crazyradio.prototype.onReadable = function() { // TODO need to make sure we read one radio packet at a time, exactly var buf = this.inputStream().read(); if (!buf || !buf.length) return; this.pingTimer = setTimeout(this.ping(), 100); var ack = new Crazypacket.Ack(buf); if (ack.packet.data.length === 0) return; // console.log('got packet for port ' + ack.packet.port, 'length:', ack.packet.data.length); switch (ack.packet.port) { case Protocol.Ports.CONSOLE: this.emit('console', ack.packet); break; case Protocol.Ports.PARAM: this.emit('param', ack.packet); break; case Protocol.Ports.COMMANDER: this.emit('commander', ack.packet); break; case Protocol.Ports.LOGGING: this.emit('logging', ack.packet); break; case Protocol.Ports.LINKCTRL: this.emit('linkcontrol', ack); break; default: console.log('unknown port', ack.packet.port); } }; Crazyradio.prototype.onInputEnd = function() { // TODO }; Crazyradio.prototype.onInputError = function(err) { console.log('usb stream input error:', err); this.close() .then(function() { console.log('shutting down following usb error.'); process.exit(0); }); }; Crazyradio.prototype.write = function(data, timeout) { var self = this, deferred = P.defer(); var d = domain.create(); d.on('error', function(err) { deferred.reject(err); }); d.run(function() { self.outputStream().write(data, function(err) { if (err) deferred.reject(err); else deferred.resolve('OK'); }); }); return deferred.promise; }; Crazyradio.prototype.sendPacket = function(packet) { if (Buffer.isBuffer(packet)) return this.write(packet); if (Buffer.isBuffer(packet.data)) return this.write(packet.data); throw (new Error('argument to sendPacket() must either be a buffer or have one in .data')); };