blob: 207c481b0e14715adf00544889dccc6713b0ceb4 [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"
Austin Schuh010eb812014-10-25 18:06:49 -070023#include "frc971/constants.h"
Daniel Pettiaece37f2014-10-25 17:13:44 -070024#include "frc971/shifter_hall_effect.h"
Austin Schuh010eb812014-10-25 18:06:49 -070025
Brian Silvermanda45b6c2014-12-28 11:36:50 -080026#include "frc971/wpilib/hall_effect.h"
27#include "frc971/wpilib/joystick_sender.h"
Brian Silvermand8f403a2014-12-13 19:12:04 -050028#include "frc971/wpilib/loop_output_handler.h"
29#include "frc971/wpilib/buffered_solenoid.h"
30#include "frc971/wpilib/buffered_pcm.h"
Brian Silverman07ec88e2014-12-28 00:13:08 -080031#include "frc971/wpilib/gyro_sender.h"
Brian Silvermanda45b6c2014-12-28 11:36:50 -080032
Brian Silvermancb77f232014-12-19 21:48:36 -080033#include "Encoder.h"
Brian Silvermancb77f232014-12-19 21:48:36 -080034#include "Talon.h"
35#include "DriverStation.h"
36#include "AnalogInput.h"
Brian Silvermancb77f232014-12-19 21:48:36 -080037#include "Compressor.h"
38#include "RobotBase.h"
Austin Schuh010eb812014-10-25 18:06:49 -070039
40#ifndef M_PI
41#define M_PI 3.14159265358979323846
42#endif
43
44using ::aos::util::SimpleLogInterval;
45using ::frc971::control_loops::drivetrain;
Austin Schuh010eb812014-10-25 18:06:49 -070046using ::aos::util::WrappingCounter;
47
48namespace frc971 {
Brian Silvermanda45b6c2014-12-28 11:36:50 -080049namespace wpilib {
Austin Schuh010eb812014-10-25 18:06:49 -070050
Brian Silvermanda45b6c2014-12-28 11:36:50 -080051// TODO(brian): Split this out into a separate file once DMA is in.
Austin Schuh010eb812014-10-25 18:06:49 -070052class EdgeCounter {
53 public:
54 EdgeCounter(int priority, Encoder *encoder, HallEffect *input,
Brian Silvermanb073f242014-09-08 16:29:57 -040055 ::aos::stl_mutex *mutex)
Austin Schuh010eb812014-10-25 18:06:49 -070056 : priority_(priority),
57 encoder_(encoder),
58 input_(input),
59 mutex_(mutex),
60 run_(true),
61 any_interrupt_count_(0) {
62 thread_.reset(new ::std::thread(::std::ref(*this)));
63 }
64
65 // Waits for interrupts, locks the mutex, and updates the internal state.
66 // Updates the any_interrupt_count count when the interrupt comes in without
67 // the lock.
Austin Schuhdb516032014-12-28 00:12:38 -080068 void operator()() {
Brian Silverman2fe007c2014-12-28 12:20:01 -080069 ::aos::SetCurrentThreadName("EdgeCounter_" +
70 ::std::to_string(input_->GetChannel()));
Austin Schuh010eb812014-10-25 18:06:49 -070071
72 input_->RequestInterrupts();
73 input_->SetUpSourceEdge(true, true);
74
75 {
Brian Silvermanb073f242014-09-08 16:29:57 -040076 ::std::unique_lock<::aos::stl_mutex> mutex_guard(*mutex_);
Austin Schuh010eb812014-10-25 18:06:49 -070077 current_value_ = input_->GetHall();
78 }
79
Brian Silverman2fe007c2014-12-28 12:20:01 -080080 ::aos::SetCurrentThreadRealtimePriority(priority_);
Austin Schuh010eb812014-10-25 18:06:49 -070081 InterruptableSensorBase::WaitResult result = InterruptableSensorBase::kBoth;
82 while (run_) {
83 result = input_->WaitForInterrupt(
84 0.1, result != InterruptableSensorBase::kTimeout);
85 if (result == InterruptableSensorBase::kTimeout) {
86 continue;
87 }
88 ++any_interrupt_count_;
89
Brian Silvermanb073f242014-09-08 16:29:57 -040090 ::std::unique_lock<::aos::stl_mutex> mutex_guard(*mutex_);
Austin Schuh010eb812014-10-25 18:06:49 -070091 int32_t encoder_value = encoder_->GetRaw();
92 bool hall_value = input_->GetHall();
93 if (current_value_ != hall_value) {
94 if (hall_value) {
95 ++positive_interrupt_count_;
96 last_positive_encoder_value_ = encoder_value;
97 } else {
98 ++negative_interrupt_count_;
99 last_negative_encoder_value_ = encoder_value;
100 }
101 } else {
102 LOG(WARNING, "Detected spurious edge on %d. Dropping it.\n",
103 input_->GetChannel());
104 }
105
106 current_value_ = hall_value;
107 }
108 }
109
110 // Updates the internal hall effect value given this new observation.
111 // The mutex provided at construction time must be held during this operation.
112 void set_polled_value(bool value) {
113 polled_value_ = value;
114 bool miss_match = (value != current_value_);
115 if (miss_match && last_miss_match_) {
116 current_value_ = value;
117 last_miss_match_ = false;
118 } else {
119 last_miss_match_ = miss_match;
120 }
121 }
122
123 // Signals the thread to quit next time it gets an interrupt.
124 void Quit() {
125 run_ = false;
126 thread_->join();
127 }
128
129 // Returns the total number of interrupts since construction time. This
130 // should be done without the mutex held.
131 int any_interrupt_count() const { return any_interrupt_count_; }
132 // Returns the current interrupt edge counts and encoder values.
133 // The mutex provided at construction time must be held during this operation.
134 int positive_interrupt_count() const { return positive_interrupt_count_; }
135 int negative_interrupt_count() const { return negative_interrupt_count_; }
136 int32_t last_positive_encoder_value() const {
137 return last_positive_encoder_value_;
138 }
139 int32_t last_negative_encoder_value() const {
140 return last_negative_encoder_value_;
141 }
142 // Returns the current polled value.
143 bool polled_value() const { return polled_value_; }
144
145 private:
146 int priority_;
147 Encoder *encoder_;
148 HallEffect *input_;
Brian Silvermanb073f242014-09-08 16:29:57 -0400149 ::aos::stl_mutex *mutex_;
Austin Schuh010eb812014-10-25 18:06:49 -0700150 ::std::atomic<bool> run_;
151
152 ::std::atomic<int> any_interrupt_count_;
153
154 // The following variables represent the current state. They must be
155 // synchronized by mutex_;
156 bool current_value_ = false;
157 bool polled_value_ = false;
158 bool last_miss_match_ = true;
159 int positive_interrupt_count_ = 0;
160 int negative_interrupt_count_ = 0;
161 int32_t last_positive_encoder_value_ = 0;
162 int32_t last_negative_encoder_value_ = 0;
163
164 ::std::unique_ptr<::std::thread> thread_;
165};
166
Brian Silvermanda45b6c2014-12-28 11:36:50 -0800167// This class will synchronize sampling edges on a bunch of HallEffects with
Austin Schuh010eb812014-10-25 18:06:49 -0700168// the periodic poll.
169//
170// The data is provided to subclasses by calling SaveState when the state is
171// consistent and ready.
Brian Silvermanda45b6c2014-12-28 11:36:50 -0800172//
173// TODO(brian): Split this out into a separate file once DMA is in.
Austin Schuh010eb812014-10-25 18:06:49 -0700174template <int num_sensors>
175class PeriodicHallSynchronizer {
176 public:
177 PeriodicHallSynchronizer(
178 const char *name, int priority, int interrupt_priority,
179 ::std::unique_ptr<Encoder> encoder,
180 ::std::array<::std::unique_ptr<HallEffect>, num_sensors> *sensors)
181 : name_(name),
182 priority_(priority),
183 encoder_(::std::move(encoder)),
184 run_(true) {
185 for (int i = 0; i < num_sensors; ++i) {
186 sensors_[i] = ::std::move((*sensors)[i]);
187 edge_counters_[i] = ::std::unique_ptr<EdgeCounter>(new EdgeCounter(
188 interrupt_priority, encoder_.get(), sensors_[i].get(), &mutex_));
189 }
190 }
191
192 const char *name() const { return name_.c_str(); }
193
194 void StartThread() { thread_.reset(new ::std::thread(::std::ref(*this))); }
195
196 // Called when the state is consistent and up to date.
197 virtual void SaveState() = 0;
198
199 // Starts a sampling iteration. See RunIteration for usage.
200 void StartIteration() {
201 // Start by capturing the current interrupt counts.
202 for (int i = 0; i < num_sensors; ++i) {
203 interrupt_counts_[i] = edge_counters_[i]->any_interrupt_count();
204 }
205
206 {
207 // Now, update the encoder and sensor values.
Brian Silvermanb073f242014-09-08 16:29:57 -0400208 ::std::unique_lock<::aos::stl_mutex> mutex_guard(mutex_);
Austin Schuh010eb812014-10-25 18:06:49 -0700209 encoder_value_ = encoder_->GetRaw();
210 for (int i = 0; i < num_sensors; ++i) {
211 edge_counters_[i]->set_polled_value(sensors_[i]->GetHall());
212 }
213 }
214 }
215
216 // Attempts to finish a sampling iteration. See RunIteration for usage.
217 // Returns true if the iteration succeeded, and false otherwise.
218 bool TryFinishingIteration() {
219 // Make sure no interrupts have occurred while we were waiting. If they
220 // have, we are in an inconsistent state and need to try again.
Brian Silvermanb073f242014-09-08 16:29:57 -0400221 ::std::unique_lock<::aos::stl_mutex> mutex_guard(mutex_);
Austin Schuh010eb812014-10-25 18:06:49 -0700222 bool retry = false;
223 for (int i = 0; i < num_sensors; ++i) {
224 retry = retry || (interrupt_counts_[i] !=
225 edge_counters_[i]->any_interrupt_count());
226 }
227 if (!retry) {
228 SaveState();
229 return true;
230 }
231 LOG(WARNING, "Got an interrupt while sampling encoder %s, retrying\n",
232 name());
233 return false;
234 }
235
236 void RunIteration() {
237 while (true) {
238 StartIteration();
239
240 // Wait more than the amount of time it takes for a digital input change
241 // to go from visible to software to having triggered an interrupt.
242 ::aos::time::SleepFor(::aos::time::Time::InUS(120));
243
244 if (TryFinishingIteration()) {
245 return;
246 }
247 }
248 }
249
250 void operator()() {
Brian Silverman2fe007c2014-12-28 12:20:01 -0800251 ::aos::SetCurrentThreadName("HallSync" + ::std::to_string(num_sensors));
Brian Silvermanda45b6c2014-12-28 11:36:50 -0800252 ::aos::SetCurrentThreadRealtimePriority(priority_);
Austin Schuh010eb812014-10-25 18:06:49 -0700253 while (run_) {
254 ::aos::time::PhasedLoopXMS(10, 9000);
255 RunIteration();
256 }
257 }
258
259 void Quit() {
260 run_ = false;
261 for (int i = 0; i < num_sensors; ++i) {
262 edge_counters_[i]->Quit();
263 }
264 if (thread_) {
265 thread_->join();
266 }
267 }
268
269 protected:
270 // These values are only safe to fetch from inside SaveState()
271 int32_t encoder_value() const { return encoder_value_; }
272 ::std::array<::std::unique_ptr<EdgeCounter>, num_sensors> &edge_counters() {
273 return edge_counters_;
274 }
275
276 private:
277 // A descriptive name for error messages.
278 ::std::string name_;
279 // The priority of the polling thread.
280 int priority_;
281 // The Encoder to sample.
282 ::std::unique_ptr<Encoder> encoder_;
283 // A list of all the digital inputs.
284 ::std::array<::std::unique_ptr<HallEffect>, num_sensors> sensors_;
285 // The mutex used to synchronize all the state.
Brian Silvermanb073f242014-09-08 16:29:57 -0400286 ::aos::stl_mutex mutex_;
Austin Schuh010eb812014-10-25 18:06:49 -0700287 ::std::atomic<bool> run_;
288
289 // The state.
290 // The current encoder value.
291 int32_t encoder_value_ = 0;
292 // The current edge counters.
293 ::std::array<::std::unique_ptr<EdgeCounter>, num_sensors> edge_counters_;
294
295 ::std::unique_ptr<::std::thread> thread_;
296 ::std::array<int, num_sensors> interrupt_counts_;
297};
298
299double drivetrain_translate(int32_t in) {
Austin Schuhdb516032014-12-28 00:12:38 -0800300 return static_cast<double>(in) /
301 (256.0 /*cpr*/ * 2.0 /*2x. Stupid WPILib*/) *
302 (18.0 / 50.0 /*output stage*/) * (56.0 / 30.0 /*encoder gears*/)
303 // * constants::GetValues().drivetrain_encoder_ratio
304 *
305 (3.5 /*wheel diameter*/ * 2.54 / 100.0 * M_PI);
Austin Schuh010eb812014-10-25 18:06:49 -0700306}
307
Austin Schuh010eb812014-10-25 18:06:49 -0700308class SensorReader {
309 public:
310 SensorReader()
Brian Silverman20141f92015-01-05 17:39:01 -0800311 : left_encoder_(new Encoder(11, 10, false, Encoder::k2X)), // E0
Brian Silvermancb77f232014-12-19 21:48:36 -0800312 right_encoder_(new Encoder(13, 12, false, Encoder::k2X)), // E1
Austin Schuh010eb812014-10-25 18:06:49 -0700313 run_(true) {
314 filter_.SetPeriodNanoSeconds(100000);
Austin Schuh010eb812014-10-25 18:06:49 -0700315 }
316
317 void operator()() {
Brian Silverman2fe007c2014-12-28 12:20:01 -0800318 ::aos::SetCurrentThreadName("SensorReader");
319
Austin Schuh010eb812014-10-25 18:06:49 -0700320 const int kPriority = 30;
Brian Silverman20141f92015-01-05 17:39:01 -0800321 //const int kInterruptPriority = 55;
Austin Schuh010eb812014-10-25 18:06:49 -0700322
Brian Silverman2fe007c2014-12-28 12:20:01 -0800323 ::aos::SetCurrentThreadRealtimePriority(kPriority);
Austin Schuh010eb812014-10-25 18:06:49 -0700324 while (run_) {
Brian Silverman20141f92015-01-05 17:39:01 -0800325 ::aos::time::PhasedLoopXMS(5, 9000);
Austin Schuh010eb812014-10-25 18:06:49 -0700326 RunIteration();
Austin Schuh010eb812014-10-25 18:06:49 -0700327 }
Austin Schuh010eb812014-10-25 18:06:49 -0700328 }
329
330 void RunIteration() {
Austin Schuh010eb812014-10-25 18:06:49 -0700331 DriverStation *ds = DriverStation::GetInstance();
332
Austin Schuhdb516032014-12-28 00:12:38 -0800333 if (ds->IsSysActive()) {
Austin Schuh010eb812014-10-25 18:06:49 -0700334 auto message = ::aos::controls::output_check_received.MakeMessage();
335 // TODO(brians): Actually read a pulse value from the roboRIO.
336 message->pwm_value = 0;
337 message->pulse_length = -1;
338 LOG_STRUCT(DEBUG, "received", *message);
339 message.Send();
340 }
341
Austin Schuh010eb812014-10-25 18:06:49 -0700342 // TODO(austin): Calibrate the shifter constants again.
Brian Silverman20141f92015-01-05 17:39:01 -0800343 // TODO(sensors): Hook up the new dog position sensors.
Austin Schuh010eb812014-10-25 18:06:49 -0700344 drivetrain.position.MakeWithBuilder()
345 .right_encoder(drivetrain_translate(right_encoder_->GetRaw()))
346 .left_encoder(-drivetrain_translate(left_encoder_->GetRaw()))
Brian Silverman20141f92015-01-05 17:39:01 -0800347 .left_shifter_position(0)
348 .right_shifter_position(0)
Austin Schuh010eb812014-10-25 18:06:49 -0700349 .battery_voltage(ds->GetBatteryVoltage())
350 .Send();
351
Brian Silvermand8f403a2014-12-13 19:12:04 -0500352 // Signal that we are alive.
Austin Schuh010eb812014-10-25 18:06:49 -0700353 ::aos::controls::sensor_generation.MakeWithBuilder()
354 .reader_pid(getpid())
355 .cape_resets(0)
356 .Send();
357 }
358
359 void Quit() { run_ = false; }
360
361 private:
362 ::std::unique_ptr<AnalogInput> auto_selector_analog_;
363
364 ::std::unique_ptr<Encoder> left_encoder_;
365 ::std::unique_ptr<Encoder> right_encoder_;
Austin Schuh010eb812014-10-25 18:06:49 -0700366
367 ::std::atomic<bool> run_;
368 DigitalGlitchFilter filter_;
369};
370
Brian Silvermand8f403a2014-12-13 19:12:04 -0500371class SolenoidWriter {
Austin Schuh010eb812014-10-25 18:06:49 -0700372 public:
Brian Silvermand8f403a2014-12-13 19:12:04 -0500373 SolenoidWriter(const ::std::unique_ptr<BufferedPcm> &pcm)
Brian Silverman20141f92015-01-05 17:39:01 -0800374 : pcm_(pcm), drivetrain_(".frc971.control_loops.drivetrain.output") {}
Brian Silvermand8f403a2014-12-13 19:12:04 -0500375
376 void set_drivetrain_left(::std::unique_ptr<BufferedSolenoid> s) {
377 drivetrain_left_ = ::std::move(s);
Austin Schuh010eb812014-10-25 18:06:49 -0700378 }
379
Brian Silvermand8f403a2014-12-13 19:12:04 -0500380 void set_drivetrain_right(::std::unique_ptr<BufferedSolenoid> s) {
381 drivetrain_right_ = ::std::move(s);
382 }
Austin Schuh010eb812014-10-25 18:06:49 -0700383
Brian Silvermand8f403a2014-12-13 19:12:04 -0500384 void operator()() {
385 ::aos::SetCurrentThreadName("Solenoids");
386 ::aos::SetCurrentThreadRealtimePriority(30);
387
388 while (run_) {
389 ::aos::time::PhasedLoopXMS(20, 1000);
390
391 {
392 drivetrain_.FetchLatest();
393 if (drivetrain_.get()) {
394 LOG_STRUCT(DEBUG, "solenoids", *drivetrain_);
395 drivetrain_left_->Set(drivetrain_->left_high);
396 drivetrain_right_->Set(drivetrain_->right_high);
397 }
398 }
399
Brian Silvermand8f403a2014-12-13 19:12:04 -0500400 pcm_->Flush();
Austin Schuh010eb812014-10-25 18:06:49 -0700401 }
402 }
403
Brian Silvermand8f403a2014-12-13 19:12:04 -0500404 void Quit() { run_ = false; }
Austin Schuh010eb812014-10-25 18:06:49 -0700405
Brian Silvermand8f403a2014-12-13 19:12:04 -0500406 private:
407 const ::std::unique_ptr<BufferedPcm> &pcm_;
408 ::std::unique_ptr<BufferedSolenoid> drivetrain_left_;
409 ::std::unique_ptr<BufferedSolenoid> drivetrain_right_;
Austin Schuh010eb812014-10-25 18:06:49 -0700410
Brian Silvermand8f403a2014-12-13 19:12:04 -0500411 ::aos::Queue<::frc971::control_loops::Drivetrain::Output> drivetrain_;
Austin Schuh010eb812014-10-25 18:06:49 -0700412
Brian Silvermand8f403a2014-12-13 19:12:04 -0500413 ::std::atomic<bool> run_{true};
414};
415
416class DrivetrainWriter : public LoopOutputHandler {
417 public:
418 void set_left_drivetrain_talon(::std::unique_ptr<Talon> t) {
419 left_drivetrain_talon_ = ::std::move(t);
Austin Schuh010eb812014-10-25 18:06:49 -0700420 }
421
Brian Silvermand8f403a2014-12-13 19:12:04 -0500422 void set_right_drivetrain_talon(::std::unique_ptr<Talon> t) {
423 right_drivetrain_talon_ = ::std::move(t);
424 }
Austin Schuh010eb812014-10-25 18:06:49 -0700425
Brian Silvermand8f403a2014-12-13 19:12:04 -0500426 private:
427 virtual void Read() override {
428 ::frc971::control_loops::drivetrain.output.FetchAnother();
429 }
430
431 virtual void Write() override {
432 auto &queue = ::frc971::control_loops::drivetrain.output;
433 LOG_STRUCT(DEBUG, "will output", *queue);
434 left_drivetrain_talon_->Set(-queue->left_voltage / 12.0);
435 right_drivetrain_talon_->Set(queue->right_voltage / 12.0);
436 }
437
438 virtual void Stop() override {
439 LOG(WARNING, "drivetrain output too old\n");
440 left_drivetrain_talon_->Disable();
441 right_drivetrain_talon_->Disable();
442 }
443
Austin Schuh010eb812014-10-25 18:06:49 -0700444 ::std::unique_ptr<Talon> left_drivetrain_talon_;
Brian Silvermand8f403a2014-12-13 19:12:04 -0500445 ::std::unique_ptr<Talon> right_drivetrain_talon_;
446};
447
Austin Schuh010eb812014-10-25 18:06:49 -0700448class WPILibRobot : public RobotBase {
449 public:
450 virtual void StartCompetition() {
Brian Silvermand8f403a2014-12-13 19:12:04 -0500451 ::aos::InitNRT();
Brian Silverman2fe007c2014-12-28 12:20:01 -0800452 ::aos::SetCurrentThreadName("StartCompetition");
Brian Silvermand8f403a2014-12-13 19:12:04 -0500453
Brian Silverman98f6ee22015-01-26 17:50:12 -0500454 JoystickSender joystick_sender;
Austin Schuh010eb812014-10-25 18:06:49 -0700455 ::std::thread joystick_thread(::std::ref(joystick_sender));
Brian Silvermand8f403a2014-12-13 19:12:04 -0500456 ::std::unique_ptr<Compressor> compressor(new Compressor());
457 compressor->SetClosedLoopControl(true);
458
Brian Silverman98f6ee22015-01-26 17:50:12 -0500459 SensorReader reader;
460 ::std::thread reader_thread(::std::ref(reader));
461 GyroSender gyro_sender;
462 ::std::thread gyro_thread(::std::ref(gyro_sender));
463
464 DrivetrainWriter drivetrain_writer;
Brian Silvermand8f403a2014-12-13 19:12:04 -0500465 drivetrain_writer.set_left_drivetrain_talon(
466 ::std::unique_ptr<Talon>(new Talon(5)));
467 drivetrain_writer.set_right_drivetrain_talon(
468 ::std::unique_ptr<Talon>(new Talon(2)));
469 ::std::thread drivetrain_writer_thread(::std::ref(drivetrain_writer));
470
Brian Silverman98f6ee22015-01-26 17:50:12 -0500471 ::std::unique_ptr<BufferedPcm> pcm(new BufferedPcm());
472 SolenoidWriter solenoid_writer(pcm);
Brian Silvermand8f403a2014-12-13 19:12:04 -0500473 solenoid_writer.set_drivetrain_left(pcm->MakeSolenoid(6));
474 solenoid_writer.set_drivetrain_right(pcm->MakeSolenoid(7));
Brian Silvermand8f403a2014-12-13 19:12:04 -0500475 ::std::thread solenoid_thread(::std::ref(solenoid_writer));
476
477 // Wait forever. Not much else to do...
478 PCHECK(select(0, nullptr, nullptr, nullptr, nullptr));
479
Austin Schuh010eb812014-10-25 18:06:49 -0700480 LOG(ERROR, "Exiting WPILibRobot\n");
Brian Silverman07ec88e2014-12-28 00:13:08 -0800481
Austin Schuh010eb812014-10-25 18:06:49 -0700482 joystick_sender.Quit();
483 joystick_thread.join();
Brian Silvermand8f403a2014-12-13 19:12:04 -0500484 reader.Quit();
485 reader_thread.join();
Brian Silverman07ec88e2014-12-28 00:13:08 -0800486 gyro_sender.Quit();
487 gyro_thread.join();
Brian Silvermand8f403a2014-12-13 19:12:04 -0500488
489 drivetrain_writer.Quit();
490 drivetrain_writer_thread.join();
Brian Silvermand8f403a2014-12-13 19:12:04 -0500491 solenoid_writer.Quit();
492 solenoid_thread.join();
493
Austin Schuh010eb812014-10-25 18:06:49 -0700494 ::aos::Cleanup();
495 }
496};
497
Brian Silverman98f6ee22015-01-26 17:50:12 -0500498} // namespace wpilib
499} // namespace frc971
Austin Schuhdb516032014-12-28 00:12:38 -0800500
Brian Silverman98f6ee22015-01-26 17:50:12 -0500501
502START_ROBOT_CLASS(::frc971::wpilib::WPILibRobot);