Add tool to dump CAN logs to .asc
Signed-off-by: Ravago Jones <ravagojones@gmail.com>
Change-Id: Ief4387259d8f5860d4bd6b68cf5fd2c468b3a227
diff --git a/frc971/can_logger/BUILD b/frc971/can_logger/BUILD
index a3b002a..a18a379 100644
--- a/frc971/can_logger/BUILD
+++ b/frc971/can_logger/BUILD
@@ -37,3 +37,36 @@
gen_reflections = 1,
visibility = ["//visibility:public"],
)
+
+cc_binary(
+ name = "log_to_asc",
+ srcs = [
+ "log_to_asc.cc",
+ ],
+ visibility = ["//visibility:public"],
+ deps = [
+ ":asc_logger",
+ ":can_logging_fbs",
+ "//aos:configuration",
+ "//aos:init",
+ "//aos/events/logging:log_reader",
+ "//aos/time",
+ "@com_github_google_glog//:glog",
+ ],
+)
+
+cc_library(
+ name = "asc_logger",
+ srcs = [
+ "asc_logger.cc",
+ ],
+ hdrs = [
+ "asc_logger.h",
+ ],
+ deps = [
+ ":can_logging_fbs",
+ "//aos/events:event_loop",
+ "//aos/scoped:scoped_fd",
+ "@com_github_google_glog//:glog",
+ ],
+)
diff --git a/frc971/can_logger/asc_logger.cc b/frc971/can_logger/asc_logger.cc
new file mode 100644
index 0000000..96b2e96
--- /dev/null
+++ b/frc971/can_logger/asc_logger.cc
@@ -0,0 +1,142 @@
+#include "frc971/can_logger/asc_logger.h"
+
+#include <linux/can.h>
+
+namespace frc971 {
+namespace can_logger {
+
+AscLogger::AscLogger(aos::EventLoop *event_loop, const std::string &filename)
+ : output_(filename), event_loop_(event_loop) {
+ CHECK(output_);
+ event_loop->MakeWatcher(
+ "/can", [this](const CanFrame &frame) { HandleFrame(frame); });
+}
+
+void AscLogger::HandleFrame(const CanFrame &frame) {
+ if (!first_frame_monotonic_) {
+ aos::monotonic_clock::time_point time(
+ std::chrono::nanoseconds(frame.monotonic_timestamp_ns()));
+
+ first_frame_monotonic_ = time;
+
+ WriteHeader(output_, event_loop_->realtime_now());
+ }
+
+ WriteFrame(output_, frame);
+}
+
+void AscLogger::WriteHeader(std::ostream &file,
+ aos::realtime_clock::time_point start_time) {
+ file << "date " << start_time << "\n";
+ file << "base hex timetamps absolute\n";
+ file << "no internal events logged\n";
+}
+
+namespace {
+
+static const unsigned char len2dlc[] = {
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, /* 0 - 8 */
+ 9, 9, 9, 9, /* 9 - 12 */
+ 10, 10, 10, 10, /* 13 - 16 */
+ 11, 11, 11, 11, /* 17 - 20 */
+ 12, 12, 12, 12, /* 21 - 24 */
+ 13, 13, 13, 13, 13, 13, 13, 13, /* 25 - 32 */
+ 14, 14, 14, 14, 14, 14, 14, 14, /* 33 - 40 */
+ 14, 14, 14, 14, 14, 14, 14, 14, /* 41 - 48 */
+ 15, 15, 15, 15, 15, 15, 15, 15, /* 49 - 56 */
+ 15, 15, 15, 15, 15, 15, 15, 15}; /* 57 - 64 */
+
+/* map the sanitized data length to an appropriate data length code */
+unsigned char can_fd_len2dlc(unsigned char len) {
+ if (len > 64) return 0xF;
+
+ return len2dlc[len];
+}
+
+#define ASC_F_RTR 0x00000010
+#define ASC_F_FDF 0x00001000
+#define ASC_F_BRS 0x00002000
+#define ASC_F_ESI 0x00004000
+
+} // namespace
+
+void AscLogger::WriteFrame(std::ostream &file, const CanFrame &frame) {
+ aos::monotonic_clock::time_point frame_timestamp(
+ std::chrono::nanoseconds(frame.monotonic_timestamp_ns()));
+
+ std::chrono::duration<double> time(frame_timestamp -
+ first_frame_monotonic_.value());
+
+ // TODO: maybe this should not be hardcoded
+ const int device_id = 1;
+
+ // EFF/SFF is set in the MSB
+ bool is_extended_frame_format = frame.can_id() & CAN_EFF_FLAG;
+
+ uint32_t id_mask = is_extended_frame_format ? CAN_EFF_MASK : CAN_SFF_MASK;
+ int id = frame.can_id() & id_mask;
+
+ // data length code
+ int dlc = can_fd_len2dlc(frame.data()->size());
+
+ const uint8_t flags = frame.flags();
+
+ uint32_t asc_flags = 0;
+
+ // Mark it as a CAN FD Frame
+ asc_flags = ASC_F_FDF;
+
+ // Pass through the bit rate switch flag
+ // indicates that it used a second bitrate for payload data
+ if (flags & CANFD_BRS) {
+ asc_flags |= ASC_F_BRS;
+ }
+
+ // ESI is the error state indicator of the transmitting node
+ if (flags & CANFD_ESI) {
+ asc_flags |= ASC_F_ESI;
+ }
+
+ file << std::fixed << time.count() << " ";
+
+ file << "CANFD ";
+
+ file << std::setfill(' ') << std::setw(3) << std::right << device_id << " ";
+
+ file << "Rx ";
+
+ std::stringstream formatted_id;
+ formatted_id << std::hex << std::uppercase << std::setfill('0') << id
+ << std::dec;
+ if (is_extended_frame_format) {
+ formatted_id << "x";
+ }
+
+ file << std::setfill(' ') << std::setw(11) << formatted_id.str();
+ file << " ";
+
+ file << ((flags & CANFD_BRS) ? '1' : '0') << " ";
+ file << ((flags & CANFD_ESI) ? '1' : '0') << " ";
+
+ file << std::hex << std::nouppercase << dlc << std::dec << " ";
+
+ // actual data length
+ file << std::setfill(' ') << std::setw(2) << frame.data()->size();
+
+ file << std::hex << std::uppercase;
+ for (uint8_t byte : *frame.data()) {
+ file << " " << std::setfill('0') << std::setw(2) << static_cast<int>(byte);
+ }
+ file << std::dec;
+
+ // these are hardcoded in log2asc too, I don't know why
+ file << " 130000 130 ";
+ file << std::setfill(' ') << std::setw(8) << std::hex << asc_flags
+ << std::dec;
+ file << " 0 0 0 0 0";
+
+ file << "\n";
+}
+
+} // namespace can_logger
+} // namespace frc971
diff --git a/frc971/can_logger/asc_logger.h b/frc971/can_logger/asc_logger.h
new file mode 100644
index 0000000..213aa25
--- /dev/null
+++ b/frc971/can_logger/asc_logger.h
@@ -0,0 +1,38 @@
+#ifndef FRC971_CAN_LOGGER_ASC_LOGGER_H_
+#define FRC971_CAN_LOGGER_ASC_LOGGER_H_
+
+#include <iomanip>
+#include <iostream>
+
+#include "aos/events/event_loop.h"
+#include "frc971/can_logger/can_logging_generated.h"
+#include "gflags/gflags.h"
+#include "glog/logging.h"
+
+namespace frc971 {
+namespace can_logger {
+
+class AscLogger {
+ public:
+ AscLogger(aos::EventLoop *event_loop, const std::string &filename);
+
+ private:
+ void HandleFrame(const CanFrame &frame);
+
+ // This implementation attempts to duplicate the output of can-utils/log2asc
+ void WriteFrame(std::ostream &file, const CanFrame &frame);
+
+ static void WriteHeader(std::ostream &file,
+ aos::realtime_clock::time_point start_time);
+
+ std::optional<aos::monotonic_clock::time_point> first_frame_monotonic_;
+
+ std::ofstream output_;
+
+ aos::EventLoop *event_loop_;
+};
+
+} // namespace can_logger
+} // namespace frc971
+
+#endif // FRC971_CAN_LOGGER_ASC_LOGGER_H_
diff --git a/frc971/can_logger/log_to_asc.cc b/frc971/can_logger/log_to_asc.cc
new file mode 100644
index 0000000..73ddd4e
--- /dev/null
+++ b/frc971/can_logger/log_to_asc.cc
@@ -0,0 +1,57 @@
+#include "aos/configuration.h"
+#include "aos/events/event_loop_generated.h"
+#include "aos/events/logging/log_reader.h"
+#include "aos/init.h"
+#include "frc971/can_logger/asc_logger.h"
+#include "frc971/can_logger/can_logging_generated.h"
+
+DEFINE_string(node, "", "Node to replay from the perspective of.");
+DEFINE_string(output_path, "/tmp/can_log.asc", "Log to output.");
+
+int main(int argc, char *argv[]) {
+ aos::InitGoogle(&argc, &argv);
+
+ const std::vector<aos::logger::LogFile> logfiles =
+ aos::logger::SortParts(aos::logger::FindLogs(argc, argv));
+ CHECK(!logfiles.empty());
+ const std::string logger_node = logfiles.at(0).logger_node;
+ bool all_logs_from_same_node = true;
+ for (const aos::logger::LogFile &log : logfiles) {
+ if (log.logger_node != logger_node) {
+ all_logs_from_same_node = false;
+ break;
+ }
+ }
+ std::string replay_node = FLAGS_node;
+ if (replay_node.empty() && all_logs_from_same_node) {
+ LOG(INFO) << "Guessing \"" << logger_node
+ << "\" as node given that --node was not specified.";
+ replay_node = logger_node;
+ }
+
+ std::optional<aos::FlatbufferDetachedBuffer<aos::Configuration>> config;
+
+ aos::logger::LogReader reader(
+ logfiles, config.has_value() ? &config.value().message() : nullptr);
+ aos::SimulatedEventLoopFactory factory(reader.configuration());
+ reader.RegisterWithoutStarting(&factory);
+
+ const aos::Node *node =
+ (replay_node.empty() ||
+ !aos::configuration::MultiNode(reader.configuration()))
+ ? nullptr
+ : aos::configuration::GetNode(reader.configuration(), replay_node);
+
+ std::unique_ptr<aos::EventLoop> can_event_loop;
+ CHECK(!FLAGS_output_path.empty());
+ std::unique_ptr<frc971::can_logger::AscLogger> relogger;
+
+ factory.GetNodeEventLoopFactory(node)->OnStartup([&relogger, &can_event_loop,
+ &reader, node]() {
+ can_event_loop = reader.event_loop_factory()->MakeEventLoop("can", node);
+ relogger = std::make_unique<frc971::can_logger::AscLogger>(
+ can_event_loop.get(), FLAGS_output_path);
+ });
+ reader.event_loop_factory()->Run();
+ reader.Deregister();
+}