-
Notifications
You must be signed in to change notification settings - Fork 334
/
altitude_fuser.py
executable file
·126 lines (83 loc) · 2.92 KB
/
altitude_fuser.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
#!/usr/bin/env python3
'''
altitude_fuser.py - Sonar / Barometer fusion example using TinyEKF.
We model a single state variable, altitude above sea level (ASL) in
centimeters. This is obtained by fusing the barometer pressure in Pascals and
sonar above-ground level (ASL) in centimters.
Copyright (C) 2016 Simon D. Levy
MIT License
'''
import numpy as np
import matplotlib.pyplot as plt
from tinyekf import EKF
from math import sin, pi
# for plotting
BARO_RANGE = 20
SONAR_RANGE = 200
BARO_BASELINE = 97420
# ground-truth AGL to sonar measurement, empirically determined:
# see http://diydrones.com/profiles/blogs/altitude-hold-with-mb1242-sonar
def sonarfun(agl):
return 0.933 * agl - 2.894
# Convert ASL cm to Pascals: see
# http://www.engineeringtoolbox.com/air-altitude-pressure-d_462.html
def asl2baro(asl):
return 101325 * pow((1 - 2.25577e-7 * asl), 5.25588)
# Convert Pascals to cm ASL
def baro2asl(pa):
return (1.0 - pow(pa / 101325.0, 0.190295)) * 4433000.0
if __name__ == '__main__':
LOOPSIZE = 100
count = 0
N = 100
# One state (ASL), two measurements (baro, sonar), with
# larger-than-usual measurement covariance noise to help with sonar
# blips.
P = np.eye(1) * 1e-1
Q = np.eye(1) * 1e-4
R = np.eye(2) * 5e-1
ekf = EKF(P)
baro = np.zeros(N)
sonar = np.zeros(N)
fused = np.zeros(N)
for k in range(N):
# Model up-and-down motion with a sine wave
sine = sin((k % LOOPSIZE)/LOOPSIZE * 2 * pi)
baro[k] = BARO_BASELINE + sine * BARO_RANGE
# Add noise to simulated sonar at random intervals
sonar[k] = (sonarfun(50 * (1 - sine)) +
(50 if np.random.rand() > 0.9 else 0))
# Observation is baro and sonar
z = baro[k], sonar[k]
# State-transition function f is identity
fx = ekf.get()
# So first derivative of f is identity matrix
F = np.eye(1)
# State value is ASL
asl = ekf.get()[0]
# Convert ASL cm to sonar AGL cm by subtracting off ASL baseline from
# baro
s = sonarfun(asl - baro2asl(BARO_BASELINE))
# Convert ASL cm to Pascals: see
# http://www.engineeringtoolbox.com/air-altitude-pressure-d_462.html
b = asl2baro(asl)
hx = np.array([b, s])
# First derivative of nonlinear baro-measurement function
# Used http://www.wolframalpha.com
dpdx = -0.120131 * pow((1 - 2.2577e-7 * asl), 4.25588)
# Sonar response is linear, so derivative is constant
dsdx = 0.933
H = np.array([[dpdx], [dsdx]])
ekf.predict(fx, F, Q)
ekf.update(z, hx, H, R)
fused[k] = ekf.get()[0]
plt.subplot(3, 1, 1)
plt.plot(fused, 'r')
plt.ylabel('Fused ASL (cm)')
plt.subplot(3, 1, 2)
plt.plot(baro, 'b')
plt.ylabel('Baro (Pa)')
plt.subplot(3, 1, 3)
plt.plot(sonar, 'g')
plt.ylabel('Sonar ASL (cm)')
plt.show()