blob: 6d924e51fc63b1191661c69578b483bfca87226f [file] [log] [blame]
Austin Schuh010eb812014-10-25 18:06:49 -07001#include <stdio.h>
2#include <string.h>
Austin Schuh010eb812014-10-25 18:06:49 -07003#include <unistd.h>
4#include <inttypes.h>
5
Brian Silvermand8f403a2014-12-13 19:12:04 -05006#include <thread>
7#include <mutex>
8#include <functional>
9
Austin Schuh010eb812014-10-25 18:06:49 -070010#include "aos/common/controls/output_check.q.h"
11#include "aos/common/controls/sensor_generation.q.h"
12#include "aos/common/logging/logging.h"
13#include "aos/common/logging/queue_logging.h"
Brian Silvermand8f403a2014-12-13 19:12:04 -050014#include "aos/common/messages/robot_state.q.h"
Austin Schuh010eb812014-10-25 18:06:49 -070015#include "aos/common/time.h"
16#include "aos/common/util/log_interval.h"
17#include "aos/common/util/phased_loop.h"
18#include "aos/common/util/wrapping_counter.h"
Brian Silvermanb073f242014-09-08 16:29:57 -040019#include "aos/common/stl_mutex.h"
Austin Schuh010eb812014-10-25 18:06:49 -070020#include "aos/linux_code/init.h"
21
22#include "frc971/control_loops/drivetrain/drivetrain.q.h"
Daniel Pettiadf38432015-01-26 17:13:35 -080023#include "frc971/control_loops/fridge/fridge.q.h"
24#include "frc971/control_loops/claw/claw.q.h"
Austin Schuh010eb812014-10-25 18:06:49 -070025#include "frc971/constants.h"
Daniel Pettiaece37f2014-10-25 17:13:44 -070026#include "frc971/shifter_hall_effect.h"
Austin Schuh010eb812014-10-25 18:06:49 -070027
Brian Silvermanda45b6c2014-12-28 11:36:50 -080028#include "frc971/wpilib/hall_effect.h"
29#include "frc971/wpilib/joystick_sender.h"
Brian Silvermand8f403a2014-12-13 19:12:04 -050030#include "frc971/wpilib/loop_output_handler.h"
31#include "frc971/wpilib/buffered_solenoid.h"
32#include "frc971/wpilib/buffered_pcm.h"
Brian Silverman07ec88e2014-12-28 00:13:08 -080033#include "frc971/wpilib/gyro_sender.h"
Brian Silvermanff7b3472015-01-26 17:53:04 -050034#include "frc971/wpilib/dma_edge_counting.h"
Brian Silverman70ec7192015-01-26 17:52:40 -050035#include "frc971/wpilib/interrupt_edge_counting.h"
Brian Silverman4da58072015-01-26 20:18:52 -050036#include "frc971/wpilib/encoder_and_potentiometer.h"
Brian Silvermanda45b6c2014-12-28 11:36:50 -080037
Brian Silvermancb77f232014-12-19 21:48:36 -080038#include "Encoder.h"
Brian Silvermancb77f232014-12-19 21:48:36 -080039#include "Talon.h"
40#include "DriverStation.h"
41#include "AnalogInput.h"
Brian Silvermancb77f232014-12-19 21:48:36 -080042#include "Compressor.h"
43#include "RobotBase.h"
Austin Schuh010eb812014-10-25 18:06:49 -070044
45#ifndef M_PI
46#define M_PI 3.14159265358979323846
47#endif
48
49using ::aos::util::SimpleLogInterval;
Brian Silvermanada5f2c2015-02-01 02:41:14 -050050using ::frc971::control_loops::drivetrain_queue;
Austin Schuh010eb812014-10-25 18:06:49 -070051using ::aos::util::WrappingCounter;
52
53namespace frc971 {
Brian Silvermanda45b6c2014-12-28 11:36:50 -080054namespace wpilib {
Austin Schuh010eb812014-10-25 18:06:49 -070055
Austin Schuh010eb812014-10-25 18:06:49 -070056double drivetrain_translate(int32_t in) {
Austin Schuhdb516032014-12-28 00:12:38 -080057 return static_cast<double>(in) /
Daniel Pettiadf38432015-01-26 17:13:35 -080058 (256.0 /*cpr*/ * 4.0 /*4x*/) *
59 (20.0 / 50.0 /*output stage*/) *
Austin Schuhdb516032014-12-28 00:12:38 -080060 // * constants::GetValues().drivetrain_encoder_ratio
Daniel Pettiadf38432015-01-26 17:13:35 -080061 (4 /*wheel diameter*/ * 2.54 / 100.0 * M_PI);
62}
63
64double arm_translate(int32_t in) {
65 return static_cast<double>(in) /
66 (512.0 /*cpr*/ * 4.0 /*4x*/) *
67 (14.0 / 17.0 /*output sprockets*/) *
68 (18.0 / 48.0 /*encoder pulleys*/) *
69 (2 * M_PI /*radians*/);
70}
71
Brian Silverman8bca4a92015-02-05 15:19:06 -050072double arm_pot_translate(double voltage) {
73 return voltage /
Daniel Pettiadf38432015-01-26 17:13:35 -080074 (14.0 / 17.0 /*output sprockets*/) *
75 (5.0 /*volts*/ / 5.0 /*turns*/) *
76 (2 * M_PI /*radians*/);
77}
78
79double elevator_translate(int32_t in) {
80 return static_cast<double>(in) /
81 (512.0 /*cpr*/ * 4.0 /*4x*/) *
82 (14.0 / 84.0 /*output stage*/) *
83 (32 * 5 / 2.54 / 10 /*pulley circumference (in)*/);
84}
85
Brian Silverman8bca4a92015-02-05 15:19:06 -050086double elevator_pot_translate(double voltage) {
87 return voltage /
Daniel Pettiadf38432015-01-26 17:13:35 -080088 (32 * 5 / 2.54 / 10 /*pulley circumference (in)*/) *
89 (5.0 /*volts*/ / 5.0 /*turns*/);
90}
91
92double claw_translate(int32_t in) {
93 return static_cast<double>(in) /
94 (512.0 /*cpr*/ * 4.0 /*4x*/) *
95 (16.0 / 72.0 /*output sprockets*/) *
96 (2 * M_PI /*radians*/);
97}
98
Brian Silverman8bca4a92015-02-05 15:19:06 -050099double claw_pot_translate(double voltage) {
100 return voltage /
Daniel Pettiadf38432015-01-26 17:13:35 -0800101 (16.0 / 72.0 /*output sprockets*/) *
102 (5.0 /*volts*/ / 5.0 /*turns*/) *
103 (2 * M_PI /*radians*/);
Austin Schuh010eb812014-10-25 18:06:49 -0700104}
105
Austin Schuh010eb812014-10-25 18:06:49 -0700106class SensorReader {
107 public:
Brian Silverman1f90d672015-01-26 20:20:45 -0500108 SensorReader() {
Austin Schuh010eb812014-10-25 18:06:49 -0700109 filter_.SetPeriodNanoSeconds(100000);
Austin Schuh010eb812014-10-25 18:06:49 -0700110 }
111
Brian Silverman1f90d672015-01-26 20:20:45 -0500112 void set_left_encoder(::std::unique_ptr<Encoder> left_encoder) {
113 left_encoder_ = ::std::move(left_encoder);
114 }
115
116 void set_right_encoder(::std::unique_ptr<Encoder> right_encoder) {
117 right_encoder_ = ::std::move(right_encoder);
118 }
119
Austin Schuh010eb812014-10-25 18:06:49 -0700120 void operator()() {
Brian Silverman2fe007c2014-12-28 12:20:01 -0800121 ::aos::SetCurrentThreadName("SensorReader");
122
Brian Silverman1f90d672015-01-26 20:20:45 -0500123 static const int kPriority = 30;
124 //static const int kInterruptPriority = 55;
Austin Schuh010eb812014-10-25 18:06:49 -0700125
Brian Silverman2fe007c2014-12-28 12:20:01 -0800126 ::aos::SetCurrentThreadRealtimePriority(kPriority);
Austin Schuh010eb812014-10-25 18:06:49 -0700127 while (run_) {
Brian Silverman20141f92015-01-05 17:39:01 -0800128 ::aos::time::PhasedLoopXMS(5, 9000);
Austin Schuh010eb812014-10-25 18:06:49 -0700129 RunIteration();
Austin Schuh010eb812014-10-25 18:06:49 -0700130 }
Austin Schuh010eb812014-10-25 18:06:49 -0700131 }
132
133 void RunIteration() {
Austin Schuh010eb812014-10-25 18:06:49 -0700134 DriverStation *ds = DriverStation::GetInstance();
135
Austin Schuhdb516032014-12-28 00:12:38 -0800136 if (ds->IsSysActive()) {
Austin Schuh010eb812014-10-25 18:06:49 -0700137 auto message = ::aos::controls::output_check_received.MakeMessage();
138 // TODO(brians): Actually read a pulse value from the roboRIO.
139 message->pwm_value = 0;
140 message->pulse_length = -1;
141 LOG_STRUCT(DEBUG, "received", *message);
142 message.Send();
143 }
144
Brian Silvermanada5f2c2015-02-01 02:41:14 -0500145 drivetrain_queue.position.MakeWithBuilder()
Austin Schuh010eb812014-10-25 18:06:49 -0700146 .right_encoder(drivetrain_translate(right_encoder_->GetRaw()))
147 .left_encoder(-drivetrain_translate(left_encoder_->GetRaw()))
Austin Schuh010eb812014-10-25 18:06:49 -0700148 .battery_voltage(ds->GetBatteryVoltage())
149 .Send();
150
Brian Silvermand8f403a2014-12-13 19:12:04 -0500151 // Signal that we are alive.
Austin Schuh010eb812014-10-25 18:06:49 -0700152 ::aos::controls::sensor_generation.MakeWithBuilder()
153 .reader_pid(getpid())
154 .cape_resets(0)
155 .Send();
156 }
157
158 void Quit() { run_ = false; }
159
160 private:
Austin Schuh010eb812014-10-25 18:06:49 -0700161 ::std::unique_ptr<Encoder> left_encoder_;
162 ::std::unique_ptr<Encoder> right_encoder_;
Austin Schuh010eb812014-10-25 18:06:49 -0700163
Brian Silverman1f90d672015-01-26 20:20:45 -0500164 ::std::atomic<bool> run_{true};
Austin Schuh010eb812014-10-25 18:06:49 -0700165 DigitalGlitchFilter filter_;
166};
167
Brian Silvermand8f403a2014-12-13 19:12:04 -0500168class SolenoidWriter {
Austin Schuh010eb812014-10-25 18:06:49 -0700169 public:
Brian Silvermand8f403a2014-12-13 19:12:04 -0500170 SolenoidWriter(const ::std::unique_ptr<BufferedPcm> &pcm)
Daniel Pettiadf38432015-01-26 17:13:35 -0800171 : pcm_(pcm),
172 fridge_(".frc971.control_loops.fridge.output"),
173 claw_(".frc971.control_loops.claw.output") {}
Brian Silvermand8f403a2014-12-13 19:12:04 -0500174
Daniel Pettiadf38432015-01-26 17:13:35 -0800175 void set_fridge_grabbers_top_front(::std::unique_ptr<BufferedSolenoid> s) {
176 fridge_grabbers_top_front_ = ::std::move(s);
Austin Schuh010eb812014-10-25 18:06:49 -0700177 }
178
Daniel Pettiadf38432015-01-26 17:13:35 -0800179 void set_fridge_grabbers_top_back(::std::unique_ptr<BufferedSolenoid> s) {
180 fridge_grabbers_top_back_ = ::std::move(s);
181 }
182
183 void set_fridge_grabbers_bottom_front(
184 ::std::unique_ptr<BufferedSolenoid> s) {
185 fridge_grabbers_bottom_front_ = ::std::move(s);
186 }
187
188 void set_fridge_grabbers_bottom_back(
189 ::std::unique_ptr<BufferedSolenoid> s) {
190 fridge_grabbers_bottom_back_ = ::std::move(s);
191 }
192
193 void set_claw_pinchers(::std::unique_ptr<BufferedSolenoid> s) {
194 claw_pinchers_ = ::std::move(s);
Brian Silvermand8f403a2014-12-13 19:12:04 -0500195 }
Austin Schuh010eb812014-10-25 18:06:49 -0700196
Brian Silvermand8f403a2014-12-13 19:12:04 -0500197 void operator()() {
198 ::aos::SetCurrentThreadName("Solenoids");
199 ::aos::SetCurrentThreadRealtimePriority(30);
200
201 while (run_) {
202 ::aos::time::PhasedLoopXMS(20, 1000);
203
204 {
Daniel Pettiadf38432015-01-26 17:13:35 -0800205 fridge_.FetchLatest();
206 if (fridge_.get()) {
207 LOG_STRUCT(DEBUG, "solenoids", *fridge_);
208 fridge_grabbers_top_front_->Set(fridge_->grabbers.top_front);
209 fridge_grabbers_top_back_->Set(fridge_->grabbers.top_back);
210 fridge_grabbers_bottom_front_->Set(fridge_->grabbers.bottom_front);
211 fridge_grabbers_bottom_back_->Set(fridge_->grabbers.bottom_back);
212 }
213 }
214
215 {
216 claw_.FetchLatest();
217 if (claw_.get()) {
218 LOG_STRUCT(DEBUG, "solenoids", *claw_);
219 claw_pinchers_->Set(claw_->rollers_closed);
Brian Silvermand8f403a2014-12-13 19:12:04 -0500220 }
221 }
222
Brian Silvermand8f403a2014-12-13 19:12:04 -0500223 pcm_->Flush();
Austin Schuh010eb812014-10-25 18:06:49 -0700224 }
225 }
226
Brian Silvermand8f403a2014-12-13 19:12:04 -0500227 void Quit() { run_ = false; }
Austin Schuh010eb812014-10-25 18:06:49 -0700228
Brian Silvermand8f403a2014-12-13 19:12:04 -0500229 private:
230 const ::std::unique_ptr<BufferedPcm> &pcm_;
Daniel Pettiadf38432015-01-26 17:13:35 -0800231 ::std::unique_ptr<BufferedSolenoid> fridge_grabbers_top_front_;
232 ::std::unique_ptr<BufferedSolenoid> fridge_grabbers_top_back_;
233 ::std::unique_ptr<BufferedSolenoid> fridge_grabbers_bottom_front_;
234 ::std::unique_ptr<BufferedSolenoid> fridge_grabbers_bottom_back_;
235 ::std::unique_ptr<BufferedSolenoid> claw_pinchers_;
Austin Schuh010eb812014-10-25 18:06:49 -0700236
Daniel Pettiadf38432015-01-26 17:13:35 -0800237 ::aos::Queue<::frc971::control_loops::FridgeQueue::Output> fridge_;
238 ::aos::Queue<::frc971::control_loops::ClawQueue::Output> claw_;
Austin Schuh010eb812014-10-25 18:06:49 -0700239
Brian Silvermand8f403a2014-12-13 19:12:04 -0500240 ::std::atomic<bool> run_{true};
241};
242
243class DrivetrainWriter : public LoopOutputHandler {
244 public:
245 void set_left_drivetrain_talon(::std::unique_ptr<Talon> t) {
246 left_drivetrain_talon_ = ::std::move(t);
Austin Schuh010eb812014-10-25 18:06:49 -0700247 }
248
Brian Silvermand8f403a2014-12-13 19:12:04 -0500249 void set_right_drivetrain_talon(::std::unique_ptr<Talon> t) {
250 right_drivetrain_talon_ = ::std::move(t);
251 }
Austin Schuh010eb812014-10-25 18:06:49 -0700252
Brian Silvermand8f403a2014-12-13 19:12:04 -0500253 private:
254 virtual void Read() override {
Brian Silvermanada5f2c2015-02-01 02:41:14 -0500255 ::frc971::control_loops::drivetrain_queue.output.FetchAnother();
Brian Silvermand8f403a2014-12-13 19:12:04 -0500256 }
257
258 virtual void Write() override {
Brian Silvermanada5f2c2015-02-01 02:41:14 -0500259 auto &queue = ::frc971::control_loops::drivetrain_queue.output;
Brian Silvermand8f403a2014-12-13 19:12:04 -0500260 LOG_STRUCT(DEBUG, "will output", *queue);
261 left_drivetrain_talon_->Set(-queue->left_voltage / 12.0);
262 right_drivetrain_talon_->Set(queue->right_voltage / 12.0);
263 }
264
265 virtual void Stop() override {
266 LOG(WARNING, "drivetrain output too old\n");
267 left_drivetrain_talon_->Disable();
268 right_drivetrain_talon_->Disable();
269 }
270
Austin Schuh010eb812014-10-25 18:06:49 -0700271 ::std::unique_ptr<Talon> left_drivetrain_talon_;
Brian Silvermand8f403a2014-12-13 19:12:04 -0500272 ::std::unique_ptr<Talon> right_drivetrain_talon_;
273};
274
Daniel Pettiadf38432015-01-26 17:13:35 -0800275class FridgeWriter : public LoopOutputHandler {
276 public:
277 void set_left_arm_talon(::std::unique_ptr<Talon> t) {
278 left_arm_talon_ = ::std::move(t);
279 }
280
281 void set_right_arm_talon(::std::unique_ptr<Talon> t) {
282 right_arm_talon_ = ::std::move(t);
283 }
284
285 void set_left_elevator_talon(::std::unique_ptr<Talon> t) {
286 left_elevator_talon_ = ::std::move(t);
287 }
288
289 void set_right_elevator_talon(::std::unique_ptr<Talon> t) {
290 right_elevator_talon_ = ::std::move(t);
291 }
292
293 private:
294 virtual void Read() override {
295 ::frc971::control_loops::fridge_queue.output.FetchAnother();
296 }
297
298 virtual void Write() override {
299 auto &queue = ::frc971::control_loops::fridge_queue.output;
300 LOG_STRUCT(DEBUG, "will output", *queue);
301 left_arm_talon_->Set(-queue->left_arm / 12.0);
302 right_arm_talon_->Set(queue->right_arm / 12.0);
303 left_elevator_talon_->Set(-queue->left_elevator / 12.0);
304 right_elevator_talon_->Set(queue->right_elevator / 12.0);
305 }
306
307 virtual void Stop() override {
308 LOG(WARNING, "Fridge output too old.\n");
309 left_arm_talon_->Disable();
310 right_arm_talon_->Disable();
311 left_elevator_talon_->Disable();
312 right_elevator_talon_->Disable();
313 }
314
315 ::std::unique_ptr<Talon> left_arm_talon_;
316 ::std::unique_ptr<Talon> right_arm_talon_;
317 ::std::unique_ptr<Talon> left_elevator_talon_;
318 ::std::unique_ptr<Talon> right_elevator_talon_;
319};
320
321class ClawWriter : public LoopOutputHandler {
322 public:
323 void set_intake_talon(::std::unique_ptr<Talon> t) {
324 intake_talon_ = ::std::move(t);
325 }
326
327 void set_wrist_talon(::std::unique_ptr<Talon> t) {
328 wrist_talon_ = ::std::move(t);
329 }
330
331 private:
332 virtual void Read() override {
333 ::frc971::control_loops::claw_queue.output.FetchAnother();
334 }
335
336 virtual void Write() override {
337 auto &queue = ::frc971::control_loops::claw_queue.output;
338 LOG_STRUCT(DEBUG, "will output", *queue);
339 intake_talon_->Set(queue->intake_voltage / 12.0);
340 wrist_talon_->Set(queue->voltage / 12.0);
341 }
342
343 virtual void Stop() override {
344 LOG(WARNING, "Claw output too old.\n");
345 intake_talon_->Disable();
346 wrist_talon_->Disable();
347 }
348
349 ::std::unique_ptr<Talon> intake_talon_;
350 ::std::unique_ptr<Talon> wrist_talon_;
351};
352
Brian Silverman1f90d672015-01-26 20:20:45 -0500353// TODO(brian): Replace this with ::std::make_unique once all our toolchains
354// have support.
355template <class T, class... U>
356std::unique_ptr<T> make_unique(U &&... u) {
357 return std::unique_ptr<T>(new T(std::forward<U>(u)...));
358}
359
Austin Schuh010eb812014-10-25 18:06:49 -0700360class WPILibRobot : public RobotBase {
361 public:
362 virtual void StartCompetition() {
Brian Silvermand8f403a2014-12-13 19:12:04 -0500363 ::aos::InitNRT();
Brian Silverman2fe007c2014-12-28 12:20:01 -0800364 ::aos::SetCurrentThreadName("StartCompetition");
Brian Silvermand8f403a2014-12-13 19:12:04 -0500365
Brian Silverman98f6ee22015-01-26 17:50:12 -0500366 JoystickSender joystick_sender;
Austin Schuh010eb812014-10-25 18:06:49 -0700367 ::std::thread joystick_thread(::std::ref(joystick_sender));
Brian Silvermand8f403a2014-12-13 19:12:04 -0500368 ::std::unique_ptr<Compressor> compressor(new Compressor());
369 compressor->SetClosedLoopControl(true);
370
Brian Silverman98f6ee22015-01-26 17:50:12 -0500371 SensorReader reader;
Brian Silverman1f90d672015-01-26 20:20:45 -0500372 // TODO(sensors): Replace all the 99s with real port numbers.
373 reader.set_left_encoder(make_unique<Encoder>(99, 99, false, Encoder::k4X));
374 reader.set_right_encoder(make_unique<Encoder>(99, 99, false, Encoder::k4X));
Brian Silverman98f6ee22015-01-26 17:50:12 -0500375 ::std::thread reader_thread(::std::ref(reader));
376 GyroSender gyro_sender;
377 ::std::thread gyro_thread(::std::ref(gyro_sender));
378
379 DrivetrainWriter drivetrain_writer;
Brian Silvermand8f403a2014-12-13 19:12:04 -0500380 drivetrain_writer.set_left_drivetrain_talon(
381 ::std::unique_ptr<Talon>(new Talon(5)));
382 drivetrain_writer.set_right_drivetrain_talon(
383 ::std::unique_ptr<Talon>(new Talon(2)));
384 ::std::thread drivetrain_writer_thread(::std::ref(drivetrain_writer));
385
Daniel Pettiadf38432015-01-26 17:13:35 -0800386 // TODO(sensors): Get real PWM output and relay numbers for the fridge and
387 // claw.
388 FridgeWriter fridge_writer;
389 fridge_writer.set_left_arm_talon(
390 ::std::unique_ptr<Talon>(new Talon(99)));
391 fridge_writer.set_right_arm_talon(
392 ::std::unique_ptr<Talon>(new Talon(99)));
393 fridge_writer.set_left_elevator_talon(
394 ::std::unique_ptr<Talon>(new Talon(99)));
395 fridge_writer.set_right_elevator_talon(
396 ::std::unique_ptr<Talon>(new Talon(99)));
397 ::std::thread fridge_writer_thread(::std::ref(fridge_writer));
398
399 ClawWriter claw_writer;
400 claw_writer.set_intake_talon(
401 ::std::unique_ptr<Talon>(new Talon(99)));
402 claw_writer.set_wrist_talon(
403 ::std::unique_ptr<Talon>(new Talon(99)));
404 ::std::thread claw_writer_thread(::std::ref(claw_writer));
405
406 ::std::unique_ptr<::frc971::wpilib::BufferedPcm> pcm(
407 new ::frc971::wpilib::BufferedPcm());
Brian Silverman98f6ee22015-01-26 17:50:12 -0500408 SolenoidWriter solenoid_writer(pcm);
Daniel Pettiadf38432015-01-26 17:13:35 -0800409 solenoid_writer.set_fridge_grabbers_top_front(pcm->MakeSolenoid(99));
410 solenoid_writer.set_fridge_grabbers_top_back(pcm->MakeSolenoid(99));
411 solenoid_writer.set_fridge_grabbers_bottom_front(pcm->MakeSolenoid(99));
412 solenoid_writer.set_fridge_grabbers_bottom_back(pcm->MakeSolenoid(99));
413 solenoid_writer.set_claw_pinchers(pcm->MakeSolenoid(99));
Brian Silvermand8f403a2014-12-13 19:12:04 -0500414 ::std::thread solenoid_thread(::std::ref(solenoid_writer));
415
416 // Wait forever. Not much else to do...
417 PCHECK(select(0, nullptr, nullptr, nullptr, nullptr));
418
Austin Schuh010eb812014-10-25 18:06:49 -0700419 LOG(ERROR, "Exiting WPILibRobot\n");
Brian Silverman07ec88e2014-12-28 00:13:08 -0800420
Austin Schuh010eb812014-10-25 18:06:49 -0700421 joystick_sender.Quit();
422 joystick_thread.join();
Brian Silvermand8f403a2014-12-13 19:12:04 -0500423 reader.Quit();
424 reader_thread.join();
Brian Silverman07ec88e2014-12-28 00:13:08 -0800425 gyro_sender.Quit();
426 gyro_thread.join();
Brian Silvermand8f403a2014-12-13 19:12:04 -0500427
428 drivetrain_writer.Quit();
429 drivetrain_writer_thread.join();
Brian Silvermand8f403a2014-12-13 19:12:04 -0500430 solenoid_writer.Quit();
431 solenoid_thread.join();
432
Austin Schuh010eb812014-10-25 18:06:49 -0700433 ::aos::Cleanup();
434 }
435};
436
Brian Silverman98f6ee22015-01-26 17:50:12 -0500437} // namespace wpilib
438} // namespace frc971
Austin Schuhdb516032014-12-28 00:12:38 -0800439
Brian Silverman98f6ee22015-01-26 17:50:12 -0500440
441START_ROBOT_CLASS(::frc971::wpilib::WPILibRobot);