DetectorGraph  2.0
robotlocalization.cpp
Go to the documentation of this file.
1 // Copyright 2017 Nest Labs, Inc.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 #include "graph.hpp"
16 #include "detector.hpp"
17 #include "graphanalyzer.hpp"
18 #include "processorcontainer.hpp"
19 #include "lag.hpp"
20 
21 #include <iostream>
22 #include <cmath>
23 #include <algorithm>
24 #include <sstream>
25 #include <string>
26 #include <Eigen/Dense>
27 
28 using namespace std;
29 using namespace DetectorGraph;
30 using namespace Eigen;
31 
32 /**
33  * @file robotlocalization.cpp
34  * @brief A localization system for a mobile robot with Lag-based feedback loop.
35  *
36  * @section ex-rl-intro Introduction
37  * Back in the 2000s all [_citation needed_] robots were _differential wheeled
38  * robots_ [1] and localization with sensor fusion was a popular problem.
39  * This example shows a simple solution to that problem using DetectorGraph.
40  * It uses an Extended Kalman Filter [2] to continuously predict and correct
41  * the robot's Pose (and associated uncertainty) from two information sources:
42  * the input to the wheels and a GPS-like system.
43  *
44  * @section ex-rl-fpvslag FuturePublisher vs. Lag<T>
45  * This localization algorithm depends on a feedback-loop in that the output of
46  * one graph evaluation (i.e. `LocalizationBelief`) is used as an input for the
47  * next one (i.e. `Lagged<LocalizationBelief>`):
48  @snippetlineno robotlocalization.cpp KalmanPoseCorrector Feedback Loop
49  * Note that in this graph the TopicState where the feedback loop closes is a
50  * legitimate output in itself and it's very likely that new Detectors in the
51  * graph would subscribe to it. In cases like this the use of
52  * DetectorGraph::Lag is preferred as it is more extensible and it preserves
53  * the normal TopicState guarantee for the TopicState in question as well as it
54  * makes the Lagged version of the TopicState clearly documented and also
55  * available. Lag allows even for detectors that subscribes to both the immediate
56  * and the Lagged version of a TopicState - this could be useful for things like
57  * differentiation etc.
58  *
59  * @section ex-rl-conf-topicstates Configuration TopicStates
60  * It also shows how TopicStates can be used for static/configuration data
61  * (e.g. `RobotConfig`). This allows for easy dependency tracking, visualization
62  * and testing at pretty much no runtime cost.
63  *
64  * @section ex-rl-localization Localization Algorithm
65  * Regarding the Localization algorithm itself, it's mostly taken from [3]
66  * except for the correction model where this example uses a different input
67  * (i.e. `GPSPosition`) with much simpler transfer function to the Pose vector.
68  *
69  * @section ex-rl-arch Architecture
70  * The graph below shows the relationships between the topics (rectangles) and
71  * detectors (ellipses). Note that this graph can be automatically generated
72  * for any instance of DetectorGraph::Graph using DetectorGraph::GraphAnalyzer.
73  * @dot "RobotBrain"
74 digraph GraphAnalyzer {
75  rankdir = "LR";
76  node[fontname=Helvetica];
77  size="12,5";
78 
79  "RobotConfig" [label="0:RobotConfig",style=filled, shape=box, color=lightblue];
80  "RobotConfig" -> "ExtendedKalmanPosePredictor";
81  "InitialPose" [label="1:InitialPose",style=filled, shape=box, color=lightblue];
82  "InitialPose" -> "ExtendedKalmanPosePredictor";
83  "WheelSpeeds" [label="2:WheelSpeeds",style=filled, shape=box, color=lightblue];
84  "WheelSpeeds" -> "ExtendedKalmanPosePredictor";
85  "GPSPosition" [label="3:GPSPosition",style=filled, shape=box, color=lightblue];
86  "GPSPosition" -> "KalmanPoseCorrector";
87  "LaggedLocalizationBelief" [label="4:Lagged<LocalizationBelief>",style=filled, shape=box, color=lightblue];
88  "LaggedLocalizationBelief" -> "ExtendedKalmanPosePredictor";
89  "LaggedLocalizationBelief" -> "KalmanPoseCorrector";
90  "KalmanPoseCorrector" [label="6:KalmanPoseCorrector", color=blue];
91  "KalmanPoseCorrector" -> "LocalizationBelief";
92  "ExtendedKalmanPosePredictor" [label="5:ExtendedKalmanPosePredictor", color=blue];
93  "ExtendedKalmanPosePredictor" -> "LocalizationBelief";
94  "LocalizationBelief" [label="7:LocalizationBelief",style=filled, shape=box, color=red];
95  "LocalizationBelief" -> "LagLocalizationBelief";
96  "LagLocalizationBelief" [label="8:Lag<LocalizationBelief>", color=blue];
97  "LagLocalizationBelief" -> "LaggedLocalizationBelief" [style=dotted, color=red, constraint=false];
98 }
99  * @enddot
100  *
101  *
102  * @section ex-rl-other-notes Other Notes
103  * This example also uses the Eigen library [4] for matrix/vector arithmetic
104  * and linear-algebraic operations.
105  *
106  * Note that this entire algorithm is contained in a single file for the sake
107  * of unity as an example. In real-world scenarios the suggested pattern is to
108  * split the code into:
109  *
110  @verbatim
111  detectorgraph/
112  include/
113  robotbrain.hpp (RobotBrain header)
114  src/
115  robotbrain.hpp (RobotBrain implementation)
116  detectors/
117  include/
118  ExtendedKalmanPosePredictor.hpp
119  KalmanPoseCorrector.hpp
120  src/
121  ExtendedKalmanPosePredictor.cpp
122  KalmanPoseCorrector.cpp
123  topicstates/
124  include/
125  RobotConfig.hpp
126  InitialPose.hpp
127  WheelSpeeds.hpp
128  LocalizationBelief.hpp
129  GPSPosition.hpp
130 @endverbatim
131  *
132  * @section ex-rl-refs References
133  * - [1] Differential Wheeled Robots - https://en.wikipedia.org/wiki/Differential_wheeled_robot
134  * - [2] Extended Kalman Filter - https://en.wikipedia.org/wiki/Extended_Kalman_filter
135  * - [3] EKF applied to Mobile Robot’s Localization (Section 2.4.4, page 17) - http://cpscotti.com/pdf/pfc_scotti.pdf
136  * - [4] Eigen - http://eigen.tuxfamily.org/dox/index.html
137  */
138 
139 /// @cond DO_NOT_DOCUMENT
140 
141 // Contains the configuration parameters of this differential mobile Robot
142 // or vehicle.
143 struct RobotConfig : public TopicState
144 {
145  RobotConfig() : r(), b(), e() {}
146  RobotConfig(double aR, double aB, double aE)
147  : r(aR), b(aB), e(aE) {}
148  double r; // wheels r
149  double b; // displacement between wheels 2*l (l=d(wheel,center))
150 
151  double e; // relative wheel slippage/error
152 };
153 
154 struct InitialPose : public TopicState
155 {
156  InitialPose() : timestampMs(0), pose(Vector3d::Zero()) {}
157  InitialPose(Vector3d aPose) : timestampMs(0), pose(aPose) {}
158  InitialPose(uint64_t aTimestampMs, Vector3d aPose) : timestampMs(aTimestampMs), pose(aPose) {}
159  uint64_t timestampMs;
160  Vector3d pose;
161 };
162 
163 // The measured (via encoders or other odometry method) wheel speeds
164 struct WheelSpeeds : public TopicState
165 {
166  WheelSpeeds() : timestampMs(), phi(Vector2d::Zero()) {}
167  WheelSpeeds(uint64_t ts, Vector2d aPhi) : timestampMs(ts), phi(aPhi) {}
168  WheelSpeeds(uint64_t ts, double r, double l) : timestampMs(ts), phi(r, l) {}
169  uint64_t timestampMs;
170 
171  // phi_r, phi_l in radians
172  Vector2d phi;
173 };
174 
175 //! [TopicStates Composition Example]
176 struct KalmanState
177 {
178  KalmanState() : pose(Vector3d::Zero()), error(Matrix3d::Zero()) {}
179  KalmanState(Vector3d aPose, Matrix3d aError) : pose(aPose), error(aError) {}
180  Vector3d pose;
181  Matrix3d error;
182 };
183 
184 //! [Mutually Atomic Variables]
185 struct LocalizationBelief : public TopicState
186 {
187  LocalizationBelief() : timestampMs(), state() {}
188  LocalizationBelief(uint64_t ts, const KalmanState& aState) : timestampMs(ts), state(aState) {}
189  LocalizationBelief(uint64_t ts, const Vector3d& aPose, const Matrix3d& aError) : timestampMs(ts), state(aPose, aError) {}
190  uint64_t timestampMs;
191  KalmanState state;
192 };
193 //! [Mutually Atomic Variables]
194 
195 struct GPSPosition : public TopicState
196 {
197  GPSPosition() : timestampMs(), state() {}
198  GPSPosition(uint64_t ts, const KalmanState& aState) : timestampMs(ts), state(aState) {}
199  uint64_t timestampMs;
200  KalmanState state;
201 };
202 //! [TopicStates Composition Example]
203 
204 double WrapAngle(double th)
205 {
206  // remainder requires C++11
207  return std::remainder(th, 2. * M_PI);
208 }
209 
210 class ExtendedKalmanPosePredictor : public Detector,
211  public SubscriberInterface<RobotConfig>,
212  public SubscriberInterface<InitialPose>,
213  public SubscriberInterface< Lagged<LocalizationBelief> >,
214  public SubscriberInterface<WheelSpeeds>,
215  public Publisher<LocalizationBelief>
216 {
217 public:
218  ExtendedKalmanPosePredictor(Graph* graph) : Detector(graph), mCurrentBelief(), mConfig()
219  {
220  Subscribe<RobotConfig>(this);
221  Subscribe<InitialPose>(this);
222  Subscribe< Lagged<LocalizationBelief> >(this);
223  Subscribe<WheelSpeeds>(this);
224  SetupPublishing<LocalizationBelief>(this);
225  }
226 
227  virtual void Evaluate(const RobotConfig& aConfig)
228  {
229  mConfig = aConfig;
230  }
231 
232  virtual void Evaluate(const InitialPose& aInitialPose)
233  {
234  mCurrentBelief.state = KalmanState(aInitialPose.pose, Matrix3d::Zero());
235  mCurrentBelief.timestampMs = aInitialPose.timestampMs;
236  }
237 
238  virtual void Evaluate(const Lagged<LocalizationBelief>& aLaggedBelief)
239  {
240  mCurrentBelief.state = aLaggedBelief.data.state;
241  mCurrentBelief.timestampMs = aLaggedBelief.data.timestampMs;
242  }
243 
244  virtual void Evaluate(const WheelSpeeds& aWheelSpeeds)
245  {
246  // [\Delta S_{r}, \Delta S_{l}] from phi
247  Vector2d wheelLinearSpeed = aWheelSpeeds.phi * mConfig.r;
248 
249  // Eq. 2.36
250  double d_th = (wheelLinearSpeed.x() - wheelLinearSpeed.y()) / mConfig.b;
251  // Eq. 2.37
252  double d_travel = (wheelLinearSpeed.x() + wheelLinearSpeed.y()) / 2.;
253 
254  double theta = mCurrentBelief.state.pose.z();
255 
256  // Used on most equations below
257  double th_dth2 = theta + d_th / 2.;
258 
259  // Eq. 2.33 on [3]
260  Vector3d cartesianSpeed;
261  cartesianSpeed << d_travel * cos(th_dth2),
262  d_travel * sin(th_dth2),
263  d_th;
264 
265 
266  // Compute Time Delta
267  double tDelta = (aWheelSpeeds.timestampMs - mCurrentBelief.timestampMs) * 1e-3;
268 
269  // Eq. 2.33 (i.e. 2.23) on [3]
270  mCurrentBelief.state.pose = mCurrentBelief.state.pose + (cartesianSpeed * tDelta);
271  mCurrentBelief.state.pose.z() = WrapAngle(mCurrentBelief.state.pose.z());
272 
273  // Eq. 2.34 on [3]
274  Matrix3d G;
275  G << 1, 0, -d_travel * sin(th_dth2),
276  0, 1, d_travel * cos(th_dth2),
277  0, 0, 1;
278 
279  // Eq. 2.35 on [3]
280  Matrix<double, 3, 2> V;
281  V << 0.5 * cos(th_dth2) - (d_travel / (2. * mConfig.b)) * sin(th_dth2), 0.5 * cos(th_dth2) + (d_travel / (2. * mConfig.b)) * sin(th_dth2),
282  0.5 * sin(th_dth2) + (d_travel / (2. * mConfig.b)) * cos(th_dth2), 0.5 * sin(th_dth2) - (d_travel / (2. * mConfig.b)) * cos(th_dth2),
283  1./mConfig.b, -1./mConfig.b;
284 
285  // Eq. 2.38 on [3]
286  Matrix2d M;
287  M << mConfig.e * abs(wheelLinearSpeed.x()), 0,
288  0, mConfig.e * abs(wheelLinearSpeed.y());
289 
290  // Eq. 2.24 on [3]
291  mCurrentBelief.state.error = G * mCurrentBelief.state.error * G.transpose() + V * M * V.transpose();
292 
293  mCurrentBelief.timestampMs = aWheelSpeeds.timestampMs;
294  Publish(mCurrentBelief);
295  }
296 
297  LocalizationBelief mCurrentBelief;
298  RobotConfig mConfig;
299 };
300 
301 ///! [KalmanPoseCorrector Feedback Loop]
302 class KalmanPoseCorrector : public Detector,
303  public SubscriberInterface< Lagged<LocalizationBelief> >,
304  public SubscriberInterface<GPSPosition>,
305  public Publisher<LocalizationBelief>
306 {
307 public:
308  KalmanPoseCorrector(Graph* graph) : Detector(graph), mCurrentBelief()
309  {
310  Subscribe< Lagged<LocalizationBelief> >(this);
311  Subscribe<GPSPosition>(this);
312  SetupPublishing<LocalizationBelief>(this);
313  }
314 ///! [KalmanPoseCorrector Feedback Loop]
315 
316  virtual void Evaluate(const Lagged<LocalizationBelief>& aLaggedBelief)
317  {
318  mCurrentBelief.state = aLaggedBelief.data.state;
319  mCurrentBelief.timestampMs = aLaggedBelief.data.timestampMs;
320  }
321 
322  virtual void Evaluate(const GPSPosition& aCorrectionInput)
323  {
324  // C = Identity(3)
325 
326  // Eq. 2.15 on [3]
327  Matrix3d K = mCurrentBelief.state.error * (mCurrentBelief.state.error + aCorrectionInput.state.error).inverse();
328 
329  // Eq. 2.13 on [3]
330  mCurrentBelief.state.pose = mCurrentBelief.state.pose + K * (aCorrectionInput.state.pose - mCurrentBelief.state.pose);
331 
332  // Eq. 2.14 on [3]
333  mCurrentBelief.state.error = (Matrix3d::Identity() - K) * mCurrentBelief.state.error;
334 
335  Publish(mCurrentBelief);
336  }
337 
338  LocalizationBelief mCurrentBelief;
339 };
340 
341 //![RobotBrain Static]
342 class RobotBrain : public ProcessorContainer
343 {
344 public:
345  RobotBrain()
346  : mPosePredictor(&mGraph)
347  , mKalmanPoseCorrector(&mGraph)
348  , mBeliefFeedback(&mGraph)
349  {
350  }
351 
352  ExtendedKalmanPosePredictor mPosePredictor;
353  KalmanPoseCorrector mKalmanPoseCorrector;
354  Lag<LocalizationBelief> mBeliefFeedback;
355  //![RobotBrain Static]
356 
357  virtual void ProcessOutput()
358  {
359  Topic<LocalizationBelief>* correctedStateTopic = mGraph.ResolveTopic<LocalizationBelief>();
360  if (correctedStateTopic->HasNewValue())
361  {
362  const LocalizationBelief& belief = correctedStateTopic->GetNewValue();
363  const auto& pose = belief.state.pose;
364  cout << "Pose = [" << pose.x() << ", " << pose.y() << ", " << pose.z() << "], ";
365  const auto& error = belief.state.error.diagonal();
366  cout << "Error = [" << error.x() << ", " << error.y() << ", " << error.z() << "]";
367  cout << endl;
368  }
369  }
370 };
371 
372 int main()
373 {
374  RobotBrain robot;
375 
376  uint64_t kSimStep = 1000;
377  uint64_t dataIndex = 0;
378 
379  robot.ProcessData(RobotConfig(1, 1, 0.1));
380  robot.ProcessData(InitialPose(Vector3d(10.0, 10.0, 0.0)));
381 
382  // Circular Movement
383  // for (int i=0;i<100;i++) robot.ProcessData(WheelSpeeds((dataIndex++)*kSimStep, 1., 0.5));
384 
385  // Fwd, Rotate ~180, Fwd, GPS Update
386  robot.ProcessData(WheelSpeeds((dataIndex++)*kSimStep, 1, 1));
387  robot.ProcessData(WheelSpeeds((dataIndex++)*kSimStep, 1, 1));
388  robot.ProcessData(WheelSpeeds((dataIndex++)*kSimStep, 1, 1));
389  robot.ProcessData(WheelSpeeds((dataIndex++)*kSimStep, 1, 1));
390  robot.ProcessData(WheelSpeeds((dataIndex++)*kSimStep, 1, 1));
391  robot.ProcessData(WheelSpeeds((dataIndex++)*kSimStep, 1, -1));
392  robot.ProcessData(WheelSpeeds((dataIndex++)*kSimStep, 1, -1));
393  robot.ProcessData(WheelSpeeds((dataIndex++)*kSimStep, 1, 1));
394  robot.ProcessData(WheelSpeeds((dataIndex++)*kSimStep, 1, 1));
395  robot.ProcessData(WheelSpeeds((dataIndex++)*kSimStep, 1, 1));
396  robot.ProcessData(WheelSpeeds((dataIndex++)*kSimStep, 1, 1));
397  robot.ProcessData(WheelSpeeds((dataIndex++)*kSimStep, 1, 1));
398  robot.ProcessData(GPSPosition((dataIndex++)*kSimStep,
399  KalmanState(Vector3d(10., 10., 4), // Position
400  Vector3d(0.1,0.1,300000).asDiagonal() // Error
401  )));
402 
403  cout << "---- Done ----" << endl;
404 
405  GraphAnalyzer analyzer(robot.mGraph);
406  analyzer.GenerateDotFile("robot_localization.dot");
407 
408  return 0;
409 }
410 
411 /// @endcond DO_NOT_DOCUMENT
Implements a graph of Topics & Detectors with Input/Output APIs.
Definition: graph.hpp:127
bool HasNewValue() const
Returns true if the new Data is available.
Definition: topic.hpp:166
A Base class for a basic Graph container.
Manage data and its handler.
Definition: topic.hpp:84
A templated TopicState used as the output of Lag.
Definition: lag.hpp:49
const T & GetNewValue() const
Returns reference to the new/latest value in the topic.
Definition: topic.hpp:179
Base struct for topic data types.
Definition: topicstate.hpp:52
Produces a Lagged<T> for T.
Definition: lag.hpp:93
Base class that implements a Publisher behavior.
Definition: publisher.hpp:66
A unit of logic in a DetectorGraph.
Definition: detector.hpp:68
A Pure interface that declares the Subscriber behavior.
Class that provides debugging/diagnostics to a DetectorGraph detector graph.