blob: 018a2f5e9cf4d759ec9990d796886e3325cb9581 [file] [log] [blame]
#include <stddef.h>
#include "aos/common/logging/logging.h"
#include "aos/common/control_loop/Timing.h"
#include "aos/common/messages/RobotState.q.h"
namespace aos {
namespace control_loops {
// TODO(aschuh): Tests.
template <class T, bool has_position>
void ControlLoop<T, has_position>::ZeroOutputs() {
aos::ScopedMessagePtr<OutputType> output =
control_loop_->output.MakeMessage();
Zero(output.get());
output.Send();
}
template <class T, bool has_position>
void ControlLoop<T, has_position>::Iterate() {
// Temporary storage for printing out inputs and outputs.
char state[1024];
// Fetch the latest control loop goal and position. If there is no new
// goal, we will just reuse the old one.
// If there is no goal, we haven't started up fully. It isn't worth
// the added complexity for each loop implementation to handle that case.
control_loop_->goal.FetchLatest();
// TODO(aschuh): Check the age here if we want the loop to stop on old
// goals.
const GoalType *goal = control_loop_->goal.get();
if (goal == NULL) {
LOG(ERROR, "No prior control loop goal.\n");
ZeroOutputs();
return;
}
goal->Print(state, sizeof(state));
LOG(DEBUG, "goal={%s}\n", state);
// Only pass in a position if we got one this cycle.
const PositionType *position = NULL;
// Only fetch the latest position if we have one.
if (has_position) {
// If the position is stale, this is really bad. Try fetching a position
// and check how fresh it is, and then take the appropriate action.
if (control_loop_->position.FetchLatest()) {
position = control_loop_->position.get();
} else {
if (control_loop_->position.get()) {
int msec_age = control_loop_->position.Age().ToMSec();
if (!control_loop_->position.IsNewerThanMS(kPositionTimeoutMs)) {
LOG(ERROR, "Stale position. %d ms > %d ms. Outputs disabled.\n",
msec_age, kPositionTimeoutMs);
ZeroOutputs();
return;
} else {
LOG(ERROR, "Stale position. %d ms\n", msec_age);
}
} else {
LOG(ERROR, "Never had a position.\n");
ZeroOutputs();
return;
}
}
if (position) {
position->Print(state, sizeof(state));
LOG(DEBUG, "position={%s}\n", state);
}
}
bool outputs_enabled = false;
// Check to see if we got a driver station packet recently.
if (aos::robot_state.FetchLatest()) {
outputs_enabled = true;
} else if (aos::robot_state.IsNewerThanMS(kDSPacketTimeoutMs)) {
outputs_enabled = true;
} else {
if (aos::robot_state.get()) {
int msec_age = aos::robot_state.Age().ToMSec();
LOG(ERROR, "Driver Station packet is too old (%d ms).\n", msec_age);
} else {
LOG(ERROR, "No Driver Station packet.\n");
}
}
// Run the iteration.
aos::ScopedMessagePtr<StatusType> status =
control_loop_->status.MakeMessage();
if (status.get() == NULL) {
return;
}
if (outputs_enabled) {
aos::ScopedMessagePtr<OutputType> output =
control_loop_->output.MakeMessage();
RunIteration(goal, position, output.get(), status.get());
output->Print(state, sizeof(state));
LOG(DEBUG, "output={%s}\n", state);
output.Send();
} else {
// The outputs are disabled, so pass NULL in for the output.
RunIteration(goal, position, NULL, status.get());
ZeroOutputs();
}
status->Print(state, sizeof(state));
LOG(DEBUG, "status={%s}\n", state);
status.Send();
}
template <class T, bool has_position>
void ControlLoop<T, has_position>::Run() {
while (true) {
::aos::time::PhasedLoopXMS(kLoopFrequency.ToMSec(), 0);
Iterate();
}
}
} // namespace control_loops
} // namespace aos