blob: 4d0c1ccd1ec816a76a6fbe1b641cae3a24234b6c [file] [log] [blame]
#ifndef FRC971_ZEROING_ZEROING_H_
#define FRC971_ZEROING_ZEROING_H_
#include <algorithm>
#include <cmath>
#include <cstdint>
#include <vector>
#include "flatbuffers/flatbuffers.h"
#include "frc971/constants.h"
#include "frc971/control_loops/control_loops_generated.h"
// TODO(pschrader): Flag an error if encoder index pulse is not n revolutions
// away from the last one (i.e. got extra counts from noise, etc..)
//
// TODO(pschrader): Flag error if the pot disagrees too much with the encoder
// after being zeroed.
//
// TODO(pschrader): Watch the offset over long periods of time and flag if it
// gets too far away from the initial value.
namespace frc971 {
namespace zeroing {
template <typename TPosition, typename TZeroingConstants, typename TState>
class ZeroingEstimator {
public:
using Position = TPosition;
using ZeroingConstants = TZeroingConstants;
using State = TState;
virtual ~ZeroingEstimator() {}
// Returns true if the logic considers the corresponding mechanism to be
// zeroed.
virtual bool zeroed() const = 0;
// Returns the estimated position of the corresponding mechanism.
virtual double offset() const = 0;
// Returns true if there has been an error.
virtual bool error() const = 0;
// Returns true if an offset is ready.
virtual bool offset_ready() const = 0;
// Triggers an internal error. This is used for testing the error
// logic.
virtual void TriggerError() = 0;
// Resets the estimator, clearing error and zeroing states.
virtual void Reset() = 0;
// Updates the internal logic with new sensor values
virtual void UpdateEstimate(const Position &) = 0;
// Returns the state of the estimator
virtual flatbuffers::Offset<State> GetEstimatorState(
flatbuffers::FlatBufferBuilder *fbb) const = 0;
};
// Class to encapsulate the logic to decide when we are moving and which samples
// are safe to use.
template <typename Position, typename PositionBuffer>
class MoveDetector {
public:
MoveDetector(size_t filter_size) {
buffered_samples_.reserve(filter_size);
Reset();
}
// Clears all the state.
void Reset() {
buffered_samples_.clear();
buffered_samples_idx_ = 0;
}
// Updates the detector with a new sample. Returns true if we are moving.
// buffer_size is the number of samples in the moving buffer, and
// zeroing_threshold is the max amount we can move within the period specified
// by buffer_size.
bool Update(const PositionBuffer &position_buffer, size_t buffer_size,
double zeroing_threshold) {
bool moving = true;
Position position(position_buffer);
if (buffered_samples_.size() < buffer_size) {
// Not enough samples to start determining if the robot is moving or not,
// don't use the samples yet.
buffered_samples_.push_back(position);
} else {
// Have enough samples to start determining if the robot is moving or not.
buffered_samples_[buffered_samples_idx_] = position;
const auto minmax_value = ::std::minmax_element(
buffered_samples_.begin(), buffered_samples_.end(),
[](const Position &left, const Position &right) {
return left.encoder < right.encoder;
});
const double min_value = minmax_value.first->encoder;
const double max_value = minmax_value.second->encoder;
if (::std::abs(max_value - min_value) < zeroing_threshold) {
// Robot isn't moving, use middle sample to determine offsets.
moving = false;
}
}
buffered_samples_idx_ = (buffered_samples_idx_ + 1) % buffer_size;
return moving;
}
// Returns a safe sample if we aren't moving as reported by Update.
const Position &GetSample() const {
// The robot is not moving, use the middle sample to determine offsets.
// The middle sample makes it so that we don't use the samples from the
// beginning or end of periods when the robot is moving.
const int middle_index =
(buffered_samples_idx_ + (buffered_samples_.size() / 2)) %
buffered_samples_.size();
return buffered_samples_[middle_index];
}
private:
// The last buffer_size samples.
::std::vector<Position> buffered_samples_;
// The index to place the next sample in.
size_t buffered_samples_idx_;
};
// A trivial ZeroingEstimator which just passes the position straight through.
class RelativeEncoderZeroingEstimator
: public ZeroingEstimator<RelativePosition, void,
RelativeEncoderEstimatorState> {
public:
explicit RelativeEncoderZeroingEstimator() {}
// Update position with new position from encoder
void UpdateEstimate(const RelativePosition &position) override {
position_ = position.encoder();
}
// We alre always zeroed
bool zeroed() const override { return true; }
// Starting position of the joint
double offset() const override { return 0; }
// Has an error occured? Note: Only triggered by TriggerError()
bool error() const override { return error_; }
// Offset is always ready, since we are always zeroed.
bool offset_ready() const override { return true; }
void TriggerError() override { error_ = true; }
void Reset() override { error_ = false; }
flatbuffers::Offset<State> GetEstimatorState(
flatbuffers::FlatBufferBuilder *fbb) const override {
State::Builder builder(*fbb);
builder.add_error(error_);
builder.add_position(position_);
return builder.Finish();
}
private:
// Position from encoder relative to start
double position_ = 0;
bool error_ = false;
};
} // namespace zeroing
} // namespace frc971
// TODO(Brian): Actually split these targets apart. Need to convert all the
// reverse dependencies to #include what they actually need...
#include "frc971/zeroing/absolute_encoder.h"
#include "frc971/zeroing/hall_effect_and_position.h"
#include "frc971/zeroing/pot_and_absolute_encoder.h"
#include "frc971/zeroing/pot_and_index.h"
#include "frc971/zeroing/pulse_index.h"
#endif // FRC971_ZEROING_ZEROING_H_