UNPKG

sphero-pwn

Version:
252 lines (231 loc) 9.2 kB
# Bluetooth LE communication channel with a robot. class BleChannel # Creates a channel over a set of discovered Bluetooth LE characteristics. # # @param {Peripheral} peripheral # @param {Object<String, Object<String, Characteristic>>} characteristics # the Bluetooth LE characteristics that make up a communication channel constructor: (peripheral, characteristics) -> @sourceId = "ble://#{peripheral.address}" @_peripheral = peripheral @_characteristics = characteristics @_characteristics.robot.responses.addListener 'data', (data) => @onData data @_openPromise = null @_closePromise = null # @see {SerialChannel#open} open: -> @_openPromise ||= @_activate() # @see {SerialChannel#write} write: (data) => @open() .then => offset = 0 writeChunk = => if offset >= data.length return Promise.resolve true (new Promise (resolve, reject) => length = data.length - offset length = 20 if length > 20 chunk = data.slice offset, offset + length @_characteristics.robot.commands.write chunk, true, (error) => if error reject error else offset += length resolve true ).then => writeChunk() writeChunk() # @see {SerialChannel#close} close: -> @open() .then => @_closePromise ||= new Promise (resolve, reject) => @_peripheral.disconnect() resolve true # @see {SerialChannel#onData} onData: (data) -> return # @see {SerialChannel#onError} onError: (error) -> return # @see {SerialChannel#sourceId} sourceId: null # Performs the serial channel activation sequence. # # @return {Promise<Boolean>} resolved with true when the robot received the # commands needed to activate the serial channel _activate: -> chain = Promise.resolve true for write in BleChannel._activationSequence do (write) => name = write[0] buffer = write[1] chain = chain.then => BleChannel._bleWrite @_characteristics.radio[write[0]], write[1] chain.then => BleChannel._bleNotify @_characteristics.robot.responses # Connects to a robot over Bluetooth LE. # # @param {Peripheral} peripheral a Bluetooth LE peripheral that might be a # Sphero robot # @return {Promise<BleChannel?>} resolved with a {BleChannel} instance # connecting to the robot represented by the given peripheral; the instance # may be null if the peripheral is not a Sphero robot @fromPeripheral: (peripheral) -> @_bleConnect(peripheral) .then => @_bleGetServices(peripheral) .then (services) => if services is null peripheral.disconnect() return null @_bleGetCharacteristics peripheral, services .then (characteristics) => if characteristics is null peripheral.disconnect() return null new BleChannel peripheral, characteristics # Enables notifications coming in from a Bluetooth LE characteristic. # # @param {Characteristic} characteristic the Bluetooth LE characteristic # whose change notifications will be received # @return {Promise<Boolean>} resolved with true when the characteristic's # notifications are enabled @_bleNotify: (characteristic) -> new Promise (resolve, reject) -> characteristic.notify true, (error) -> if error reject error return resolve true # Writes to a Bluetooth LE characteristic. # # @param {Characteristic} characteristic the Bluetooth LE characteristic # @param {Buffer} data the data that will be written to the characteristic # @return {Promise<Boolean>} resolved with true when the characteristic is # written @_bleWrite: (characteristic, data) -> new Promise (resolve, reject) => characteristic.write data, true, (error) => if error reject error return resolve true # Connects to a Bluetooth LE peripheral. # # @param {Peripheral} peripheral a Bluetooth LE peripheral that might be a # Sphero robot # @return {Promise<Boolean>} resolved with true when the peripheral is # connected @_bleConnect: (peripheral) -> new Promise (resolve, reject) -> onBleConnect = -> peripheral.removeListener 'connect', onBleConnect resolve true peripheral.addListener 'connect', onBleConnect peripheral.connect() # Discovers the robot's Bluetooth LE services. # # @param {Peripheral} peripheral a Bluetooth LE peripheral that might be a # Sphero robot # @return {Promise<Boolean>} resolved with an object containing the robot's # services, indexed by type; null if the peripheral does not have all the # services of a Sphero robot @_bleGetServices: (peripheral) -> new Promise (resolve, reject) => uuidList = Object.getOwnPropertyNames @_serviceUuids peripheral.discoverServices uuidList, (error, list) => if error reject error peripheral.disconnect() return resolve @_extractBleObjects(@_serviceUuids, list) # Extracts Bluetooth LE objects by their UUIDs from a list of objects. # # @param {Object<String, String>} uuids maps the desired UUIDs to # developer-friendly object names # @param {Array<Object>} list the list that contains the desired Bluetooth # objects # @return {Object?} the desired Bluetooth LE objects, indexed by name; null # if the # list of services does not include all the robot's services @_extractBleObjects: (uuids, list) -> objects = {} for object in list if name = uuids[object.uuid] objects[name] = object for own uuid, name of uuids return null unless objects[name] objects # Discovers the characteristics of a robot's Bluetooth LE services. # # @param {Peripheral} periperheral the Bluetooth LE peripheral whose # characteristics are getting discovered # @param {Object<String, Service>} services maps developer-friendly service # names to the Bluetooth LE services whose characteristics shall be # discovered # @return {Promise<Object?<String, Object<String, Characteristic>>>} resolves # with a mapping from developer-friendly service and characteristic names # to Bluetooth LE characteristics; the mapping may be null if any of the # services is missing a characteristic @_bleGetCharacteristics: (peripheral, services) -> promiseList = for name, service of services @_bleGetServiceCharacteristics peripheral, name, service Promise.all(promiseList).then (resolutions) -> result = {} for resolution in resolutions name = resolution[0] characteristics = resolution[1] if characteristics is null return null result[name] = characteristics result # Discovers the characteristics of a Bluetooth LE service. # # @param {Peripheral} periperheral the Bluetooth LE peripheral whose # characteristics are getting discovered # @param {Service} service the service whose characteristics are getting # discovered # @param {String} developer-friendly name for the service whose # characteristics are getting discovered # @return {Promise<Array<String, Object<String, Characteristic>>} resolves # with a 2-element array; the first element is the service's # developer-friendly name, and the second element maps developer-friendly # characteristic names to Bluetooth LE characteristics; the second element # may be null if any of the desired characteristics is missing @_bleGetServiceCharacteristics: (peripheral, name, service) -> new Promise (resolve, reject) => uuids = @_characteristicUuids[name] uuidList = Object.getOwnPropertyNames uuids service.discoverCharacteristics uuidList, (error, list) => if error peripheral.disconnect() reject error return resolve [name, @_extractBleObjects(uuids, list)] # @return {Object<String, String>} UUIDs for Sphero BLE services @_serviceUuids: '22bb746f2ba075542d6f726568705327': 'robot' '22bb746f2bb075542d6f726568705327': 'radio' # @return {Object<String, String>} UUIDs for the interesting characteristics # of the radio service @_characteristicUuids: radio: '22bb746f2bb275542d6f726568705327': 'txPower' '22bb746f2bb775542d6f726568705327': 'deepSleep' '22bb746f2bbd75542d6f726568705327': 'antiDos' '22bb746f2bbe75542d6f726568705327': 'antiDosTimeout' '22bb746f2bbf75542d6f726568705327': 'wakeCpu' robot: '22bb746f2ba175542d6f726568705327': 'commands' '22bb746f2ba675542d6f726568705327': 'responses' # @return {Array<String, Buffer>} the sequence of data that must be written # to the robot's radio characteristics to activate its serial connection @_activationSequence: [ ['antiDos', new Buffer("011i3")], ['txPower', new Buffer("\x07")], ['wakeCpu', new Buffer("\x01")], ] module.exports = BleChannel