ardrone-autonomy
Version:
Building blocks for autonomous flying an AR.Drone.
146 lines (118 loc) • 4.98 kB
JavaScript
var sylvester = require('sylvester');
var util = require('util');
var Matrix = sylvester.Matrix;
var Vector = sylvester.Vector;
EKF.DELTA_T = 1 / 15; // In demo mode, 15 navdata per second
module.exports = EKF;
function EKF(options) {
options = options || {};
this._options = options;
this._delta_t = options.delta_t || EKF.DELTA_T;
this.reset();
}
EKF.prototype.state = function() {
return this._state;
}
EKF.prototype.confidence = function() {
return this._sigma;
}
EKF.prototype.reset = function() {
this._state = this._options.state || {x: 0, y: 0, yaw: 0};
this._sigma = Matrix.I(3);
this._q = Matrix.Diagonal([0.0003, 0.0003, 0.0001]);
this._r = Matrix.Diagonal([0.3, 0.3, 0.3]);
this._last_yaw = null;
}
EKF.prototype.predict = function(data) {
var pitch = data.demo.rotation.pitch.toRad()
, roll = data.demo.rotation.roll.toRad()
, yaw = normAngle(data.demo.rotation.yaw.toRad())
, vx = data.demo.velocity.x / 1000 //We want m/s instead of mm/s
, vy = data.demo.velocity.y / 1000
, dt = this._delta_t
;
// We are not interested by the absolute yaw, but the yaw motion,
// so we need at least a prior value to get started.
if (this._last_yaw == null) {
this._last_yaw = yaw;
return;
}
// Compute the odometry by integrating the motion over delta_t
var o = {dx: vx * dt, dy: vy * dt, dyaw: yaw - this._last_yaw};
this._last_yaw = yaw;
// Update the state estimate
var state = this._state;
state.x = state.x + o.dx * Math.cos(state.yaw) - o.dy * Math.sin(state.yaw);
state.y = state.y + o.dx * Math.sin(state.yaw) + o.dy * Math.cos(state.yaw);
state.yaw = state.yaw + o.dyaw;
// Normalize the yaw value
state.yaw = Math.atan2(Math.sin(state.yaw),Math.cos(state.yaw));
// Compute the G term (due to the Taylor approximation to linearize the function).
var G = $M(
[[1, 0, -1 * Math.sin(state.yaw) * o.dx - Math.cos(state.yaw) * o.dy],
[0, 1, Math.cos(state.yaw) * o.dx - Math.sin(state.yaw) * o.dy],
[0, 0, 1]]
);
// Compute the new sigma
this._sigma = G.multiply(this._sigma).multiply(G.transpose()).add(this._q);
}
/*
* measure.x: x-position of marker in drone's xy-coordinate system (independant of roll, pitch)
* measure.y: y-position of marker in drone's xy-coordinate system (independant of roll, pitch)
* measure.yaw: yaw rotation of marker, in drone's xy-coordinate system (independant of roll, pitch)
*
* pose.x: x-position of marker in world-coordinate system
* pose.y: y-position of marker in world-coordinate system
* pose.yaw: yaw-rotation of marker in world-coordinate system
*/
EKF.prototype.correct = function(measure, pose) {
// Compute expected measurement given our current state and the marker pose
var state = this._state;
var psi = state.yaw;
this._s = {x: state.x, y: state.y, yaw: state.yaw};
// Normalized the measure yaw
measure.yaw = normAngle(measure.yaw);
this._m = {x: measure.x, y: measure.y, yaw: measure.yaw};
var z1 = Math.cos(psi) * (pose.x - state.x) + Math.sin(psi) * (pose.y - state.y);
var z2 = -1 * Math.sin(psi) * (pose.x - state.x) + Math.cos(psi) * (pose.y - state.y);
var z3 = pose.yaw - psi;
this._z = {x: z1, y: z2, yaw: z3};
// Compute the error
var e1 = measure.x - z1;
var e2 = measure.y - z2;
var e3 = measure.yaw - z3;
this._e = {x: e1, y: e2, yaw: e3};
// Compute the H term
var H = $M([[ -Math.cos(psi), -Math.sin(psi), Math.sin(psi) * (state.x - pose.x) - Math.cos(psi) * (state.y - pose.y)],
[ Math.sin(psi), -Math.cos(psi), Math.cos(psi) * (state.x - pose.x) + Math.sin(psi) * (state.y - pose.y)],
[ 0, 0, -1]]);
// Compute the Kalman Gain
var Ht = H.transpose();
var K = this._sigma.multiply(Ht).multiply(H.multiply(this._sigma).multiply(Ht).add(this._r).inverse())
// Correct the pose estimate
var err = $V([e1, e2, e3]);
var c = K . multiply(err);
state.x = state.x + c.e(1);
state.y = state.y + c.e(2);
// TODO - This does not work, need more investigation.
// In the meanwhile, we don't correct yaw based on observation.
// state.yaw = state.yaw + c.e(3);
this._sigma = Matrix.I(3).subtract(K.multiply(H)).multiply(this._sigma);
};
function normAngle(rad) {
while (rad > Math.PI) { rad -= 2 * Math.PI;}
while (rad < -Math.PI) { rad += 2 * Math.PI;}
return rad;
}
/** Converts numeric degrees to radians */
if (typeof(Number.prototype.toRad) === "undefined") {
Number.prototype.toRad = function() {
return this * Math.PI / 180;
}
}
/** Converts radians to numeric dregrees */
if (typeof(Number.prototype.toDeg) === "undefined") {
Number.prototype.toDeg = function() {
return this * 180 / Math.PI;
}
}