task_closed_loop_control.cpp
Go to the documentation of this file.
1 /*********************************************************************
2  *
3  * Software License Agreement
4  *
5  * Copyright (c) 2020,
6  * TU Dortmund - Institute of Control Theory and Systems Engineering.
7  * All rights reserved.
8  *
9  * This program is free software: you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation, either version 3 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program. If not, see <https://www.gnu.org/licenses/>.
21  *
22  * Authors: Christoph Rösmann
23  *********************************************************************/
24 
26 #include <corbo-core/console.h>
27 #include <corbo-core/time.h>
28 #include <corbo-core/time_series.h>
30 #include <string>
31 
32 namespace corbo {
33 
35 {
36  // default reference
37  _xreference = std::make_shared<StaticReference>(Eigen::Matrix<double, 1, 1>::Ones());
38  _ureference = std::make_shared<ZeroReference>(1);
39 }
40 
41 void ClosedLoopControlTask::getAvailableSignals(const Environment& environment, SignalTargetInterface& signal_target, const std::string& ns) const
42 {
43  if (environment.getController())
44  {
45  environment.getController()->getAvailableSignals(signal_target);
46 
47  if (environment.getController()->getControlInputDimension() != property::INHERITED)
48  {
49  signal_target.registerMeasurement(ns + "control_input", environment.getController()->getControlInputDimension(), {}, true);
50  }
51  }
52 
53  if (environment.getPlant())
54  {
55  environment.getPlant()->getAvailableSignals(signal_target);
56  if (environment.getPlant()->getOutputDimension() != property::INHERITED)
57  signal_target.registerMeasurement(ns + "plant_output", environment.getPlant()->getOutputDimension());
58  }
59 
60  if (environment.getObserver())
61  {
62  environment.getObserver()->getAvailableSignals(signal_target);
63  if (environment.getObserver()->getStateDimension() != property::INHERITED)
64  signal_target.registerMeasurement(ns + "observed_states", environment.getObserver()->getStateDimension());
65  }
66 
68  {
69  signal_target.registerMeasurement(ns + "comp_states", environment.getObserver()->getStateDimension());
70  }
71 
72  if (_xreference)
73  {
74  signal_target.registerMeasurement(ns + "reference/x", _xreference->getDimension());
75  }
76  if (_ureference)
77  {
78  signal_target.registerMeasurement(ns + "reference/u", _ureference->getDimension());
79  }
80 }
81 
82 void ClosedLoopControlTask::performTask(Environment& environment, SignalTargetInterface* signal_target, std::string* msg, const std::string& ns)
83 {
84  // environment->reset();
85 
86  if (_dt <= 0 && !environment.getController()->supportsAsynchronousControl())
87  {
88  PRINT_ERROR("ClosedLoopControlTask::performTask(): dt <= 0 selected, but current controller does not support asynchronous control.");
89  return;
90  }
91 
92  // initialize variables
93  Time t(0);
94  Time tf(_sim_time);
95  Duration dt(_dt);
96 
97  ControllerInterface* controller = environment.getController().get();
98  PlantInterface* plant = environment.getPlant().get();
99  ObserverInterface* observer = environment.getObserver().get();
100 
101  using ControlVector = Eigen::VectorXd;
102  using StateVector = Eigen::VectorXd;
103  using OutputVector = Eigen::VectorXd;
104 
105  TimeSeries::Ptr u_sequence = std::make_shared<TimeSeries>();
106  TimeSeries::Ptr x_sequence = std::make_shared<TimeSeries>();
107  u_sequence->add(0, ControlVector::Zero(plant->getOutputDimension()));
108 
109  OutputVector y(plant->getOutputDimension());
110  StateVector x(controller->getStateDimension());
111 
112  Eigen::VectorXd xref(_xreference->getDimension());
113  Eigen::VectorXd uref(_ureference->getDimension());
114 
115  if (!verify(environment, msg)) return;
116 
117  // initialize modules
118  if (!controller->initialize(x, *_xreference, *_ureference, dt, t))
119  {
120  PRINT_ERROR("Controller initialization failed.");
121  return;
122  }
123  if (!plant->initialize())
124  {
125  PRINT_ERROR("Plant initialization failed.");
126  return;
127  }
128 
130  {
131  PRINT_ERROR("Compensator initialization failed.");
132  return;
133  }
134 
135  Rate rate(_realtime_sync ? dt : Duration(1e-6)); // wait a small amount also if realtime sync is disabled
136 
137  Time t_measure_x;
138  Time compensator_meas_start;
139  Duration cpu_time(0);
140  Duration cpu_time_cache(0);
141 
142  double comp_dt = 0;
143  double deadtime = 0;
144 
146 
147  std::vector<std::pair<double, Eigen::VectorXd>> useq_predict;
148  if (_time_value_buffer.isEmpty()) _time_value_buffer.setInitialValue(ControlVector::Zero(plant->getOutputDimension()));
149 
150  // perform actual closed-loop task
151  Time t_wall_start = Time::now();
152 
153  while (t <= tf && ok())
154  {
155  Duration last_dt = (t == Time(0)) ? Duration(0) : dt;
156 
157  if (_use_wall_time) t = (Time::now() - t_wall_start).toTime();
158 
159  // publish reference signals
160  if (signal_target)
161  {
162  _xreference->getReference(t, xref);
163  _ureference->getReference(t, uref);
164  signal_target->sendMeasurement(ns + "reference/x", t.toSec(), xref);
165  signal_target->sendMeasurement(ns + "reference/u", t.toSec(), uref);
166  }
167 
168  // plant output
169  if (_use_wall_time) t = (Time::now() - t_wall_start).toTime();
170  if (!plant->output(y, t, signal_target, ns)) PRINT_ERROR("ClosedLoopControl::performTask(): error while retreiving plant output.");
171  if (signal_target) signal_target->sendMeasurement(ns + "plant_output", t.toSec(), y);
172 
173  t_measure_x = t;
174  compensator_meas_start = Time::now();
175 
176  // observer
177  if (_use_wall_time) t = (Time::now() - t_wall_start).toTime();
178  if (!observer->observe(y, x, last_dt, t, signal_target, ns)) PRINT_ERROR("ClosedLoopControl::performTask(): observer error.");
179  if (signal_target) signal_target->sendMeasurement(ns + "observed_states", t.toSec(), x);
180 
181  // compensator (we might want to compensate for long controller CPU times)
182 
184  {
185  comp_dt = (_computation_delay < 0) ? cpu_time.toSec() : _computation_delay;
186  }
187 
189  {
190  _time_value_buffer.getValues(t_measure_x.toSec() - deadtime, comp_dt + deadtime, useq_predict);
191  _compensator.predict(x, useq_predict, comp_dt + deadtime, x);
192 
193  // TODO(roesmann) seems not to work in gui
194  // if (signal_target) signal_target->sendMeasurement("comp_states", t_measure_x.toSec() + comp_dt, x);
195  }
196 
197  // controller
198  if (_use_wall_time) t = (Time::now() - t_wall_start).toTime();
199  if (!controller->step(x, *_xreference, *_ureference, last_dt, t, u_sequence, x_sequence, signal_target, nullptr, nullptr, nullptr, ns))
200  {
201  PRINT_ERROR("ClosedLoopControl::performTask(): controller error.");
202  u_sequence->clear();
203  u_sequence->add(t.toSec(), Eigen::VectorXd::Zero(controller->getControlInputDimension()));
204  }
205 
206  // if dt<=0 -> inherit from controller (asynchronous control mode)
207  if (_dt <= 0)
208  {
209  if (controller->getControlDuration() >= _min_dt)
210  {
211  if (controller->getControlDuration() <= _max_dt)
212  {
213  dt.fromSec(controller->getControlDuration());
214  rate.updateCycleTime(dt);
215  }
216  else
217  {
219  "ClosedLoopControl::performTask(): asychnronous control mode: controller returned dt>_max_dt. Setting dt=dt_max. This "
220  "warning is printed once.");
221  dt.fromSec(_max_dt);
222  }
223  }
224  else
225  {
227  "ClosedLoopControl::performTask(): asychnronous control mode: controller returned dt<_min_dt. Setting dt=dt_min. This warning is "
228  "printed once.");
229  dt.fromSec(_min_dt);
230  }
231  }
232 
233  // control plant
234  if (_use_wall_time) t = (Time::now() - t_wall_start).toTime();
235  plant->control(u_sequence, x_sequence, dt, t, signal_target, ns);
236 
238  {
239  _time_value_buffer.appendValues(t.toSec(), u_sequence->getValuesMap(0));
240  }
241 
242  // save cpu time required for the controller step (used for compensation in the next step)
243  cpu_time = Time::now() - compensator_meas_start;
244  cpu_time_cache = cpu_time;
245 
247  {
248  if (_computation_delay_filter) cpu_time.fromSec(_computation_delay_filter->filter(t.toSec(), cpu_time.toSec()));
249  }
250 
251  if (signal_target)
252  {
253  if (_use_wall_time) t.fromSec(t_measure_x.toSec() + deadtime + cpu_time_cache.toSec()); // TODO(kraemer) correct?
254  controller->sendSignals(t.toSec(), *signal_target, ns);
255  if (u_sequence && !u_sequence->isEmpty()) signal_target->sendMeasurement(ns + "control_input", t.toSec(), u_sequence->getValuesMap(0));
256  }
257 
258  if (!rate.sleep() && _realtime_sync)
259  {
260  PRINT_WARNING("ClosedLoopControlTask(): rate exceeded (" << rate.lastCycleTime().toSec() << "s/" << dt << "s).");
261  }
262  if (!_use_wall_time) t += dt;
263  }
264 
265  plant->stop();
266 }
267 
268 bool ClosedLoopControlTask::verify(const Environment& environment, std::string* msg) const
269 {
270  bool ret_val = true;
271 
272  if (msg) msg->clear();
273 
274  // check if all objects are set
275  if (!_xreference)
276  {
277  ret_val = false;
278  if (msg) *msg += "State reference trajectory not specified for ClosedLoopControlTask\n";
279  }
280 
281  // check if all objects are set
282  if (!_ureference)
283  {
284  ret_val = false;
285  if (msg) *msg += "Control reference trajectory not specified for ClosedLoopControlTask\n";
286  }
287 
288  // verify environment
289  std::string environment_msg;
290  ret_val = ret_val && environment.verify(&environment_msg);
291  if (msg) *msg += environment_msg;
292  if (ret_val == false) return false; // we need all objects in environment allocated!
293 
294  // check reference dimension
295  if (environment.getController()->getStateDimension() != _xreference->getDimension())
296  {
297  ret_val = false;
298  if (msg)
299  *msg += "State reference trajectory dimension (" + std::to_string(_xreference->getDimension()) +
300  ") does not match controller state dimension (" + std::to_string(environment.getController()->getStateDimension()) + ").\n";
301  }
302  if (environment.getController()->getControlInputDimension() != _ureference->getDimension())
303  {
304  ret_val = false;
305  if (msg)
306  *msg += "Control reference trajectory dimension (" + std::to_string(_ureference->getDimension()) +
307  ") does not match controller control input dimension (" +
308  std::to_string(environment.getController()->getControlInputDimension()) + ").\n";
309  }
310 
311  if (environment.getPlant()->requiresFutureControls() && !environment.getController()->providesFutureControls())
312  {
313  ret_val = false;
314  if (msg) *msg += "Controller does not support control sequences, that are required by the plant";
315  }
316 
317  if (environment.getPlant()->requiresFutureStates() && !environment.getController()->providesFutureStates())
318  {
319  ret_val = false;
320  if (msg) *msg += "Controller does not support state sequences, that are required by the plant";
321  }
322 
323  return ret_val;
324 }
325 
327 {
330 }
331 
332 #ifdef MESSAGE_SUPPORT
333 void ClosedLoopControlTask::toMessage(corbo::messages::ClosedLoopControlTask& message) const
334 {
335  message.set_sim_time(_sim_time);
336  message.set_dt(_dt);
337  message.set_realtime_sync(_realtime_sync);
338  message.set_use_wall_time(_use_wall_time);
339  message.set_min_dt(_min_dt);
340  message.set_max_dt(_max_dt);
341  if (_xreference) _xreference->toMessage(*message.mutable_xreference());
342  if (_ureference) _ureference->toMessage(*message.mutable_ureference());
343 
344  message.set_compensate_dead_time(_compensate_dead_time);
345  message.set_compensate_cpu_time(_compensate_cpu_time);
346  message.set_computation_delay(_computation_delay);
347 
348  _compensator.toMessage(*message.mutable_compensator());
349  if (_computation_delay_filter) _computation_delay_filter->toMessage(*message.mutable_computation_delay_filter());
350 }
351 void ClosedLoopControlTask::fromMessage(const corbo::messages::ClosedLoopControlTask& message, std::stringstream* issues)
352 {
353  _sim_time = message.sim_time();
354  _dt = message.dt();
355  _realtime_sync = message.realtime_sync();
356  _use_wall_time = message.use_wall_time();
357  _min_dt = message.min_dt();
358  _max_dt = message.max_dt();
359 
360  // xreference
361  _xreference.reset();
362  if (message.has_xreference())
363  {
364  // construct object
365  std::string type;
366  if (util::get_oneof_field_type(message.xreference(), "reference", type, false))
367  {
369  // import parameters
370  if (xreference)
371  {
372  xreference->fromMessage(message.xreference(), issues);
373  setStateReference(xreference);
374  }
375  }
376  }
377 
378  // ureference
379  _ureference.reset();
380  if (message.has_ureference())
381  {
382  // construct object
383  std::string type;
384  if (util::get_oneof_field_type(message.ureference(), "reference", type, false))
385  {
387  // import parameters
388  if (ureference)
389  {
390  ureference->fromMessage(message.ureference(), issues);
391  setControlReference(ureference);
392  }
393  }
394  }
395 
396  // compensator
397  _computation_delay = message.computation_delay();
398  _compensate_dead_time = message.compensate_dead_time();
399  _compensate_cpu_time = message.compensate_cpu_time();
400 
401  _compensator.fromMessage(message.compensator(), issues);
402 
404 
405  if (message.has_computation_delay_filter() && !message.computation_delay_filter().has_no_filter())
406  {
407  // construct object
408  std::string type;
409  if (util::get_oneof_field_type(message.computation_delay_filter(), "filter", type, false))
410  {
412  // import parameters
413  if (filter)
414  {
415  filter->fromMessage(message.computation_delay_filter(), issues);
416  _computation_delay_filter = filter;
417  }
418  }
419  }
420 }
421 #endif
422 
423 } // namespace corbo
PRINT_WARNING_ONCE
#define PRINT_WARNING_ONCE(msg)
Print msg-stream only once.
Definition: console.h:149
corbo::ClosedLoopControlTask::getAvailableSignals
void getAvailableSignals(const Environment &environment, SignalTargetInterface &signal_target, const std::string &ns="") const override
Retrieve available signals from the task.
Definition: task_closed_loop_control.cpp:63
corbo::Time::now
static Time now()
Retrieve current system time.
Definition: time.h:297
corbo::ClosedLoopControlTask::_min_dt
double _min_dt
Definition: task_closed_loop_control.h:144
corbo::ClosedLoopControlTask::_max_dt
double _max_dt
Definition: task_closed_loop_control.h:145
PRINT_WARNING
#define PRINT_WARNING(msg)
Print msg-stream.
Definition: console.h:145
corbo::ClosedLoopControlTask::_computation_delay
double _computation_delay
Definition: task_closed_loop_control.h:150
corbo
Definition: communication/include/corbo-communication/utilities.h:37
corbo::TimeValueBuffer::getValues
void getValues(double ts, double dt, std::vector< std::pair< double, Eigen::VectorXd >> &useq_out)
Compute the delayed values.
Definition: time_value_buffer.cpp:52
corbo::TimeValueBuffer::setInitialValue
void setInitialValue(const Eigen::Ref< const Eigen::VectorXd > &uinit)
Specify initial value.
Definition: time_value_buffer.h:123
corbo::Time::fromSec
void fromSec(double t)
Set time stamp from seconds.
Definition: time.h:305
corbo::TimeValueBuffer::appendValues
void appendValues(double t, const Eigen::Ref< const Eigen::VectorXd > &u)
Definition: time_value_buffer.cpp:111
corbo::ClosedLoopControlTask::_ureference
ReferenceTrajectoryInterface::Ptr _ureference
Definition: task_closed_loop_control.h:148
corbo::OneStepPredictor::initialize
bool initialize()
initialize the predictor
Definition: one_step_predictor.cpp:55
corbo::TimeValueBuffer::reset
void reset()
Reset internal buffer.
Definition: time_value_buffer.h:128
console.h
corbo::TimeValueBuffer::isEmpty
bool isEmpty() const
Definition: time_value_buffer.h:125
corbo::ClosedLoopControlTask::_use_wall_time
bool _use_wall_time
Definition: task_closed_loop_control.h:141
corbo::ClosedLoopControlTask::_compensate_cpu_time
bool _compensate_cpu_time
Definition: task_closed_loop_control.h:143
corbo::Factory::instance
static Factory & instance()
< Retrieve static instance of the factory
Definition: factory.h:116
corbo::OneStepPredictor::predict
void predict(const Eigen::Ref< const StateVector > &x0, std::vector< std::pair< double, ControlVector >> u_seq, double dt, Eigen::Ref< StateVector > x1)
Predict x1 using t0, x0, u and dt (alias between x0 and x1 allowed)
Definition: one_step_predictor.cpp:64
task_closed_loop_control.h
time.h
time_series.h
x
Scalar * x
Definition: level1_cplx_impl.h:89
corbo::ClosedLoopControlTask::_xreference
ReferenceTrajectoryInterface::Ptr _xreference
Definition: task_closed_loop_control.h:147
corbo::property::INHERITED
constexpr const int INHERITED
Inherit property.
Definition: core/include/corbo-core/types.h:84
corbo::ClosedLoopControlTask::ClosedLoopControlTask
ClosedLoopControlTask()
Default constructor.
Definition: task_closed_loop_control.cpp:56
corbo::ReferenceTrajectoryInterface::Ptr
std::shared_ptr< ReferenceTrajectoryInterface > Ptr
Definition: reference_trajectory.h:107
y
Scalar * y
Definition: level1_cplx_impl.h:102
corbo::FilterInterface::Ptr
std::shared_ptr< FilterInterface > Ptr
Definition: filter_interface.h:97
utilities.h
corbo::Factory::create
std::shared_ptr< Derived > create(const std::string &name, bool print_error=true) const
Create a shared instance of the desired object.
Definition: factory.h:137
corbo::ClosedLoopControlTask::_realtime_sync
bool _realtime_sync
Definition: task_closed_loop_control.h:140
corbo::ClosedLoopControlTask::_time_value_buffer
TimeValueBuffer _time_value_buffer
Definition: task_closed_loop_control.h:152
corbo::ClosedLoopControlTask::setControlReference
void setControlReference(ReferenceTrajectoryInterface::Ptr ureference)
Set control input reference trajectory.
Definition: task_closed_loop_control.h:112
corbo::ClosedLoopControlTask::_dt
double _dt
Definition: task_closed_loop_control.h:139
corbo::ClosedLoopControlTask::setStateReference
void setStateReference(ReferenceTrajectoryInterface::Ptr xreference)
Set state reference trajectory.
Definition: task_closed_loop_control.h:110
Eigen::Matrix
The matrix class, also used for vectors and row-vectors.
Definition: Matrix.h:178
corbo::OneStepPredictor::getDeadTime
double getDeadTime()
Definition: one_step_predictor.cpp:53
corbo::ClosedLoopControlTask::_computation_delay_filter
FilterInterface::Ptr _computation_delay_filter
Definition: task_closed_loop_control.h:153
corbo::TimeSeries::Ptr
std::shared_ptr< TimeSeries > Ptr
Definition: time_series.h:108
corbo::ok
bool ok()
global method to check whether to proceed or cancel the current action
Definition: global.cpp:54
corbo::ClosedLoopControlTask::verify
bool verify(const Environment &environment, std::string *msg=nullptr) const override
Check if the environment and other settings satisfy all requirements for the given task.
Definition: task_closed_loop_control.cpp:290
PRINT_ERROR
#define PRINT_ERROR(msg)
Print msg-stream as error msg.
Definition: console.h:173
corbo::ClosedLoopControlTask::performTask
void performTask(Environment &environment, SignalTargetInterface *signal_target=nullptr, std::string *msg=nullptr, const std::string &ns="") override
Perform task.
Definition: task_closed_loop_control.cpp:104
corbo::ClosedLoopControlTask::_compensator
OneStepPredictor _compensator
Definition: task_closed_loop_control.h:151
corbo::ClosedLoopControlTask::_sim_time
double _sim_time
Definition: task_closed_loop_control.h:138
corbo::ClosedLoopControlTask::_compensate_dead_time
bool _compensate_dead_time
Definition: task_closed_loop_control.h:142
corbo::ClosedLoopControlTask::reset
void reset() override
Reset task state.
Definition: task_closed_loop_control.cpp:348


control_box_rst
Author(s): Christoph Rösmann
autogenerated on Wed Mar 2 2022 00:06:33