blob: 634a4b287d15d519ee3b0b060edd3e7173327757 [file] [log] [blame]
Comran Morsheddaf69232016-04-20 22:25:37 -07001#include "y2016/dashboard/dashboard.h"
2
3#include <iostream>
4#include <sstream>
5#include <string>
6#include <thread>
7#include <vector>
8#include <complex>
9
10#include "seasocks/Server.h"
11
12#include "aos/linux_code/init.h"
13#include "aos/common/logging/logging.h"
14#include "aos/common/time.h"
15#include "aos/common/util/phased_loop.h"
16#include "aos/common/mutex.h"
17
18#include "y2016/actors/autonomous_action.q.h"
19#include "y2016/vision/vision.q.h"
20#include "y2016/control_loops/superstructure/superstructure.q.h"
21#include "y2016/queues/ball_detector.q.h"
22
23#include "y2016/dashboard/embedded.h"
24
25namespace y2016 {
26namespace dashboard {
27namespace big_indicator {
28constexpr int kBlack = 0;
29constexpr int kBallIntaked = 1;
30constexpr int kAiming = 2;
31constexpr int kLockedOn = 3;
32} // namespace big_indicator
33
34namespace superstructure_indicator {
35constexpr int kBlack = 0;
36constexpr int kNotZeroed = 1;
37constexpr int kEstopped = 2;
38} // namespace superstructure_indicator
39
40// Define the following if we want to use a local www directory and feed in
41// dummy data.
42//#define DASHBOARD_TESTING
43
44// Define the following if we want to read from the vision queue, which has
45// caused problems in the past when auto aiming that still need to be addressed.
46//#define DASHBOARD_READ_VISION_QUEUE
47
48DataCollector::DataCollector()
49 : cur_raw_data_("no data"),
50 sample_id_(0),
51 measure_index_(0),
52 overflow_id_(20) {}
53
54void DataCollector::RunIteration() {
55 ::aos::MutexLocker locker(&mutex_);
56 measure_index_ = 0;
57
58// Add recorded data here. /////
59#ifdef DASHBOARD_TESTING
60 // The following feeds data into the webserver when we do not have a process
61 // feeding data to the queues.
62 // To test, we are sending three streams holding randomly generated numbers.
63 AddPoint("test", ::std::rand() % 4);
64 AddPoint("test2", ::std::rand() % 3);
65 AddPoint("test3", ::std::rand() % 3 - 1);
66 (void)big_indicator::kBlack;
67 (void)big_indicator::kBallIntaked;
68 (void)big_indicator::kAiming;
69 (void)big_indicator::kLockedOn;
70 (void)superstructure_indicator::kBlack;
71 (void)superstructure_indicator::kNotZeroed;
72 (void)superstructure_indicator::kEstopped;
73#else
74 int big_indicator = big_indicator::kBlack;
75 int superstructure_state_indicator = superstructure_indicator::kBlack;
76 // We should never have a -1 here, so this is an indicator that somethings
77 // gone wrong with reading the auto queue.
78 int auto_mode_indicator = -1;
79
80 ::y2016::actors::auto_mode.FetchLatest();
81 ::y2016::control_loops::superstructure_queue.status.FetchLatest();
82 ::y2016::sensors::ball_detector.FetchLatest();
83
84 if (::y2016::sensors::ball_detector.get()) {
85 // TODO(comran): Grab detected voltage from joystick_reader. Except this
86 // value may not change, so it may be worth it to keep it as it is right
87 // now.
88 if (::y2016::sensors::ball_detector->voltage > 2.5) big_indicator = big_indicator::kBallIntaked;
89#ifdef DASHBOARD_READ_VISION_QUEUE
90 // Caused glitching with auto-aim at NASA, so be cautious with this until
91 // we find a good fix.
92 if (::y2016::vision::vision_status.FetchLatest()) {
93 if (::y2016::vision::vision_status->left_image_valid ||
94 ::y2016::vision::vision_status->right_image_valid) {
95 big_indicator = big_indicator::kAiming;
96 if (::std::abs(::y2016::vision::vision_status->horizontal_angle) <
97 0.002) {
98 big_indicator = big_indicator::kLockedOn;
99 }
100 }
101 }
102#else
103 (void)big_indicator::kAiming;
104 (void)big_indicator::kLockedOn;
105#endif
106 }
107
108 if (::y2016::control_loops::superstructure_queue.status.get()) {
109 if (!::y2016::control_loops::superstructure_queue.status->zeroed) {
110 superstructure_state_indicator = superstructure_indicator::kNotZeroed;
111 }
112 if (::y2016::control_loops::superstructure_queue.status->estopped) {
113 superstructure_state_indicator = superstructure_indicator::kEstopped;
114 }
115 }
116
117 if (::y2016::actors::auto_mode.get()) {
118 auto_mode_indicator = ::y2016::actors::auto_mode->mode;
119 }
120
121 AddPoint("big indicator", big_indicator);
122 AddPoint("superstructure state indicator", superstructure_state_indicator);
123 AddPoint("auto mode indicator", auto_mode_indicator);
124#endif
125
126 // Get ready for next iteration. /////
127 sample_id_++;
128}
129
130void DataCollector::AddPoint(const ::std::string &name, double value) {
131 // Mutex should be locked when this method is called to synchronize packets.
132 CHECK(mutex_.OwnedBySelf());
133
134 size_t index = GetIndex(sample_id_);
135
136 ItemDatapoint datapoint{value, ::aos::time::Time::Now()};
137 if (measure_index_ >= sample_items_.size()) {
138 // New item in our data table.
139 ::std::vector<ItemDatapoint> datapoints;
140 SampleItem item{name, datapoints};
141 sample_items_.emplace_back(item);
142 } else if (index >= sample_items_.at(measure_index_).datapoints.size()) {
143 // New data point for an already existing item.
144 sample_items_.at(measure_index_).datapoints.emplace_back(datapoint);
145 } else {
146 // Overwrite an already existing data point for an already existing item.
147 sample_items_.at(measure_index_).datapoints.at(index) = datapoint;
148 }
149
150 measure_index_++;
151}
152
153::std::string DataCollector::Fetch(int32_t from_sample) {
154 ::aos::MutexLocker locker(&mutex_);
155
156 ::std::stringstream message;
157 message.precision(10);
158
159 // Send out the names of each item when requested by the client.
160 // Example: *item_one_name,item_two_name,item_three_name
161 if (from_sample == 0) {
162 message << "*"; // Begin name packet.
163
164 // Add comma-separated list of names.
165 for (size_t cur_data_name = 0; cur_data_name < sample_items_.size();
166 cur_data_name++) {
167 if (cur_data_name > 0) {
168 message << ",";
169 }
170 message << sample_items_.at(cur_data_name).name;
171 }
172 return message.str();
173 }
174
175 // Send out one sample containing the data.
176 // Samples are split with dollar signs, info with percent signs, and
177 // measurements with commas.
178 // Example of data with two samples: $289%2803.13%10,67$290%2803.14%12,68
179
180 // Note that we are ignoring the from_sample being sent to keep up with the
181 // live data without worrying about client lag.
182 int32_t cur_sample = sample_id_ - 2;
183 int32_t adjusted_index = GetIndex(cur_sample);
184 message << "$"; // Begin data packet.
185
186 // Make sure we are not out of range.
187 if (static_cast<size_t>(adjusted_index) < sample_items_.at(0).datapoints.size()){
188 message << cur_sample << "%"
189 << sample_items_.at(0).datapoints.at(adjusted_index).time.ToSeconds()
190 << "%"; // Send time.
191 // Add comma-separated list of data points.
192 for (size_t cur_measure = 0; cur_measure < sample_items_.size();
193 cur_measure++) {
194 if (cur_measure > 0) {
195 message << ",";
196 }
197 message << sample_items_.at(cur_measure)
198 .datapoints.at(adjusted_index)
199 .value;
200 }
201 }
202
203 return message.str();
204}
205
206size_t DataCollector::GetIndex(size_t sample_id) {
207 return sample_id % overflow_id_;
208}
209
210void DataCollector::operator()() {
211 ::aos::SetCurrentThreadName("DashboardData");
212
213 while (run_) {
214 ::aos::time::PhasedLoopXMS(100, 0);
215 RunIteration();
216 }
217}
218
219SocketHandler::SocketHandler()
220 : data_collector_thread_(::std::ref(data_collector_)) {}
221
222void SocketHandler::onConnect(seasocks::WebSocket *connection) {
223 connections_.insert(connection);
224 LOG(INFO, "Connected: %s : %s\n", connection->getRequestUri().c_str(),
225 seasocks::formatAddress(connection->getRemoteAddress()).c_str());
226}
227
228void SocketHandler::onData(seasocks::WebSocket *connection, const char *data) {
229 int32_t from_sample = atoi(data);
230
231 ::std::string send_data = data_collector_.Fetch(from_sample);
232 connection->send(send_data.c_str());
233}
234
235void SocketHandler::onDisconnect(seasocks::WebSocket *connection) {
236 connections_.erase(connection);
237 LOG(INFO, "Disconnected: %s : %s\n", connection->getRequestUri().c_str(),
238 seasocks::formatAddress(connection->getRemoteAddress()).c_str());
239}
240
241void SocketHandler::Quit() {
242 data_collector_.Quit();
243 data_collector_thread_.join();
244}
245
246SeasocksLogger::SeasocksLogger(Level min_level_to_log)
247 : PrintfLogger(min_level_to_log) {}
248
249void SeasocksLogger::log(Level level, const char *message) {
250 // Convert Seasocks error codes to AOS.
251 log_level aos_level;
252 switch (level) {
253 case seasocks::Logger::INFO:
254 aos_level = INFO;
255 break;
256 case seasocks::Logger::WARNING:
257 aos_level = WARNING;
258 break;
259 case seasocks::Logger::ERROR:
260 case seasocks::Logger::SEVERE:
261 aos_level = ERROR;
262 break;
263 case seasocks::Logger::DEBUG:
264 case seasocks::Logger::ACCESS:
265 default:
266 aos_level = DEBUG;
267 break;
268 }
269 LOG(aos_level, "Seasocks: %s\n", message);
270}
271
272} // namespace dashboard
273} // namespace y2016
274
275int main(int, char *[]) {
276 ::aos::InitNRT();
277
278 ::seasocks::Server server(::std::shared_ptr<seasocks::Logger>(
279 new ::y2016::dashboard::SeasocksLogger(seasocks::Logger::INFO)));
280 ::y2016::dashboard::SocketHandler socket_handler;
281
282 server.addWebSocketHandler(
283 "/ws",
284 ::std::shared_ptr<::y2016::dashboard::SocketHandler>(&socket_handler));
285#ifdef DASHBOARD_TESTING
286 server.serve("www", 1180);
287#else
288 // Absolute directory of www folder on the robot.
289 server.serve("/home/admin/robot_code/www", 1180);
290#endif
291
292 socket_handler.Quit();
293
294 ::aos::Cleanup();
295 return 0;
296}