blob: 235054b46e102369937c784be1edf081ec19a7fe [file] [log] [blame]
Austin Schuh53596192019-03-23 18:53:33 -07001#include <sys/stat.h>
2#include <sys/types.h>
3#include <unistd.h>
Brian Silvermanb42ff2d2019-03-23 15:36:39 -07004#include <array>
Brian Silvermanacdabeb2019-03-23 14:04:36 -07005#include <memory>
6#include <set>
Brian Silvermanb42ff2d2019-03-23 15:36:39 -07007#include <sstream>
Brian Silvermanacdabeb2019-03-23 14:04:36 -07008
9#include "aos/init.h"
10#include "aos/logging/logging.h"
11#include "aos/time/time.h"
Brian Silvermanb42ff2d2019-03-23 15:36:39 -070012#include "frc971/control_loops/drivetrain/drivetrain.q.h"
Brian Silvermanacdabeb2019-03-23 14:04:36 -070013#include "internal/Embedded.h"
14#include "seasocks/PrintfLogger.h"
15#include "seasocks/Server.h"
16#include "seasocks/StringUtil.h"
17#include "seasocks/WebSocket.h"
Brian Silvermanb42ff2d2019-03-23 15:36:39 -070018#include "y2019/control_loops/drivetrain/camera.q.h"
Brian Silvermanacdabeb2019-03-23 14:04:36 -070019
20namespace y2019 {
21namespace vision {
22
23class WebsocketHandler : public seasocks::WebSocket::Handler {
24 public:
25 WebsocketHandler();
26 void onConnect(seasocks::WebSocket* connection) override;
27 void onData(seasocks::WebSocket* connection, const char* data) override;
28 void onDisconnect(seasocks::WebSocket* connection) override;
29
Brian Silvermanb42ff2d2019-03-23 15:36:39 -070030 void SendData(const std::string &data);
31
Brian Silvermanacdabeb2019-03-23 14:04:36 -070032 private:
33 std::set<seasocks::WebSocket *> connections_;
34};
35
36WebsocketHandler::WebsocketHandler() {
37}
38
39void WebsocketHandler::onConnect(seasocks::WebSocket *connection) {
40 connections_.insert(connection);
41 LOG(INFO, "Connected: %s : %s\n", connection->getRequestUri().c_str(),
42 seasocks::formatAddress(connection->getRemoteAddress()).c_str());
43}
44
45void WebsocketHandler::onData(seasocks::WebSocket * /*connection*/,
46 const char *data) {
47 LOG(INFO, "Got data: %s\n", data);
48}
49
50void WebsocketHandler::onDisconnect(seasocks::WebSocket *connection) {
51 connections_.erase(connection);
52 LOG(INFO, "Disconnected: %s : %s\n", connection->getRequestUri().c_str(),
53 seasocks::formatAddress(connection->getRemoteAddress()).c_str());
54}
55
Brian Silvermanb42ff2d2019-03-23 15:36:39 -070056void WebsocketHandler::SendData(const std::string &data) {
57 for (seasocks::WebSocket *websocket : connections_) {
58 websocket->send(reinterpret_cast<const uint8_t *>(data.data()),
59 data.size());
60 }
61}
62
Brian Silvermanacdabeb2019-03-23 14:04:36 -070063// TODO(Brian): Put this somewhere shared.
64class SeasocksLogger : public seasocks::PrintfLogger {
65 public:
66 SeasocksLogger(Level min_level_to_log) : PrintfLogger(min_level_to_log) {}
67 void log(Level level, const char* message) override;
68};
69
70void SeasocksLogger::log(Level level, const char *message) {
71 // Convert Seasocks error codes to AOS.
72 log_level aos_level;
73 switch (level) {
74 case seasocks::Logger::INFO:
75 aos_level = INFO;
76 break;
77 case seasocks::Logger::WARNING:
78 aos_level = WARNING;
79 break;
80 case seasocks::Logger::ERROR:
81 case seasocks::Logger::SEVERE:
82 aos_level = ERROR;
83 break;
84 case seasocks::Logger::DEBUG:
85 case seasocks::Logger::ACCESS:
86 default:
87 aos_level = DEBUG;
88 break;
89 }
90 LOG(aos_level, "Seasocks: %s\n", message);
91}
92
Brian Silvermanb42ff2d2019-03-23 15:36:39 -070093struct LocalCameraTarget {
94 double x, y, theta;
95};
96
97struct LocalCameraFrame {
98 aos::monotonic_clock::time_point capture_time =
99 aos::monotonic_clock::min_time;
100 std::vector<LocalCameraTarget> targets;
101
102 bool IsValid(aos::monotonic_clock::time_point now) {
103 return capture_time + std::chrono::seconds(1) > now;
104 }
105};
106
107// Sends a new chunk of data to all the websocket clients.
108class UpdateData : public seasocks::Server::Runnable {
109 public:
110 UpdateData(WebsocketHandler *websocket_handler, std::string &&data)
111 : websocket_handler_(websocket_handler), data_(std::move(data)) {}
112 ~UpdateData() override = default;
113 UpdateData(const UpdateData &) = delete;
114 UpdateData &operator=(const UpdateData &) = delete;
115
116 void run() override { websocket_handler_->SendData(data_); }
117
118 private:
119 WebsocketHandler *const websocket_handler_;
120 const std::string data_;
121};
122
123void DataThread(seasocks::Server *server, WebsocketHandler *websocket_handler) {
124 auto &camera_frames = y2019::control_loops::drivetrain::camera_frames;
125 auto &drivetrain_status = frc971::control_loops::drivetrain_queue.status;
126
127 std::array<LocalCameraFrame, 5> latest_frames;
James Kuszmaul92ba0e52019-03-29 17:19:30 -0700128 std::array<aos::monotonic_clock::time_point, 5> last_target_time;
129 last_target_time.fill(aos::monotonic_clock::epoch());
Brian Silvermanb42ff2d2019-03-23 15:36:39 -0700130 aos::monotonic_clock::time_point last_send_time = aos::monotonic_clock::now();
131
132 while (true) {
133 camera_frames.FetchNextBlocking();
134 drivetrain_status.FetchLatest();
135 if (!drivetrain_status.get()) {
136 // Try again if we don't have any drivetrain statuses.
137 continue;
138 }
James Kuszmaul92ba0e52019-03-29 17:19:30 -0700139 const auto now = aos::monotonic_clock::now();
Brian Silvermanb42ff2d2019-03-23 15:36:39 -0700140
141 {
142 const auto &new_frame = *camera_frames;
143 if (new_frame.camera < latest_frames.size()) {
144 latest_frames[new_frame.camera].capture_time =
145 aos::monotonic_clock::time_point(
146 std::chrono::nanoseconds(new_frame.timestamp));
147 latest_frames[new_frame.camera].targets.clear();
James Kuszmaul92ba0e52019-03-29 17:19:30 -0700148 if (new_frame.num_targets > 0) {
149 last_target_time[new_frame.camera] = now;
150 }
Brian Silvermanb42ff2d2019-03-23 15:36:39 -0700151 for (int target = 0; target < new_frame.num_targets; ++target) {
152 latest_frames[new_frame.camera].targets.emplace_back();
153 // TODO: Do something useful.
154 }
155 }
156 }
157
Brian Silvermanb42ff2d2019-03-23 15:36:39 -0700158 if (now > last_send_time + std::chrono::milliseconds(100)) {
James Kuszmaul92ba0e52019-03-29 17:19:30 -0700159 // TODO(james): Use protobuf or the such to generate JSON rather than
160 // doing so manually.
Brian Silvermanb42ff2d2019-03-23 15:36:39 -0700161 last_send_time = now;
162 std::ostringstream stream;
163 stream << "{\n";
164
Alex Perry554cec02019-03-23 20:15:12 -0700165 stream << "\"robot\": {";
166 stream << "\"x\": " << drivetrain_status->x << ",";
167 stream << "\"y\": " << drivetrain_status->y << ",";
168 stream << "\"theta\": " << drivetrain_status->theta;
Brian Silvermanb42ff2d2019-03-23 15:36:39 -0700169 stream << "}\n";
James Kuszmaul94b3c172019-03-24 14:31:48 -0700170 stream << ",\"target\": {";
171 stream << "\"x\": " << drivetrain_status->line_follow_logging.x << ",";
172 stream << "\"y\": " << drivetrain_status->line_follow_logging.y << ",";
173 stream << "\"theta\": " << drivetrain_status->line_follow_logging.theta
174 << ",";
175 stream << "\"frozen\": " << drivetrain_status->line_follow_logging.frozen
176 << ",";
177 stream << "\"have_target\": "
178 << drivetrain_status->line_follow_logging.have_target;
179 stream << "} ";
Brian Silvermanb42ff2d2019-03-23 15:36:39 -0700180
James Kuszmaul92ba0e52019-03-29 17:19:30 -0700181 stream << ", \"last_target_age\": [";
182 bool first = true;
183 for (const auto t : last_target_time) {
184 if (!first) {
185 stream << ",";
186 }
187 first = false;
188 stream << ::std::chrono::duration_cast<::std::chrono::duration<double>>(
189 now - t).count();
190 }
191 stream << "]";
192
Brian Silvermanb42ff2d2019-03-23 15:36:39 -0700193 stream << "}";
194 server->execute(
195 std::make_shared<UpdateData>(websocket_handler, stream.str()));
196 }
197 }
198}
199
Brian Silvermanacdabeb2019-03-23 14:04:36 -0700200} // namespace vision
201} // namespace y2019
202
203int main(int, char *[]) {
204 // Make sure to reference this to force the linker to include it.
205 findEmbeddedContent("");
206
207 aos::InitNRT();
208
209 seasocks::Server server(::std::shared_ptr<seasocks::Logger>(
210 new y2019::vision::SeasocksLogger(seasocks::Logger::INFO)));
211
212 auto websocket_handler = std::make_shared<y2019::vision::WebsocketHandler>();
Brian Silvermanacdabeb2019-03-23 14:04:36 -0700213 server.addWebSocketHandler("/ws", websocket_handler);
Brian Silvermanb42ff2d2019-03-23 15:36:39 -0700214
215 std::thread data_thread{[&server, websocket_handler]() {
216 y2019::vision::DataThread(&server, websocket_handler.get());
217 }};
218
Austin Schuh53596192019-03-23 18:53:33 -0700219 // See if we are on a robot. If so, serve the robot www folder.
220 bool serve_www = false;
221 {
222 struct stat result;
223 if (stat("/home/admin/robot_code/www", &result) == 0) {
224 serve_www = true;
225 }
226 }
227
228 server.serve(
229 serve_www ? "/home/admin/robot_code/www" : "y2019/vision/server/www",
230 1180);
Brian Silvermanacdabeb2019-03-23 14:04:36 -0700231
232 return 0;
233}