blob: b82e2b54d45bfecef59eb1569e74f3d148efaee5 [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 Silvermanda45b6c2014-12-28 11:36:50 -080036
Brian Silvermancb77f232014-12-19 21:48:36 -080037#include "Encoder.h"
Brian Silvermancb77f232014-12-19 21:48:36 -080038#include "Talon.h"
39#include "DriverStation.h"
40#include "AnalogInput.h"
Brian Silvermancb77f232014-12-19 21:48:36 -080041#include "Compressor.h"
42#include "RobotBase.h"
Austin Schuh010eb812014-10-25 18:06:49 -070043
44#ifndef M_PI
45#define M_PI 3.14159265358979323846
46#endif
47
48using ::aos::util::SimpleLogInterval;
Brian Silvermanada5f2c2015-02-01 02:41:14 -050049using ::frc971::control_loops::drivetrain_queue;
Austin Schuh010eb812014-10-25 18:06:49 -070050using ::aos::util::WrappingCounter;
51
52namespace frc971 {
Brian Silvermanda45b6c2014-12-28 11:36:50 -080053namespace wpilib {
Austin Schuh010eb812014-10-25 18:06:49 -070054
Austin Schuh010eb812014-10-25 18:06:49 -070055double drivetrain_translate(int32_t in) {
Austin Schuhdb516032014-12-28 00:12:38 -080056 return static_cast<double>(in) /
Daniel Pettiadf38432015-01-26 17:13:35 -080057 (256.0 /*cpr*/ * 4.0 /*4x*/) *
58 (20.0 / 50.0 /*output stage*/) *
Austin Schuhdb516032014-12-28 00:12:38 -080059 // * constants::GetValues().drivetrain_encoder_ratio
Daniel Pettiadf38432015-01-26 17:13:35 -080060 (4 /*wheel diameter*/ * 2.54 / 100.0 * M_PI);
61}
62
63double arm_translate(int32_t in) {
64 return static_cast<double>(in) /
65 (512.0 /*cpr*/ * 4.0 /*4x*/) *
66 (14.0 / 17.0 /*output sprockets*/) *
67 (18.0 / 48.0 /*encoder pulleys*/) *
68 (2 * M_PI /*radians*/);
69}
70
71double arm_pot_translate(int32_t in) {
72 return static_cast<double>(in) /
73 (14.0 / 17.0 /*output sprockets*/) *
74 (5.0 /*volts*/ / 5.0 /*turns*/) *
75 (2 * M_PI /*radians*/);
76}
77
78double elevator_translate(int32_t in) {
79 return static_cast<double>(in) /
80 (512.0 /*cpr*/ * 4.0 /*4x*/) *
81 (14.0 / 84.0 /*output stage*/) *
82 (32 * 5 / 2.54 / 10 /*pulley circumference (in)*/);
83}
84
85double elevator_pot_translate(int32_t in) {
86 return static_cast<double>(in) /
87 (32 * 5 / 2.54 / 10 /*pulley circumference (in)*/) *
88 (5.0 /*volts*/ / 5.0 /*turns*/);
89}
90
91double claw_translate(int32_t in) {
92 return static_cast<double>(in) /
93 (512.0 /*cpr*/ * 4.0 /*4x*/) *
94 (16.0 / 72.0 /*output sprockets*/) *
95 (2 * M_PI /*radians*/);
96}
97
98double claw_pot_translate(int32_t in) {
99 return static_cast<double>(in) /
100 (16.0 / 72.0 /*output sprockets*/) *
101 (5.0 /*volts*/ / 5.0 /*turns*/) *
102 (2 * M_PI /*radians*/);
Austin Schuh010eb812014-10-25 18:06:49 -0700103}
104
Austin Schuh010eb812014-10-25 18:06:49 -0700105class SensorReader {
106 public:
Brian Silverman1f90d672015-01-26 20:20:45 -0500107 SensorReader() {
Austin Schuh010eb812014-10-25 18:06:49 -0700108 filter_.SetPeriodNanoSeconds(100000);
Austin Schuh010eb812014-10-25 18:06:49 -0700109 }
110
Brian Silverman1f90d672015-01-26 20:20:45 -0500111 void set_left_encoder(::std::unique_ptr<Encoder> left_encoder) {
112 left_encoder_ = ::std::move(left_encoder);
113 }
114
115 void set_right_encoder(::std::unique_ptr<Encoder> right_encoder) {
116 right_encoder_ = ::std::move(right_encoder);
117 }
118
Austin Schuh010eb812014-10-25 18:06:49 -0700119 void operator()() {
Brian Silverman2fe007c2014-12-28 12:20:01 -0800120 ::aos::SetCurrentThreadName("SensorReader");
121
Brian Silverman1f90d672015-01-26 20:20:45 -0500122 static const int kPriority = 30;
123 //static const int kInterruptPriority = 55;
Austin Schuh010eb812014-10-25 18:06:49 -0700124
Brian Silverman2fe007c2014-12-28 12:20:01 -0800125 ::aos::SetCurrentThreadRealtimePriority(kPriority);
Austin Schuh010eb812014-10-25 18:06:49 -0700126 while (run_) {
Brian Silverman20141f92015-01-05 17:39:01 -0800127 ::aos::time::PhasedLoopXMS(5, 9000);
Austin Schuh010eb812014-10-25 18:06:49 -0700128 RunIteration();
Austin Schuh010eb812014-10-25 18:06:49 -0700129 }
Austin Schuh010eb812014-10-25 18:06:49 -0700130 }
131
132 void RunIteration() {
Austin Schuh010eb812014-10-25 18:06:49 -0700133 DriverStation *ds = DriverStation::GetInstance();
134
Austin Schuhdb516032014-12-28 00:12:38 -0800135 if (ds->IsSysActive()) {
Austin Schuh010eb812014-10-25 18:06:49 -0700136 auto message = ::aos::controls::output_check_received.MakeMessage();
137 // TODO(brians): Actually read a pulse value from the roboRIO.
138 message->pwm_value = 0;
139 message->pulse_length = -1;
140 LOG_STRUCT(DEBUG, "received", *message);
141 message.Send();
142 }
143
Brian Silvermanada5f2c2015-02-01 02:41:14 -0500144 drivetrain_queue.position.MakeWithBuilder()
Austin Schuh010eb812014-10-25 18:06:49 -0700145 .right_encoder(drivetrain_translate(right_encoder_->GetRaw()))
146 .left_encoder(-drivetrain_translate(left_encoder_->GetRaw()))
Austin Schuh010eb812014-10-25 18:06:49 -0700147 .battery_voltage(ds->GetBatteryVoltage())
148 .Send();
149
Brian Silvermand8f403a2014-12-13 19:12:04 -0500150 // Signal that we are alive.
Austin Schuh010eb812014-10-25 18:06:49 -0700151 ::aos::controls::sensor_generation.MakeWithBuilder()
152 .reader_pid(getpid())
153 .cape_resets(0)
154 .Send();
155 }
156
157 void Quit() { run_ = false; }
158
159 private:
Austin Schuh010eb812014-10-25 18:06:49 -0700160 ::std::unique_ptr<Encoder> left_encoder_;
161 ::std::unique_ptr<Encoder> right_encoder_;
Austin Schuh010eb812014-10-25 18:06:49 -0700162
Brian Silverman1f90d672015-01-26 20:20:45 -0500163 ::std::atomic<bool> run_{true};
Austin Schuh010eb812014-10-25 18:06:49 -0700164 DigitalGlitchFilter filter_;
165};
166
Brian Silvermand8f403a2014-12-13 19:12:04 -0500167class SolenoidWriter {
Austin Schuh010eb812014-10-25 18:06:49 -0700168 public:
Brian Silvermand8f403a2014-12-13 19:12:04 -0500169 SolenoidWriter(const ::std::unique_ptr<BufferedPcm> &pcm)
Daniel Pettiadf38432015-01-26 17:13:35 -0800170 : pcm_(pcm),
171 fridge_(".frc971.control_loops.fridge.output"),
172 claw_(".frc971.control_loops.claw.output") {}
Brian Silvermand8f403a2014-12-13 19:12:04 -0500173
Daniel Pettiadf38432015-01-26 17:13:35 -0800174 void set_fridge_grabbers_top_front(::std::unique_ptr<BufferedSolenoid> s) {
175 fridge_grabbers_top_front_ = ::std::move(s);
Austin Schuh010eb812014-10-25 18:06:49 -0700176 }
177
Daniel Pettiadf38432015-01-26 17:13:35 -0800178 void set_fridge_grabbers_top_back(::std::unique_ptr<BufferedSolenoid> s) {
179 fridge_grabbers_top_back_ = ::std::move(s);
180 }
181
182 void set_fridge_grabbers_bottom_front(
183 ::std::unique_ptr<BufferedSolenoid> s) {
184 fridge_grabbers_bottom_front_ = ::std::move(s);
185 }
186
187 void set_fridge_grabbers_bottom_back(
188 ::std::unique_ptr<BufferedSolenoid> s) {
189 fridge_grabbers_bottom_back_ = ::std::move(s);
190 }
191
192 void set_claw_pinchers(::std::unique_ptr<BufferedSolenoid> s) {
193 claw_pinchers_ = ::std::move(s);
Brian Silvermand8f403a2014-12-13 19:12:04 -0500194 }
Austin Schuh010eb812014-10-25 18:06:49 -0700195
Brian Silvermand8f403a2014-12-13 19:12:04 -0500196 void operator()() {
197 ::aos::SetCurrentThreadName("Solenoids");
198 ::aos::SetCurrentThreadRealtimePriority(30);
199
200 while (run_) {
201 ::aos::time::PhasedLoopXMS(20, 1000);
202
203 {
Daniel Pettiadf38432015-01-26 17:13:35 -0800204 fridge_.FetchLatest();
205 if (fridge_.get()) {
206 LOG_STRUCT(DEBUG, "solenoids", *fridge_);
207 fridge_grabbers_top_front_->Set(fridge_->grabbers.top_front);
208 fridge_grabbers_top_back_->Set(fridge_->grabbers.top_back);
209 fridge_grabbers_bottom_front_->Set(fridge_->grabbers.bottom_front);
210 fridge_grabbers_bottom_back_->Set(fridge_->grabbers.bottom_back);
211 }
212 }
213
214 {
215 claw_.FetchLatest();
216 if (claw_.get()) {
217 LOG_STRUCT(DEBUG, "solenoids", *claw_);
218 claw_pinchers_->Set(claw_->rollers_closed);
Brian Silvermand8f403a2014-12-13 19:12:04 -0500219 }
220 }
221
Brian Silvermand8f403a2014-12-13 19:12:04 -0500222 pcm_->Flush();
Austin Schuh010eb812014-10-25 18:06:49 -0700223 }
224 }
225
Brian Silvermand8f403a2014-12-13 19:12:04 -0500226 void Quit() { run_ = false; }
Austin Schuh010eb812014-10-25 18:06:49 -0700227
Brian Silvermand8f403a2014-12-13 19:12:04 -0500228 private:
229 const ::std::unique_ptr<BufferedPcm> &pcm_;
Daniel Pettiadf38432015-01-26 17:13:35 -0800230 ::std::unique_ptr<BufferedSolenoid> fridge_grabbers_top_front_;
231 ::std::unique_ptr<BufferedSolenoid> fridge_grabbers_top_back_;
232 ::std::unique_ptr<BufferedSolenoid> fridge_grabbers_bottom_front_;
233 ::std::unique_ptr<BufferedSolenoid> fridge_grabbers_bottom_back_;
234 ::std::unique_ptr<BufferedSolenoid> claw_pinchers_;
Austin Schuh010eb812014-10-25 18:06:49 -0700235
Daniel Pettiadf38432015-01-26 17:13:35 -0800236 ::aos::Queue<::frc971::control_loops::FridgeQueue::Output> fridge_;
237 ::aos::Queue<::frc971::control_loops::ClawQueue::Output> claw_;
Austin Schuh010eb812014-10-25 18:06:49 -0700238
Brian Silvermand8f403a2014-12-13 19:12:04 -0500239 ::std::atomic<bool> run_{true};
240};
241
242class DrivetrainWriter : public LoopOutputHandler {
243 public:
244 void set_left_drivetrain_talon(::std::unique_ptr<Talon> t) {
245 left_drivetrain_talon_ = ::std::move(t);
Austin Schuh010eb812014-10-25 18:06:49 -0700246 }
247
Brian Silvermand8f403a2014-12-13 19:12:04 -0500248 void set_right_drivetrain_talon(::std::unique_ptr<Talon> t) {
249 right_drivetrain_talon_ = ::std::move(t);
250 }
Austin Schuh010eb812014-10-25 18:06:49 -0700251
Brian Silvermand8f403a2014-12-13 19:12:04 -0500252 private:
253 virtual void Read() override {
Brian Silvermanada5f2c2015-02-01 02:41:14 -0500254 ::frc971::control_loops::drivetrain_queue.output.FetchAnother();
Brian Silvermand8f403a2014-12-13 19:12:04 -0500255 }
256
257 virtual void Write() override {
Brian Silvermanada5f2c2015-02-01 02:41:14 -0500258 auto &queue = ::frc971::control_loops::drivetrain_queue.output;
Brian Silvermand8f403a2014-12-13 19:12:04 -0500259 LOG_STRUCT(DEBUG, "will output", *queue);
260 left_drivetrain_talon_->Set(-queue->left_voltage / 12.0);
261 right_drivetrain_talon_->Set(queue->right_voltage / 12.0);
262 }
263
264 virtual void Stop() override {
265 LOG(WARNING, "drivetrain output too old\n");
266 left_drivetrain_talon_->Disable();
267 right_drivetrain_talon_->Disable();
268 }
269
Austin Schuh010eb812014-10-25 18:06:49 -0700270 ::std::unique_ptr<Talon> left_drivetrain_talon_;
Brian Silvermand8f403a2014-12-13 19:12:04 -0500271 ::std::unique_ptr<Talon> right_drivetrain_talon_;
272};
273
Daniel Pettiadf38432015-01-26 17:13:35 -0800274class FridgeWriter : public LoopOutputHandler {
275 public:
276 void set_left_arm_talon(::std::unique_ptr<Talon> t) {
277 left_arm_talon_ = ::std::move(t);
278 }
279
280 void set_right_arm_talon(::std::unique_ptr<Talon> t) {
281 right_arm_talon_ = ::std::move(t);
282 }
283
284 void set_left_elevator_talon(::std::unique_ptr<Talon> t) {
285 left_elevator_talon_ = ::std::move(t);
286 }
287
288 void set_right_elevator_talon(::std::unique_ptr<Talon> t) {
289 right_elevator_talon_ = ::std::move(t);
290 }
291
292 private:
293 virtual void Read() override {
294 ::frc971::control_loops::fridge_queue.output.FetchAnother();
295 }
296
297 virtual void Write() override {
298 auto &queue = ::frc971::control_loops::fridge_queue.output;
299 LOG_STRUCT(DEBUG, "will output", *queue);
300 left_arm_talon_->Set(-queue->left_arm / 12.0);
301 right_arm_talon_->Set(queue->right_arm / 12.0);
302 left_elevator_talon_->Set(-queue->left_elevator / 12.0);
303 right_elevator_talon_->Set(queue->right_elevator / 12.0);
304 }
305
306 virtual void Stop() override {
307 LOG(WARNING, "Fridge output too old.\n");
308 left_arm_talon_->Disable();
309 right_arm_talon_->Disable();
310 left_elevator_talon_->Disable();
311 right_elevator_talon_->Disable();
312 }
313
314 ::std::unique_ptr<Talon> left_arm_talon_;
315 ::std::unique_ptr<Talon> right_arm_talon_;
316 ::std::unique_ptr<Talon> left_elevator_talon_;
317 ::std::unique_ptr<Talon> right_elevator_talon_;
318};
319
320class ClawWriter : public LoopOutputHandler {
321 public:
322 void set_intake_talon(::std::unique_ptr<Talon> t) {
323 intake_talon_ = ::std::move(t);
324 }
325
326 void set_wrist_talon(::std::unique_ptr<Talon> t) {
327 wrist_talon_ = ::std::move(t);
328 }
329
330 private:
331 virtual void Read() override {
332 ::frc971::control_loops::claw_queue.output.FetchAnother();
333 }
334
335 virtual void Write() override {
336 auto &queue = ::frc971::control_loops::claw_queue.output;
337 LOG_STRUCT(DEBUG, "will output", *queue);
338 intake_talon_->Set(queue->intake_voltage / 12.0);
339 wrist_talon_->Set(queue->voltage / 12.0);
340 }
341
342 virtual void Stop() override {
343 LOG(WARNING, "Claw output too old.\n");
344 intake_talon_->Disable();
345 wrist_talon_->Disable();
346 }
347
348 ::std::unique_ptr<Talon> intake_talon_;
349 ::std::unique_ptr<Talon> wrist_talon_;
350};
351
Brian Silverman1f90d672015-01-26 20:20:45 -0500352// TODO(brian): Replace this with ::std::make_unique once all our toolchains
353// have support.
354template <class T, class... U>
355std::unique_ptr<T> make_unique(U &&... u) {
356 return std::unique_ptr<T>(new T(std::forward<U>(u)...));
357}
358
Austin Schuh010eb812014-10-25 18:06:49 -0700359class WPILibRobot : public RobotBase {
360 public:
361 virtual void StartCompetition() {
Brian Silvermand8f403a2014-12-13 19:12:04 -0500362 ::aos::InitNRT();
Brian Silverman2fe007c2014-12-28 12:20:01 -0800363 ::aos::SetCurrentThreadName("StartCompetition");
Brian Silvermand8f403a2014-12-13 19:12:04 -0500364
Brian Silverman98f6ee22015-01-26 17:50:12 -0500365 JoystickSender joystick_sender;
Austin Schuh010eb812014-10-25 18:06:49 -0700366 ::std::thread joystick_thread(::std::ref(joystick_sender));
Brian Silvermand8f403a2014-12-13 19:12:04 -0500367 ::std::unique_ptr<Compressor> compressor(new Compressor());
368 compressor->SetClosedLoopControl(true);
369
Brian Silverman98f6ee22015-01-26 17:50:12 -0500370 SensorReader reader;
Brian Silverman1f90d672015-01-26 20:20:45 -0500371 // TODO(sensors): Replace all the 99s with real port numbers.
372 reader.set_left_encoder(make_unique<Encoder>(99, 99, false, Encoder::k4X));
373 reader.set_right_encoder(make_unique<Encoder>(99, 99, false, Encoder::k4X));
Brian Silverman98f6ee22015-01-26 17:50:12 -0500374 ::std::thread reader_thread(::std::ref(reader));
375 GyroSender gyro_sender;
376 ::std::thread gyro_thread(::std::ref(gyro_sender));
377
378 DrivetrainWriter drivetrain_writer;
Brian Silvermand8f403a2014-12-13 19:12:04 -0500379 drivetrain_writer.set_left_drivetrain_talon(
380 ::std::unique_ptr<Talon>(new Talon(5)));
381 drivetrain_writer.set_right_drivetrain_talon(
382 ::std::unique_ptr<Talon>(new Talon(2)));
383 ::std::thread drivetrain_writer_thread(::std::ref(drivetrain_writer));
384
Daniel Pettiadf38432015-01-26 17:13:35 -0800385 // TODO(sensors): Get real PWM output and relay numbers for the fridge and
386 // claw.
387 FridgeWriter fridge_writer;
388 fridge_writer.set_left_arm_talon(
389 ::std::unique_ptr<Talon>(new Talon(99)));
390 fridge_writer.set_right_arm_talon(
391 ::std::unique_ptr<Talon>(new Talon(99)));
392 fridge_writer.set_left_elevator_talon(
393 ::std::unique_ptr<Talon>(new Talon(99)));
394 fridge_writer.set_right_elevator_talon(
395 ::std::unique_ptr<Talon>(new Talon(99)));
396 ::std::thread fridge_writer_thread(::std::ref(fridge_writer));
397
398 ClawWriter claw_writer;
399 claw_writer.set_intake_talon(
400 ::std::unique_ptr<Talon>(new Talon(99)));
401 claw_writer.set_wrist_talon(
402 ::std::unique_ptr<Talon>(new Talon(99)));
403 ::std::thread claw_writer_thread(::std::ref(claw_writer));
404
405 ::std::unique_ptr<::frc971::wpilib::BufferedPcm> pcm(
406 new ::frc971::wpilib::BufferedPcm());
Brian Silverman98f6ee22015-01-26 17:50:12 -0500407 SolenoidWriter solenoid_writer(pcm);
Daniel Pettiadf38432015-01-26 17:13:35 -0800408 solenoid_writer.set_fridge_grabbers_top_front(pcm->MakeSolenoid(99));
409 solenoid_writer.set_fridge_grabbers_top_back(pcm->MakeSolenoid(99));
410 solenoid_writer.set_fridge_grabbers_bottom_front(pcm->MakeSolenoid(99));
411 solenoid_writer.set_fridge_grabbers_bottom_back(pcm->MakeSolenoid(99));
412 solenoid_writer.set_claw_pinchers(pcm->MakeSolenoid(99));
Brian Silvermand8f403a2014-12-13 19:12:04 -0500413 ::std::thread solenoid_thread(::std::ref(solenoid_writer));
414
415 // Wait forever. Not much else to do...
416 PCHECK(select(0, nullptr, nullptr, nullptr, nullptr));
417
Austin Schuh010eb812014-10-25 18:06:49 -0700418 LOG(ERROR, "Exiting WPILibRobot\n");
Brian Silverman07ec88e2014-12-28 00:13:08 -0800419
Austin Schuh010eb812014-10-25 18:06:49 -0700420 joystick_sender.Quit();
421 joystick_thread.join();
Brian Silvermand8f403a2014-12-13 19:12:04 -0500422 reader.Quit();
423 reader_thread.join();
Brian Silverman07ec88e2014-12-28 00:13:08 -0800424 gyro_sender.Quit();
425 gyro_thread.join();
Brian Silvermand8f403a2014-12-13 19:12:04 -0500426
427 drivetrain_writer.Quit();
428 drivetrain_writer_thread.join();
Brian Silvermand8f403a2014-12-13 19:12:04 -0500429 solenoid_writer.Quit();
430 solenoid_thread.join();
431
Austin Schuh010eb812014-10-25 18:06:49 -0700432 ::aos::Cleanup();
433 }
434};
435
Brian Silverman98f6ee22015-01-26 17:50:12 -0500436} // namespace wpilib
437} // namespace frc971
Austin Schuhdb516032014-12-28 00:12:38 -0800438
Brian Silverman98f6ee22015-01-26 17:50:12 -0500439
440START_ROBOT_CLASS(::frc971::wpilib::WPILibRobot);