/*
* Copyright 2017-2019 Tom Swindell
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
const EventEmitter = require('events')
const CommandDict = require('./commands')
const {
FEATURE_CLASSES,
ARDrone3,
SkyController,
} = require('./features')
/**
* This class handles sending and recieving commands from ARNet based
* devices. It serves as a nice abstraction layer removing the underlying
* protocol architecture, and providing an easy to use interface for
* communicating with Parrot devices.
*
* @class Device
* @public
*
* @hideconstructor
*
* @param {string} uid
* @param {MessageChannel} channel
*/
function Device (__uid, __channel) {
EventEmitter.call(this)
const __self = this
const __messages = new CommandDict()
const __features = {}
let __is_connected = false
let __is_ready = false
/**
* Handle incoming navdata messages, detect connected features, parse
* messages and notify next layer with annotated messages.
*
* @private
* @param {*} mesg
* @param {*} rinfo
*/
const onNavdata = (mesg, rinfo) => {
// If we're in disconnected state, mark as connected and emit.
if (!__is_connected) {
__messages.import(
'common', 'ardrone3', 'skyctrl', 'wifi', 'rc', 'drone_manager',
'mapper', 'debug', 'controller_info', 'animation', 'user_storage',
'rth', 'gimbal', 'battery', 'mediastore', 'precise_home',
)
.then(async () => {
__self.emit('connected')
__is_ready = true
})
.catch(e => __self.emit('error', e))
__is_connected = true
}
// Wait until message dictionary is populated.
if (!__is_ready) return
// Start detecting plugin features.
if (!(mesg.featureId in __features) && mesg.featureId in FEATURE_CLASSES) {
const feature = new FEATURE_CLASSES[mesg.featureId](__self)
__features[mesg.featureId] = feature
console.info('Detected feature:', feature.uid)
feature.init()
.then(() => {
__self.emit('feature:attached', feature)
})
.catch(e => console.error(e))
}
// Lookup message information and annotate message if successful.
const minfo = __messages.resolve(mesg)
// Ignore messages we can't decode.
if (!minfo || !minfo.decode) return
mesg.info = minfo
mesg.path = minfo.path
mesg.params = minfo.decode && minfo.decode(mesg.args)
// Notify navdata received
__self.emit('message', mesg)
}
/**
* Send an ARNet message / command to remote device.
* @method Device#command
* @public
*
* @param {string} message Message type path format: featureId.classId.messageId
* @param {any[]} [args] Arguments for message
*/
const message = function (message, args) {
const minfo = __messages.resolve(message)
return __channel.sendMessage(minfo, Array.prototype.slice.call(arguments, 1))
.then(results => results.map(mesg => {
const minfo = __messages.resolve(mesg)
mesg.path = minfo.path
mesg.params = minfo.decode(mesg.args)
return mesg
}))
}
/**
* Returns the unique id associated with this device.
* @member Device#uid
* @public
*
* @returns {string} Unique Id for this device.
*/
this.uid = __uid
/**
* Returns the MessageChannel associated with this device.
* @member Device#channel
* @public
*
* @returns {MessageChannel}
*/
this.channel = __channel
// Expose Public Interface
this.message = message.bind(this)
this.toJSON = () => ({
uid: __uid,
features: Object.keys(__features).map(k => __features[k].toJSON())
})
// Listen to MessageChannel events.
__channel.on('navdata', onNavdata)
}
Device.prototype = Object.create(EventEmitter.prototype)
module.exports = Device