| #include <unistd.h> |
| |
| #include <array> |
| #include <chrono> |
| #include <cinttypes> |
| #include <cmath> |
| #include <cstdio> |
| #include <cstring> |
| #include <functional> |
| #include <memory> |
| #include <mutex> |
| #include <thread> |
| |
| #include "ctre/phoenix/CANifier.h" |
| |
| #include "frc971/wpilib/ahal/AnalogInput.h" |
| #include "frc971/wpilib/ahal/Counter.h" |
| #include "frc971/wpilib/ahal/DigitalGlitchFilter.h" |
| #include "frc971/wpilib/ahal/DriverStation.h" |
| #include "frc971/wpilib/ahal/Encoder.h" |
| #include "frc971/wpilib/ahal/VictorSP.h" |
| #undef ERROR |
| |
| #include "ctre/phoenix/motorcontrol/can/TalonSRX.h" |
| |
| #include "aos/commonmath.h" |
| #include "aos/events/event_loop.h" |
| #include "aos/events/shm_event_loop.h" |
| #include "aos/init.h" |
| #include "aos/logging/logging.h" |
| #include "aos/realtime.h" |
| #include "aos/time/time.h" |
| #include "aos/util/log_interval.h" |
| #include "aos/util/phased_loop.h" |
| #include "aos/util/wrapping_counter.h" |
| #include "frc971/autonomous/auto_mode_generated.h" |
| #include "frc971/control_loops/drivetrain/drivetrain_position_generated.h" |
| #include "frc971/input/robot_state_generated.h" |
| #include "frc971/wpilib/ADIS16448.h" |
| #include "frc971/wpilib/buffered_pcm.h" |
| #include "frc971/wpilib/buffered_solenoid.h" |
| #include "frc971/wpilib/dma.h" |
| #include "frc971/wpilib/drivetrain_writer.h" |
| #include "frc971/wpilib/encoder_and_potentiometer.h" |
| #include "frc971/wpilib/joystick_sender.h" |
| #include "frc971/wpilib/logging_generated.h" |
| #include "frc971/wpilib/loop_output_handler.h" |
| #include "frc971/wpilib/pdp_fetcher.h" |
| #include "frc971/wpilib/sensor_reader.h" |
| #include "frc971/wpilib/wpilib_robot_base.h" |
| #include "y2019/camera_log_generated.h" |
| #include "y2019/constants.h" |
| #include "y2019/control_loops/drivetrain/camera_generated.h" |
| #include "y2019/control_loops/superstructure/superstructure_output_generated.h" |
| #include "y2019/control_loops/superstructure/superstructure_position_static.h" |
| #include "y2019/jevois/spi.h" |
| #include "y2019/status_light_generated.h" |
| |
| #ifndef M_PI |
| #define M_PI 3.14159265358979323846 |
| #endif |
| |
| using ::aos::monotonic_clock; |
| using ::y2019::constants::Values; |
| namespace superstructure = ::y2019::control_loops::superstructure; |
| namespace chrono = ::std::chrono; |
| using std::make_unique; |
| |
| namespace y2019::wpilib { |
| namespace { |
| |
| constexpr double kMaxBringupPower = 12.0; |
| |
| // TODO(Brian): Fix the interpretation of the result of GetRaw here and in the |
| // DMA stuff and then removing the * 2.0 in *_translate. |
| // The low bit is direction. |
| |
| // TODO(brian): Use ::std::max instead once we have C++14 so that can be |
| // constexpr. |
| template <typename T> |
| constexpr T max(T a, T b) { |
| return (a > b) ? a : b; |
| } |
| |
| template <typename T, typename... Rest> |
| constexpr T max(T a, T b, T c, Rest... rest) { |
| return max(max(a, b), c, rest...); |
| } |
| |
| double drivetrain_translate(int32_t in) { |
| return ((static_cast<double>(in) / |
| Values::kDrivetrainEncoderCountsPerRevolution()) * |
| (2.0 * M_PI)) * |
| Values::kDrivetrainEncoderRatio() * |
| control_loops::drivetrain::kWheelRadius; |
| } |
| |
| double drivetrain_velocity_translate(double in) { |
| return (((1.0 / in) / Values::kDrivetrainCyclesPerRevolution()) * |
| (2.0 * M_PI)) * |
| Values::kDrivetrainEncoderRatio() * |
| control_loops::drivetrain::kWheelRadius; |
| } |
| |
| double elevator_pot_translate(double voltage) { |
| return voltage * Values::kElevatorPotRatio() * |
| (10.0 /*turns*/ / 5.0 /*volts*/) * (2 * M_PI /*radians*/); |
| } |
| |
| double wrist_pot_translate(double voltage) { |
| return voltage * Values::kWristPotRatio() * (5.0 /*turns*/ / 5.0 /*volts*/) * |
| (2 * M_PI /*radians*/); |
| } |
| |
| double stilts_pot_translate(double voltage) { |
| return voltage * Values::kStiltsPotRatio() * |
| (10.0 /*turns*/ / 5.0 /*volts*/) * (2 * M_PI /*radians*/); |
| } |
| |
| constexpr double kMaxFastEncoderPulsesPerSecond = |
| max(Values::kMaxDrivetrainEncoderPulsesPerSecond(), |
| Values::kMaxIntakeEncoderPulsesPerSecond()); |
| static_assert(kMaxFastEncoderPulsesPerSecond <= 1300000, |
| "fast encoders are too fast"); |
| constexpr double kMaxMediumEncoderPulsesPerSecond = |
| max(Values::kMaxElevatorEncoderPulsesPerSecond(), |
| Values::kMaxWristEncoderPulsesPerSecond()); |
| |
| static_assert(kMaxMediumEncoderPulsesPerSecond <= 400000, |
| "medium encoders are too fast"); |
| |
| // Class to send position messages with sensor readings to our loops. |
| class SensorReader : public ::frc971::wpilib::SensorReader { |
| public: |
| SensorReader(::aos::ShmEventLoop *event_loop) |
| : ::frc971::wpilib::SensorReader(event_loop), |
| auto_mode_sender_( |
| event_loop->MakeSender<::frc971::autonomous::AutonomousMode>( |
| "/autonomous")), |
| superstructure_position_sender_( |
| event_loop->MakeSender<superstructure::PositionStatic>( |
| "/superstructure")), |
| drivetrain_position_sender_( |
| event_loop |
| ->MakeSender<::frc971::control_loops::drivetrain::Position>( |
| "/drivetrain")) { |
| // Set to filter out anything shorter than 1/4 of the minimum pulse width |
| // we should ever see. |
| UpdateFastEncoderFilterHz(kMaxFastEncoderPulsesPerSecond); |
| UpdateMediumEncoderFilterHz(kMaxMediumEncoderPulsesPerSecond); |
| } |
| |
| // Elevator |
| |
| void set_elevator_encoder(::std::unique_ptr<frc::Encoder> encoder) { |
| medium_encoder_filter_.Add(encoder.get()); |
| elevator_encoder_.set_encoder(::std::move(encoder)); |
| } |
| |
| void set_elevator_absolute_pwm( |
| ::std::unique_ptr<frc::DigitalInput> absolute_pwm) { |
| elevator_encoder_.set_absolute_pwm(::std::move(absolute_pwm)); |
| } |
| |
| void set_elevator_potentiometer( |
| ::std::unique_ptr<frc::AnalogInput> potentiometer) { |
| elevator_encoder_.set_potentiometer(::std::move(potentiometer)); |
| } |
| |
| // Intake |
| |
| void set_intake_encoder(::std::unique_ptr<frc::Encoder> encoder) { |
| medium_encoder_filter_.Add(encoder.get()); |
| intake_encoder_.set_encoder(::std::move(encoder)); |
| } |
| |
| void set_intake_absolute_pwm( |
| ::std::unique_ptr<frc::DigitalInput> absolute_pwm) { |
| intake_encoder_.set_absolute_pwm(::std::move(absolute_pwm)); |
| } |
| |
| // Wrist |
| |
| void set_wrist_encoder(::std::unique_ptr<frc::Encoder> encoder) { |
| medium_encoder_filter_.Add(encoder.get()); |
| wrist_encoder_.set_encoder(::std::move(encoder)); |
| } |
| |
| void set_wrist_absolute_pwm( |
| ::std::unique_ptr<frc::DigitalInput> absolute_pwm) { |
| wrist_encoder_.set_absolute_pwm(::std::move(absolute_pwm)); |
| } |
| |
| void set_wrist_potentiometer( |
| ::std::unique_ptr<frc::AnalogInput> potentiometer) { |
| wrist_encoder_.set_potentiometer(::std::move(potentiometer)); |
| } |
| |
| // Stilts |
| |
| void set_stilts_encoder(::std::unique_ptr<frc::Encoder> encoder) { |
| medium_encoder_filter_.Add(encoder.get()); |
| stilts_encoder_.set_encoder(::std::move(encoder)); |
| } |
| |
| void set_stilts_absolute_pwm( |
| ::std::unique_ptr<frc::DigitalInput> absolute_pwm) { |
| stilts_encoder_.set_absolute_pwm(::std::move(absolute_pwm)); |
| } |
| |
| void set_stilts_potentiometer( |
| ::std::unique_ptr<frc::AnalogInput> potentiometer) { |
| stilts_encoder_.set_potentiometer(::std::move(potentiometer)); |
| } |
| |
| void set_platform_left_detect( |
| ::std::unique_ptr<frc::DigitalInput> platform_left_detect) { |
| platform_left_detect_ = ::std::move(platform_left_detect); |
| } |
| |
| void set_platform_right_detect( |
| ::std::unique_ptr<frc::DigitalInput> platform_right_detect) { |
| platform_right_detect_ = ::std::move(platform_right_detect); |
| } |
| |
| // Vacuum pressure sensor |
| void set_vacuum_sensor(int port) { |
| vacuum_sensor_ = make_unique<frc::AnalogInput>(port); |
| } |
| |
| // Auto mode switches. |
| void set_autonomous_mode(int i, ::std::unique_ptr<frc::DigitalInput> sensor) { |
| autonomous_modes_.at(i) = ::std::move(sensor); |
| } |
| |
| void RunIteration() override { |
| { |
| auto builder = drivetrain_position_sender_.MakeBuilder(); |
| frc971::control_loops::drivetrain::Position::Builder drivetrain_builder = |
| builder.MakeBuilder<frc971::control_loops::drivetrain::Position>(); |
| drivetrain_builder.add_left_encoder( |
| drivetrain_translate(drivetrain_left_encoder_->GetRaw())); |
| drivetrain_builder.add_left_speed( |
| drivetrain_velocity_translate(drivetrain_left_encoder_->GetPeriod())); |
| |
| drivetrain_builder.add_right_encoder( |
| -drivetrain_translate(drivetrain_right_encoder_->GetRaw())); |
| drivetrain_builder.add_right_speed(-drivetrain_velocity_translate( |
| drivetrain_right_encoder_->GetPeriod())); |
| |
| builder.CheckOk(builder.Send(drivetrain_builder.Finish())); |
| } |
| const auto values = constants::GetValues(); |
| |
| { |
| aos::Sender<superstructure::PositionStatic>::StaticBuilder builder = |
| superstructure_position_sender_.MakeStaticBuilder(); |
| |
| // Elevator |
| CopyPosition(elevator_encoder_, builder->add_elevator(), |
| Values::kElevatorEncoderCountsPerRevolution(), |
| Values::kElevatorEncoderRatio(), elevator_pot_translate, |
| false, values.elevator.potentiometer_offset); |
| // Intake |
| CopyPosition(intake_encoder_, builder->add_intake_joint(), |
| Values::kIntakeEncoderCountsPerRevolution(), |
| Values::kIntakeEncoderRatio(), false); |
| // Wrist |
| CopyPosition(wrist_encoder_, builder->add_wrist(), |
| Values::kWristEncoderCountsPerRevolution(), |
| Values::kWristEncoderRatio(), wrist_pot_translate, false, |
| values.wrist.potentiometer_offset); |
| // Stilts |
| CopyPosition(stilts_encoder_, builder->add_stilts(), |
| Values::kStiltsEncoderCountsPerRevolution(), |
| Values::kStiltsEncoderRatio(), stilts_pot_translate, false, |
| values.stilts.potentiometer_offset); |
| |
| // Suction |
| constexpr float kMinVoltage = 0.5; |
| constexpr float kMaxVoltage = 2.1; |
| builder->set_suction_pressure( |
| (vacuum_sensor_->GetVoltage() - kMinVoltage) / |
| (kMaxVoltage - kMinVoltage)); |
| |
| builder->set_platform_left_detect(!platform_left_detect_->Get()); |
| builder->set_platform_right_detect(!platform_right_detect_->Get()); |
| |
| builder.CheckOk(builder.Send()); |
| } |
| |
| { |
| auto builder = auto_mode_sender_.MakeBuilder(); |
| |
| uint32_t mode = 0; |
| for (size_t i = 0; i < autonomous_modes_.size(); ++i) { |
| if (autonomous_modes_[i] && autonomous_modes_[i]->Get()) { |
| mode |= 1 << i; |
| } |
| } |
| |
| auto auto_mode_builder = |
| builder.MakeBuilder<frc971::autonomous::AutonomousMode>(); |
| |
| auto_mode_builder.add_mode(mode); |
| |
| builder.CheckOk(builder.Send(auto_mode_builder.Finish())); |
| } |
| } |
| |
| private: |
| ::aos::Sender<::frc971::autonomous::AutonomousMode> auto_mode_sender_; |
| ::aos::Sender<superstructure::PositionStatic> superstructure_position_sender_; |
| ::aos::Sender<::frc971::control_loops::drivetrain::Position> |
| drivetrain_position_sender_; |
| |
| ::frc971::wpilib::AbsoluteEncoderAndPotentiometer elevator_encoder_, |
| wrist_encoder_, stilts_encoder_; |
| |
| ::std::unique_ptr<frc::DigitalInput> platform_left_detect_; |
| ::std::unique_ptr<frc::DigitalInput> platform_right_detect_; |
| |
| ::std::unique_ptr<frc::AnalogInput> vacuum_sensor_; |
| |
| ::std::array<::std::unique_ptr<frc::DigitalInput>, 2> autonomous_modes_; |
| |
| ::frc971::wpilib::AbsoluteEncoder intake_encoder_; |
| }; |
| |
| class CameraReader { |
| public: |
| CameraReader(::aos::EventLoop *event_loop) |
| : camera_frame_sender_( |
| event_loop |
| ->MakeSender<::y2019::control_loops::drivetrain::CameraFrame>( |
| "/camera")), |
| camera_log_fetcher_( |
| event_loop->MakeFetcher<::y2019::CameraLog>("/camera")) {} |
| |
| CameraReader(const CameraReader &) = delete; |
| CameraReader &operator=(const CameraReader &) = delete; |
| |
| void set_spi(frc::SPI *spi) { |
| spi_ = spi; |
| spi_->SetClockRate(1e6); |
| spi_->SetChipSelectActiveHigh(); |
| spi_->SetMode(frc::SPI::Mode::kMode3); |
| } |
| |
| void set_activate_usb(std::unique_ptr<frc::DigitalInput> activate_usb) { |
| activate_usb_ = std::move(activate_usb); |
| } |
| |
| void set_activate_passthrough( |
| std::unique_ptr<frc::DigitalInput> activate_passthrough) { |
| activate_passthrough_ = std::move(activate_passthrough); |
| } |
| |
| void DoSpiTransaction() { |
| using namespace frc971::jevois; |
| RoborioToTeensy to_teensy{}; |
| to_teensy.realtime_now = aos::realtime_clock::now(); |
| camera_log_fetcher_.Fetch(); |
| if (activate_usb_ && !activate_usb_->Get()) { |
| to_teensy.camera_command = CameraCommand::kUsb; |
| } else if (activate_passthrough_ && !activate_passthrough_->Get()) { |
| to_teensy.camera_command = CameraCommand::kCameraPassthrough; |
| } else if (camera_log_fetcher_.get() && camera_log_fetcher_->log()) { |
| to_teensy.camera_command = CameraCommand::kLog; |
| } else { |
| to_teensy.camera_command = CameraCommand::kNormal; |
| } |
| |
| std::array<char, spi_transfer_size() + 1> to_send{}; |
| { |
| const auto to_send_data = |
| absl::MakeSpan(to_send).last(spi_transfer_size()); |
| const auto encoded = SpiPackToTeensy(to_teensy); |
| std::copy(encoded.begin(), encoded.end(), to_send_data.begin()); |
| } |
| rx_clearer_.ClearRxFifo(); |
| // First, send recieve a dummy byte because the Teensy can't control what it |
| // sends for the first byte. |
| std::array<char, spi_transfer_size() + 1> to_receive; |
| DoTransaction(absl::Span<char>(to_send), absl::Span<char>(to_receive)); |
| const auto unpacked = SpiUnpackToRoborio( |
| absl::MakeSpan(to_receive).last(spi_transfer_size())); |
| if (!unpacked) { |
| AOS_LOG(INFO, "Decoding SPI data failed\n"); |
| return; |
| } |
| |
| const aos::monotonic_clock::time_point now = aos::monotonic_clock::now(); |
| for (const auto &received : unpacked->frames) { |
| auto builder = camera_frame_sender_.MakeBuilder(); |
| |
| std::array< |
| flatbuffers::Offset<y2019::control_loops::drivetrain::CameraTarget>, |
| 3> |
| targets; |
| |
| for (size_t i = 0; i < received.targets.size(); ++i) { |
| y2019::control_loops::drivetrain::CameraTarget::Builder |
| camera_target_builder = builder.MakeBuilder< |
| y2019::control_loops::drivetrain::CameraTarget>(); |
| |
| camera_target_builder.add_distance(received.targets[i].distance); |
| camera_target_builder.add_height(received.targets[i].height); |
| camera_target_builder.add_heading(received.targets[i].heading); |
| camera_target_builder.add_skew(received.targets[i].skew); |
| |
| targets[i] = camera_target_builder.Finish(); |
| } |
| |
| flatbuffers::Offset<flatbuffers::Vector< |
| flatbuffers::Offset<y2019::control_loops::drivetrain::CameraTarget>>> |
| targets_offset = builder.fbb()->CreateVector(targets.begin(), |
| received.targets.size()); |
| |
| y2019::control_loops::drivetrain::CameraFrame::Builder |
| camera_frame_builder = |
| builder |
| .MakeBuilder<y2019::control_loops::drivetrain::CameraFrame>(); |
| |
| camera_frame_builder.add_targets(targets_offset); |
| |
| // Add an extra 10ms delay to account for unmodeled delays that Austin |
| // thinks exists. |
| camera_frame_builder.add_timestamp( |
| std::chrono::nanoseconds( |
| (now - received.age - ::std::chrono::milliseconds(10)) |
| .time_since_epoch()) |
| .count()); |
| camera_frame_builder.add_camera(received.camera_index); |
| builder.CheckOk(builder.Send(camera_frame_builder.Finish())); |
| } |
| |
| if (dummy_spi_) { |
| uint8_t dummy_send, dummy_receive; |
| dummy_spi_->Transaction(&dummy_send, &dummy_receive, 1); |
| } |
| } |
| |
| void DoTransaction(absl::Span<char> to_send, absl::Span<char> to_receive) { |
| AOS_CHECK_EQ(to_send.size(), to_receive.size()); |
| const int result = spi_->Transaction( |
| reinterpret_cast<uint8_t *>(to_send.data()), |
| reinterpret_cast<uint8_t *>(to_receive.data()), to_send.size()); |
| if (result == static_cast<int>(to_send.size())) { |
| return; |
| } |
| if (result == -1) { |
| AOS_LOG(INFO, "SPI::Transaction of %zd bytes failed\n", to_send.size()); |
| return; |
| } |
| AOS_LOG(FATAL, "SPI::Transaction returned something weird\n"); |
| } |
| |
| void SetDummySPI(frc::SPI::Port port) { |
| dummy_spi_.reset(new frc::SPI(port)); |
| // Pick the same settings here in case the roboRIO decides to try something |
| // stupid when switching. |
| if (dummy_spi_) { |
| dummy_spi_->SetClockRate(1e5); |
| dummy_spi_->SetChipSelectActiveLow(); |
| dummy_spi_->SetMode(frc::SPI::Mode::kMode3); |
| } |
| } |
| |
| private: |
| ::aos::Sender<::y2019::control_loops::drivetrain::CameraFrame> |
| camera_frame_sender_; |
| ::aos::Fetcher<::y2019::CameraLog> camera_log_fetcher_; |
| |
| frc::SPI *spi_ = nullptr; |
| ::std::unique_ptr<frc::SPI> dummy_spi_; |
| |
| std::unique_ptr<frc::DigitalInput> activate_usb_; |
| std::unique_ptr<frc::DigitalInput> activate_passthrough_; |
| |
| frc971::wpilib::SpiRxClearer rx_clearer_; |
| }; |
| |
| class SuperstructureWriter |
| : public ::frc971::wpilib::LoopOutputHandler<superstructure::Output> { |
| public: |
| SuperstructureWriter(::aos::EventLoop *event_loop) |
| : ::frc971::wpilib::LoopOutputHandler<superstructure::Output>( |
| event_loop, "/superstructure"), |
| robot_state_fetcher_( |
| event_loop->MakeFetcher<::aos::RobotState>("/aos")) {} |
| |
| void set_elevator_victor(::std::unique_ptr<::frc::VictorSP> t) { |
| elevator_victor_ = ::std::move(t); |
| } |
| |
| void set_suction_victor(::std::unique_ptr<::frc::VictorSP> t) { |
| suction_victor_ = ::std::move(t); |
| } |
| |
| void set_intake_victor(::std::unique_ptr<::frc::VictorSP> t) { |
| intake_victor_ = ::std::move(t); |
| } |
| |
| void set_wrist_victor(::std::unique_ptr<::frc::VictorSP> t) { |
| wrist_victor_ = ::std::move(t); |
| } |
| |
| void set_stilts_victor(::std::unique_ptr<::frc::VictorSP> t) { |
| stilts_victor_ = ::std::move(t); |
| } |
| |
| private: |
| void Write(const superstructure::Output &output) override { |
| elevator_victor_->SetSpeed(::aos::Clip(output.elevator_voltage(), |
| -kMaxBringupPower, |
| kMaxBringupPower) / |
| 12.0); |
| |
| intake_victor_->SetSpeed(::aos::Clip(output.intake_joint_voltage(), |
| -kMaxBringupPower, kMaxBringupPower) / |
| 12.0); |
| |
| wrist_victor_->SetSpeed(::aos::Clip(-output.wrist_voltage(), |
| -kMaxBringupPower, kMaxBringupPower) / |
| 12.0); |
| |
| stilts_victor_->SetSpeed(::aos::Clip(output.stilts_voltage(), |
| -kMaxBringupPower, kMaxBringupPower) / |
| 12.0); |
| |
| robot_state_fetcher_.Fetch(); |
| const double battery_voltage = robot_state_fetcher_.get() |
| ? robot_state_fetcher_->voltage_battery() |
| : 12.0; |
| |
| // Throw a fast low pass filter on the battery voltage so we don't respond |
| // too fast to noise. |
| filtered_battery_voltage_ = |
| 0.5 * filtered_battery_voltage_ + 0.5 * battery_voltage; |
| |
| suction_victor_->SetSpeed(::aos::Clip( |
| output.pump_voltage() / filtered_battery_voltage_, -1.0, 1.0)); |
| } |
| |
| void Stop() override { |
| AOS_LOG(WARNING, "Superstructure output too old.\n"); |
| |
| elevator_victor_->SetDisabled(); |
| intake_victor_->SetDisabled(); |
| wrist_victor_->SetDisabled(); |
| stilts_victor_->SetDisabled(); |
| suction_victor_->SetDisabled(); |
| } |
| |
| ::aos::Fetcher<::aos::RobotState> robot_state_fetcher_; |
| |
| ::std::unique_ptr<::frc::VictorSP> elevator_victor_, intake_victor_, |
| wrist_victor_, stilts_victor_, suction_victor_; |
| |
| double filtered_battery_voltage_ = 12.0; |
| }; |
| |
| class SolenoidWriter { |
| public: |
| SolenoidWriter(::aos::EventLoop *event_loop) |
| : event_loop_(event_loop), |
| superstructure_fetcher_( |
| event_loop->MakeFetcher<superstructure::Output>("/superstructure")), |
| status_light_fetcher_( |
| event_loop->MakeFetcher<::y2019::StatusLight>("/superstructure")), |
| pneumatics_to_log_sender_( |
| event_loop->MakeSender<::frc971::wpilib::PneumaticsToLog>("/aos")) { |
| event_loop_->SetRuntimeRealtimePriority(27); |
| |
| event_loop_->AddPhasedLoop([this](int iterations) { Loop(iterations); }, |
| ::std::chrono::milliseconds(20), |
| ::std::chrono::milliseconds(1)); |
| } |
| |
| void set_big_suction_cup(int index0, int index1) { |
| big_suction_cup0_ = pcm_.MakeSolenoid(index0); |
| big_suction_cup1_ = pcm_.MakeSolenoid(index1); |
| } |
| void set_small_suction_cup(int index0, int index1) { |
| small_suction_cup0_ = pcm_.MakeSolenoid(index0); |
| small_suction_cup1_ = pcm_.MakeSolenoid(index1); |
| } |
| |
| void set_intake_roller_talon( |
| ::std::unique_ptr<::ctre::phoenix::motorcontrol::can::TalonSRX> t) { |
| intake_rollers_talon_ = ::std::move(t); |
| intake_rollers_talon_->ConfigContinuousCurrentLimit(10.0, 0); |
| intake_rollers_talon_->EnableCurrentLimit(true); |
| } |
| |
| void Loop(const int iterations) { |
| if (iterations != 1) { |
| AOS_LOG(DEBUG, "Solenoids skipped %d iterations\n", iterations - 1); |
| } |
| |
| { |
| superstructure_fetcher_.Fetch(); |
| if (superstructure_fetcher_.get()) { |
| big_suction_cup0_->Set( |
| !superstructure_fetcher_->intake_suction_bottom()); |
| big_suction_cup1_->Set( |
| !superstructure_fetcher_->intake_suction_bottom()); |
| small_suction_cup0_->Set(superstructure_fetcher_->intake_suction_top()); |
| small_suction_cup1_->Set(superstructure_fetcher_->intake_suction_top()); |
| |
| intake_rollers_talon_->Set( |
| ctre::phoenix::motorcontrol::ControlMode::PercentOutput, |
| ::aos::Clip(superstructure_fetcher_->intake_roller_voltage(), |
| -kMaxBringupPower, kMaxBringupPower) / |
| 12.0); |
| } |
| } |
| |
| { |
| auto builder = pneumatics_to_log_sender_.MakeBuilder(); |
| |
| ::frc971::wpilib::PneumaticsToLog::Builder to_log_builder = |
| builder.MakeBuilder<frc971::wpilib::PneumaticsToLog>(); |
| |
| pcm_.Flush(); |
| to_log_builder.add_read_solenoids(pcm_.GetAll()); |
| (void)builder.Send(to_log_builder.Finish()); |
| } |
| |
| status_light_fetcher_.Fetch(); |
| // If we don't have a light request (or it's an old one), we are borked. |
| // Flash the red light slowly. |
| StatusLightT color; |
| if (!status_light_fetcher_.get() || |
| status_light_fetcher_.context().monotonic_event_time + |
| chrono::milliseconds(100) < |
| event_loop_->monotonic_now()) { |
| color.red = 0.0; |
| color.green = 0.0; |
| color.blue = 0.0; |
| |
| ++light_flash_; |
| if (light_flash_ > 10) { |
| color.red = 0.5; |
| } |
| |
| if (light_flash_ > 20) { |
| light_flash_ = 0; |
| } |
| |
| } else { |
| status_light_fetcher_->UnPackTo(&color); |
| } |
| SetColor(color); |
| } |
| |
| void SetColor(const StatusLightT status_light) { |
| // Save CAN bandwidth and CPU at the cost of RT. Only change the light when |
| // it actually changes. This is pretty low priority anyways. |
| static int time_since_last_send = 0; |
| ++time_since_last_send; |
| if (time_since_last_send > 10) { |
| time_since_last_send = 0; |
| } |
| if (status_light.green != last_green_ || time_since_last_send == 0) { |
| canifier_.SetLEDOutput(status_light.green, |
| ::ctre::phoenix::CANifier::LEDChannelA); |
| last_green_ = status_light.green; |
| } |
| |
| if (status_light.blue != last_blue_ || time_since_last_send == 0) { |
| canifier_.SetLEDOutput(status_light.blue, |
| ::ctre::phoenix::CANifier::LEDChannelC); |
| last_blue_ = status_light.blue; |
| } |
| |
| if (status_light.red != last_red_ || time_since_last_send == 0) { |
| canifier_.SetLEDOutput(status_light.red, |
| ::ctre::phoenix::CANifier::LEDChannelB); |
| last_red_ = status_light.red; |
| } |
| } |
| |
| private: |
| ::aos::EventLoop *event_loop_; |
| |
| ::frc971::wpilib::BufferedPcm pcm_; |
| |
| ::std::unique_ptr<::frc971::wpilib::BufferedSolenoid> big_suction_cup0_, |
| big_suction_cup1_, small_suction_cup0_, small_suction_cup1_; |
| |
| ::std::unique_ptr<::ctre::phoenix::motorcontrol::can::TalonSRX> |
| intake_rollers_talon_; |
| |
| ::aos::Fetcher<::y2019::control_loops::superstructure::Output> |
| superstructure_fetcher_; |
| ::aos::Fetcher<::y2019::StatusLight> status_light_fetcher_; |
| |
| aos::Sender<::frc971::wpilib::PneumaticsToLog> pneumatics_to_log_sender_; |
| |
| ::ctre::phoenix::CANifier canifier_{0}; |
| |
| double last_red_ = -1.0; |
| double last_green_ = -1.0; |
| double last_blue_ = -1.0; |
| |
| int light_flash_ = 0; |
| }; |
| |
| class WPILibRobot : public ::frc971::wpilib::WPILibRobotBase { |
| public: |
| ::std::unique_ptr<frc::Encoder> make_encoder(int index) { |
| return make_unique<frc::Encoder>(10 + index * 2, 11 + index * 2, false, |
| frc::Encoder::k4X); |
| } |
| |
| void Run() override { |
| aos::FlatbufferDetachedBuffer<aos::Configuration> config = |
| aos::configuration::ReadConfig("aos_config.json"); |
| |
| // Thread 1. |
| ::aos::ShmEventLoop joystick_sender_event_loop(&config.message()); |
| ::frc971::wpilib::JoystickSender joystick_sender( |
| &joystick_sender_event_loop); |
| AddLoop(&joystick_sender_event_loop); |
| |
| // Thread 2. |
| ::aos::ShmEventLoop pdp_fetcher_event_loop(&config.message()); |
| ::frc971::wpilib::PDPFetcher pdp_fetcher(&pdp_fetcher_event_loop); |
| AddLoop(&pdp_fetcher_event_loop); |
| |
| // Thread 3. |
| ::aos::ShmEventLoop sensor_reader_event_loop(&config.message()); |
| SensorReader sensor_reader(&sensor_reader_event_loop); |
| sensor_reader.set_drivetrain_left_encoder(make_encoder(0)); |
| sensor_reader.set_drivetrain_right_encoder(make_encoder(1)); |
| |
| sensor_reader.set_elevator_encoder(make_encoder(4)); |
| sensor_reader.set_elevator_absolute_pwm(make_unique<frc::DigitalInput>(4)); |
| sensor_reader.set_elevator_potentiometer(make_unique<frc::AnalogInput>(4)); |
| |
| sensor_reader.set_wrist_encoder(make_encoder(5)); |
| sensor_reader.set_wrist_absolute_pwm(make_unique<frc::DigitalInput>(5)); |
| sensor_reader.set_wrist_potentiometer(make_unique<frc::AnalogInput>(5)); |
| |
| sensor_reader.set_intake_encoder(make_encoder(2)); |
| sensor_reader.set_intake_absolute_pwm(make_unique<frc::DigitalInput>(2)); |
| |
| sensor_reader.set_stilts_encoder(make_encoder(3)); |
| sensor_reader.set_stilts_absolute_pwm(make_unique<frc::DigitalInput>(3)); |
| sensor_reader.set_stilts_potentiometer(make_unique<frc::AnalogInput>(3)); |
| |
| sensor_reader.set_pwm_trigger(true); |
| sensor_reader.set_vacuum_sensor(7); |
| |
| sensor_reader.set_platform_right_detect(make_unique<frc::DigitalInput>(6)); |
| sensor_reader.set_platform_left_detect(make_unique<frc::DigitalInput>(7)); |
| |
| sensor_reader.set_autonomous_mode(0, make_unique<frc::DigitalInput>(22)); |
| sensor_reader.set_autonomous_mode(0, make_unique<frc::DigitalInput>(23)); |
| AddLoop(&sensor_reader_event_loop); |
| |
| // Thread 4. |
| ::aos::ShmEventLoop imu_event_loop(&config.message()); |
| CameraReader camera_reader(&imu_event_loop); |
| frc::SPI camera_spi(frc::SPI::Port::kOnboardCS3); |
| camera_reader.set_spi(&camera_spi); |
| camera_reader.SetDummySPI(frc::SPI::Port::kOnboardCS2); |
| // Austin says 8, 9, 24, and 25 are good options to choose from for these. |
| camera_reader.set_activate_usb(make_unique<frc::DigitalInput>(24)); |
| camera_reader.set_activate_passthrough(make_unique<frc::DigitalInput>(25)); |
| |
| auto imu_trigger = make_unique<frc::DigitalInput>(0); |
| ::frc971::wpilib::ADIS16448 imu( |
| &imu_event_loop, frc::SPI::Port::kOnboardCS1, imu_trigger.get()); |
| imu.set_spi_idle_callback( |
| [&camera_reader]() { camera_reader.DoSpiTransaction(); }); |
| auto imu_reset = make_unique<frc::DigitalOutput>(1); |
| imu.set_reset(imu_reset.get()); |
| AddLoop(&imu_event_loop); |
| |
| // While as of 2/9/18 the drivetrain Victors are SPX, it appears as though |
| // they are identical, as far as DrivetrainWriter is concerned, to the SP |
| // variety so all the Victors are written as SPs. |
| |
| // Thread 5. |
| ::aos::ShmEventLoop output_event_loop(&config.message()); |
| ::frc971::wpilib::DrivetrainWriter drivetrain_writer(&output_event_loop); |
| drivetrain_writer.set_left_controller0( |
| ::std::unique_ptr<::frc::VictorSP>(new ::frc::VictorSP(0)), true); |
| drivetrain_writer.set_right_controller0( |
| ::std::unique_ptr<::frc::VictorSP>(new ::frc::VictorSP(1)), false); |
| |
| SuperstructureWriter superstructure_writer(&output_event_loop); |
| superstructure_writer.set_elevator_victor( |
| ::std::unique_ptr<::frc::VictorSP>(new ::frc::VictorSP(4))); |
| // TODO(austin): Do the vacuum |
| superstructure_writer.set_suction_victor( |
| ::std::unique_ptr<::frc::VictorSP>(new ::frc::VictorSP(6))); |
| superstructure_writer.set_intake_victor( |
| ::std::unique_ptr<::frc::VictorSP>(new ::frc::VictorSP(2))); |
| superstructure_writer.set_wrist_victor( |
| ::std::unique_ptr<::frc::VictorSP>(new ::frc::VictorSP(5))); |
| superstructure_writer.set_stilts_victor( |
| ::std::unique_ptr<::frc::VictorSP>(new ::frc::VictorSP(3))); |
| AddLoop(&output_event_loop); |
| |
| // Thread 6. |
| ::aos::ShmEventLoop solenoid_writer_event_loop(&config.message()); |
| SolenoidWriter solenoid_writer(&solenoid_writer_event_loop); |
| solenoid_writer.set_intake_roller_talon( |
| make_unique<::ctre::phoenix::motorcontrol::can::TalonSRX>(10)); |
| solenoid_writer.set_big_suction_cup(0, 1); |
| solenoid_writer.set_small_suction_cup(2, 3); |
| AddLoop(&solenoid_writer_event_loop); |
| |
| RunLoops(); |
| } |
| }; |
| |
| } // namespace |
| } // namespace y2019::wpilib |
| |
| AOS_ROBOT_CLASS(::y2019::wpilib::WPILibRobot); |