blob: 6cdb3034589bdaffd031f1db428633a2d46ba6e4 [file] [log] [blame]
Comran Morshedfe7f9ea2015-02-19 23:52:57 +00001#include "seasocks/PrintfLogger.h"
Comran Morshedfe7f9ea2015-02-19 23:52:57 +00002#include "seasocks/WebSocket.h"
Comran Morshedc4ce9512015-03-08 11:51:09 +00003#include "seasocks/Server.h"
Comran Morshedfe7f9ea2015-02-19 23:52:57 +00004
Comran Morshedfe7f9ea2015-02-19 23:52:57 +00005#include <iostream>
Comran Morshedfe7f9ea2015-02-19 23:52:57 +00006#include <sstream>
7#include <string>
Comran Morshedc4ce9512015-03-08 11:51:09 +00008#include <thread>
9#include <vector>
Comran Morshedfe7f9ea2015-02-19 23:52:57 +000010
Comran Morshedc4ce9512015-03-08 11:51:09 +000011#include "aos/linux_code/init.h"
12#include "aos/common/logging/logging.h"
13#include "aos/common/time.h"
14#include "aos/common/util/phased_loop.h"
15#include "aos/common/mutex.h"
Comran Morshedfe7f9ea2015-02-19 23:52:57 +000016
Comran Morshedc4ce9512015-03-08 11:51:09 +000017#include "frc971/control_loops/claw/claw.q.h"
18#include "frc971/control_loops/fridge/fridge.q.h"
Comran Morshedfe7f9ea2015-02-19 23:52:57 +000019
Comran Morshedc4ce9512015-03-08 11:51:09 +000020#include "frc971/http_status/http_status.h"
21
22namespace frc971 {
23namespace http_status {
24
25// TODO(comran): Make some of these separate libraries & document them better.
26
27HTTPStatusMessage::HTTPStatusMessage()
28 : sample_id_(0),
29 measure_index_(0),
30 overflow_id_(200),
31 num_samples_per_packet_(50) {}
32
33void HTTPStatusMessage::NextSample() {
34 int32_t adjusted_index = GetIndex(sample_id_);
35
36 ::aos::time::Time time_now = ::aos::time::Time::Now();
37
38 if (sample_id_ < overflow_id_) {
39 sample_times_.emplace_back(time_now);
40 data_values_.emplace_back(::std::vector<double>());
41 } else {
42 sample_times_[adjusted_index] = time_now;
43 }
44
45 sample_id_++;
46 measure_index_ = 0;
47
48 CHECK(!mutex_.Lock()); // Lock the mutex so measures can be added.
49}
50
51void HTTPStatusMessage::EndSample() { mutex_.Unlock(); }
52
53int32_t HTTPStatusMessage::GetIndex(int32_t sample_id) {
54 return sample_id % overflow_id_;
55}
56
57void HTTPStatusMessage::AddMeasure(::std::string name, double value) {
58 // Mutex should be locked when this method is called to synchronize packets.
59 assert(mutex_.OwnedBySelf());
60
61 int32_t index = GetIndex(sample_id_ - 1);
62
63 if (measure_index_ >= static_cast<int32_t>(data_names_.size())) {
64 data_names_.emplace_back(name);
65 }
66
67 if (measure_index_ >= static_cast<int32_t>(data_values_.at(index).size())) {
68 data_values_.at(index).emplace_back(value);
69 } else {
70 data_values_.at(index).at(measure_index_) = value;
71 }
72 measure_index_++;
73}
74
75::std::string HTTPStatusMessage::Fetch(size_t from_sample) {
76 ::aos::MutexLocker locker(&mutex_);
77
78 ::std::stringstream message;
79 message.precision(10); // Cap how precise the time/measurement data is.
80
81 // To save space, data is being sent with a custom protocol over to the
82 // client.
83 // Initially, a message containing all the names of the measurements is sent
84 // and is preceeded with a *.
85 // Names begin with a star and are split with commas.
86
87 // Example: *test,test2
88 if (static_cast<int32_t>(from_sample) == -1) {
89 message << "*";
90 for (int32_t cur_data_name = 0;
91 cur_data_name < static_cast<int32_t>(data_names_.size());
92 cur_data_name++) {
93 if (cur_data_name > 0) {
94 message << ",";
95 }
96 message << data_names_.at(cur_data_name);
97 }
98 return message.str();
99 }
100
101 // TODO(comran): Use from_sample to determine the speed packets should be sent
102 // out to avoid skipping packets.
103 from_sample = sample_id_ - num_samples_per_packet_;
104
105 // Data packets are sent, with raw data being placed at the same index as the
106 // original index of the measurement name sent in the initial packet.
107 // Samples are split with dollar signs, info with percent signs, and
108 // measurements with commas.
109 // This special format system is helpful for debugging issues and looping
110 // through the data on the client side.
111
112 // Example of two samples that correspond with the initialized example:
113 // 289%2803.135127%10,67$290%2803.140109%12,68
114 for (int32_t cur_sample = from_sample;
115 cur_sample <
116 static_cast<int32_t>(from_sample + num_samples_per_packet_) &&
117 GetIndex(cur_sample) < static_cast<int32_t>(data_values_.size());
118 cur_sample++) {
119 if (cur_sample != static_cast<int32_t>(from_sample)) {
120 message << "$";
Comran Morshedfe7f9ea2015-02-19 23:52:57 +0000121 }
122
Comran Morshedc4ce9512015-03-08 11:51:09 +0000123 int32_t adjusted_index = GetIndex(cur_sample);
124
125 message << cur_sample << "%" << sample_times_.at(adjusted_index).ToSeconds()
126 << "%";
127 for (int32_t cur_measure = 0;
128 cur_measure < static_cast<int32_t>(data_names_.size());
129 cur_measure++) {
130 if (cur_measure > 0) {
131 message << ",";
132 }
133 message << data_values_.at(adjusted_index).at(cur_measure);
Comran Morshedfe7f9ea2015-02-19 23:52:57 +0000134 }
Comran Morshedc4ce9512015-03-08 11:51:09 +0000135 }
136 return message.str();
137}
Comran Morshedfe7f9ea2015-02-19 23:52:57 +0000138
Comran Morshedc4ce9512015-03-08 11:51:09 +0000139DataCollector::DataCollector() : cur_raw_data_("no data") {}
Comran Morshedfe7f9ea2015-02-19 23:52:57 +0000140
Comran Morshedc4ce9512015-03-08 11:51:09 +0000141void DataCollector::RunIteration() {
142 auto& fridge_queue = control_loops::fridge_queue;
143 auto& claw_queue = control_loops::claw_queue;
Comran Morshedfe7f9ea2015-02-19 23:52:57 +0000144
Comran Morshedc4ce9512015-03-08 11:51:09 +0000145 fridge_queue.status.FetchAnother();
146 claw_queue.status.FetchAnother();
Comran Morshedfe7f9ea2015-02-19 23:52:57 +0000147
Comran Morshedc4ce9512015-03-08 11:51:09 +0000148 message_.NextSample();
149 // Add recorded data here. /////
150 // NOTE: Try to use fewer than 30 measures, or the whole thing will lag.
151 // Abbreviate names if long, otherwise just use the command to get the value
152 // from the queue.
Comran Morshedfe7f9ea2015-02-19 23:52:57 +0000153
Comran Morshedc4ce9512015-03-08 11:51:09 +0000154 // TODO(comran): Make it so that the name doesn't have to be copied as a
155 // string.
Comran Morshedfe7f9ea2015-02-19 23:52:57 +0000156
Comran Morshedc4ce9512015-03-08 11:51:09 +0000157 // //// Fridge
158 // Positions
159 message_.AddMeasure("(fridge position left arm encoder)",
160 fridge_queue.position->arm.left.encoder);
161 message_.AddMeasure("(fridge position right arm encoder)",
162 fridge_queue.position->arm.right.encoder);
163 message_.AddMeasure("(fridge position left elev encoder)",
164 fridge_queue.position->elevator.left.encoder);
165 message_.AddMeasure("(fridge position right elev encoder)",
166 fridge_queue.position->elevator.right.encoder);
167 // Goals
168 message_.AddMeasure("fridge_queue.goal->profiling_type",
169 fridge_queue.goal->profiling_type);
170 message_.AddMeasure("fridge_queue.goal->angle", fridge_queue.goal->angle);
171 message_.AddMeasure("fridge_queue.goal->angular_velocity",
172 fridge_queue.goal->angular_velocity);
173 message_.AddMeasure("fridge_queue.goal->height", fridge_queue.goal->height);
174 message_.AddMeasure("fridge_queue.goal->velocity",
175 fridge_queue.goal->velocity);
176 message_.AddMeasure("fridge_queue.x", fridge_queue.goal->x);
177 message_.AddMeasure("fridge_queue.x_velocity", fridge_queue.goal->x_velocity);
178 message_.AddMeasure("fridge_queue.y", fridge_queue.goal->y);
179 message_.AddMeasure("fridge_queue.y_velocity", fridge_queue.goal->y_velocity);
180 // Statuses
181 message_.AddMeasure("fridge_queue.status->height",
182 fridge_queue.status->height);
183 message_.AddMeasure("fridge_queue.status->velocity",
184 fridge_queue.status->velocity);
185 message_.AddMeasure("fridge_queue.status->angle", fridge_queue.status->angle);
186 message_.AddMeasure("fridge_queue.status->angular_velocity",
187 fridge_queue.status->angular_velocity);
188 message_.AddMeasure("fridge_queue.status->x", fridge_queue.status->x);
189 message_.AddMeasure("fridge_queue.status->x_velocity",
190 fridge_queue.status->x_velocity);
191 message_.AddMeasure("fridge_queue.status->y", fridge_queue.status->y);
192 message_.AddMeasure("fridge_queue.status->y_velocity",
193 fridge_queue.status->y_velocity);
194 message_.AddMeasure("fridge_queue.status->state", fridge_queue.status->state);
195 message_.AddMeasure("fridge_queue.status->zeroed",
196 fridge_queue.status->zeroed);
197 message_.AddMeasure("fridge_queue.status->estopped",
198 fridge_queue.status->estopped);
199 // Outputs
200 message_.AddMeasure("fridge_queue.output->left_arm",
201 fridge_queue.output->left_arm);
202 message_.AddMeasure("fridge_queue.output->right_arm",
203 fridge_queue.output->right_arm);
204 message_.AddMeasure("fridge_queue.output->left_elevator",
205 fridge_queue.output->left_elevator);
206 message_.AddMeasure("fridge_queue.output->right_elevator",
207 fridge_queue.output->right_elevator);
208 // End recorded data. /////
209 message_.EndSample();
210}
Comran Morshedfe7f9ea2015-02-19 23:52:57 +0000211
Comran Morshedc4ce9512015-03-08 11:51:09 +0000212::std::string DataCollector::GetData(int32_t from_sample) {
213 return message_.Fetch(from_sample);
214}
Comran Morshedfe7f9ea2015-02-19 23:52:57 +0000215
Comran Morshedc4ce9512015-03-08 11:51:09 +0000216void DataCollector::operator()() {
217 ::aos::SetCurrentThreadName("HTTPStatusData");
218
219 while (run_) {
220 ::aos::time::PhasedLoopXMS(5, 0);
221 RunIteration();
222 }
223}
224
225SocketHandler::SocketHandler()
226 : data_collector_thread_(::std::ref(data_collector_)) {}
227
228void SocketHandler::onConnect(seasocks::WebSocket* connection) {
229 connections_.insert(connection);
230 LOG(INFO, "Connected: %s : %s\n", connection->getRequestUri().c_str(),
231 seasocks::formatAddress(connection->getRemoteAddress()).c_str());
232}
233
234void SocketHandler::onData(seasocks::WebSocket* connection, const char* data) {
235 int32_t from_sample = atoi(data);
236
237 ::std::string send_data = data_collector_.GetData(from_sample);
238 connection->send(send_data.c_str());
239}
240
241void SocketHandler::onDisconnect(seasocks::WebSocket* connection) {
242 connections_.erase(connection);
243 LOG(INFO, "Disconnected: %s : %s\n", connection->getRequestUri().c_str(),
244 seasocks::formatAddress(connection->getRemoteAddress()).c_str());
245}
246
247void SocketHandler::Quit() {
248 data_collector_.Quit();
249 data_collector_thread_.join();
250}
251
252SeasocksLogger::SeasocksLogger(Level min_level_to_log)
253 : PrintfLogger(min_level_to_log) {}
254
255void SeasocksLogger::log(Level level, const char* message) {
256 log_level aos_level;
257 switch (level) {
258 case seasocks::Logger::INFO:
259 aos_level = INFO;
260 break;
261 case seasocks::Logger::WARNING:
262 aos_level = WARNING;
263 break;
264 case seasocks::Logger::ERROR:
265 case seasocks::Logger::SEVERE:
266 aos_level = ERROR;
267 break;
268 case seasocks::Logger::DEBUG:
269 case seasocks::Logger::ACCESS:
270 default:
271 aos_level = DEBUG;
272 break;
273 }
274 LOG(aos_level, "Seasocks: %s\n", message);
275}
276
277} // namespace http_status
278} // namespace frc971
279
280int main(int, char* []) {
281 ::aos::InitNRT();
282
283 seasocks::Server server(::std::shared_ptr<seasocks::Logger>(
284 new frc971::http_status::SeasocksLogger(seasocks::Logger::INFO)));
285 frc971::http_status::SocketHandler socket_handler;
286
287 server.addWebSocketHandler(
288 "/ws",
289 ::std::shared_ptr<frc971::http_status::SocketHandler>(&socket_handler));
290 server.serve("www", 8080);
291
292 socket_handler.Quit();
293
294 ::aos::Cleanup();
295 return 0;
Comran Morshedfe7f9ea2015-02-19 23:52:57 +0000296}