forked from IQ.Lvbs/IQ.Pilot
IQ.Pilot Release Commit @ 0b96bd5
This commit is contained in:
325
iqpilot/tools/cabana/ui/tools/findsignal.cc
Normal file
325
iqpilot/tools/cabana/ui/tools/findsignal.cc
Normal file
@@ -0,0 +1,325 @@
|
||||
#include "tools/cabana/ui/tools/findsignal.h"
|
||||
|
||||
#include <chrono>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <mutex>
|
||||
#include <set>
|
||||
|
||||
#include "imgui.h"
|
||||
#include "tools/cabana/ui/threadpool.h"
|
||||
#include "tools/cabana/ui/util.h"
|
||||
#include "tools/cabana/utils/strings.h"
|
||||
#include "tools/cabana/utils/util.h"
|
||||
|
||||
namespace {
|
||||
constexpr int MAX_ROWS = 300;
|
||||
}
|
||||
|
||||
void SignalSearch::search(const std::function<bool(double)> &cmp) {
|
||||
const auto prev_sigs = !histories.empty() ? histories.back() : initial_signals;
|
||||
filtered_signals.clear();
|
||||
filtered_signals.reserve(prev_sigs.size());
|
||||
|
||||
std::mutex lock;
|
||||
parallelFor(prev_sigs.size(), [&](size_t begin, size_t end) {
|
||||
for (size_t i = begin; i < end; ++i) {
|
||||
const auto &s = prev_sigs[i];
|
||||
const auto &events = can->events(s.id);
|
||||
auto first = std::upper_bound(events.cbegin(), events.cend(), s.mono_time, CompareCanEvent());
|
||||
auto last = events.cend();
|
||||
if (last_time < std::numeric_limits<uint64_t>::max()) {
|
||||
last = std::upper_bound(events.cbegin(), events.cend(), last_time, CompareCanEvent());
|
||||
}
|
||||
|
||||
auto it = std::find_if(first, last, [&](const CanEvent *e) { return cmp(get_raw_value(e->dat, e->size, s.sig)); });
|
||||
if (it != last) {
|
||||
auto values = s.values;
|
||||
char buf[64];
|
||||
snprintf(buf, sizeof(buf), "(%.3f, %g)", can->toSeconds((*it)->mono_time), get_raw_value((*it)->dat, (*it)->size, s.sig));
|
||||
values.push_back(buf);
|
||||
std::lock_guard lk(lock);
|
||||
filtered_signals.push_back({.id = s.id, .mono_time = (*it)->mono_time, .sig = s.sig, .values = values});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
histories.push_back(filtered_signals);
|
||||
}
|
||||
|
||||
void SignalSearch::undo() {
|
||||
if (!histories.empty()) {
|
||||
histories.pop_back();
|
||||
filtered_signals.clear();
|
||||
if (!histories.empty()) filtered_signals = histories.back();
|
||||
}
|
||||
}
|
||||
|
||||
void SignalSearch::reset() {
|
||||
histories.clear();
|
||||
filtered_signals.clear();
|
||||
initial_signals.clear();
|
||||
}
|
||||
|
||||
FindSignalDlg::FindSignalDlg() {
|
||||
setTitle("Find Signal");
|
||||
}
|
||||
|
||||
FindSignalDlg::~FindSignalDlg() {
|
||||
if (search_future_.valid()) search_future_.wait();
|
||||
}
|
||||
|
||||
bool FindSignalDlg::draw() {
|
||||
if (search_future_.valid() && search_future_.wait_for(std::chrono::seconds(0)) == std::future_status::ready) {
|
||||
search_future_.get();
|
||||
searched_ = true;
|
||||
}
|
||||
searching_ = search_future_.valid();
|
||||
if (begin(ImVec2(900, 650))) {
|
||||
float group_w = (ImGui::GetContentRegionAvail().x - ImGui::GetStyle().ItemSpacing.x) / 2;
|
||||
ImGui::BeginChild("Messages", ImVec2(group_w, 0), ImGuiChildFlags_Borders | ImGuiChildFlags_AutoResizeY);
|
||||
drawMessageGroup();
|
||||
ImGui::EndChild();
|
||||
ImGui::SameLine();
|
||||
ImGui::BeginChild("Signal", ImVec2(group_w, 0), ImGuiChildFlags_Borders | ImGuiChildFlags_AutoResizeY);
|
||||
drawPropertiesGroup();
|
||||
ImGui::EndChild();
|
||||
float footer = searched_ ? ImGui::GetTextLineHeightWithSpacing() : 0;
|
||||
ImGui::BeginChild("Find signal", ImVec2(0, -footer), ImGuiChildFlags_Borders);
|
||||
drawFindGroup();
|
||||
ImGui::EndChild();
|
||||
if (searched_) {
|
||||
ImGui::Text("%zu matches. right click on an item to create signal. double click to open message",
|
||||
search_.filtered_signals.size());
|
||||
}
|
||||
}
|
||||
return end();
|
||||
}
|
||||
|
||||
void FindSignalDlg::drawMessageGroup() {
|
||||
ImGui::BeginDisabled(searching_ || !search_.histories.empty());
|
||||
ImGui::TextUnformatted("Messages");
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGui::TextUnformatted("Bus");
|
||||
ImGui::SameLine(80);
|
||||
ImGui::SetNextItemWidth(-1);
|
||||
inputText("##bus", &bus_, "comma-separated values. Leave blank for all");
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGui::TextUnformatted("Address");
|
||||
ImGui::SameLine(80);
|
||||
ImGui::SetNextItemWidth(-1);
|
||||
inputText("##address", &address_, "comma-separated hex values. Leave blank for all");
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGui::TextUnformatted("Time");
|
||||
ImGui::SameLine(80);
|
||||
ImGui::SetNextItemWidth(70);
|
||||
validatedText("##first_time", &first_time_, validateDouble);
|
||||
ImGui::SameLine();
|
||||
ImGui::TextUnformatted("-");
|
||||
ImGui::SameLine();
|
||||
ImGui::SetNextItemWidth(70);
|
||||
validatedText("##last_time", &last_time_, validateDouble);
|
||||
ImGui::SameLine();
|
||||
ImGui::TextUnformatted("seconds");
|
||||
ImGui::EndDisabled();
|
||||
}
|
||||
|
||||
void FindSignalDlg::drawPropertiesGroup() {
|
||||
ImGui::BeginDisabled(searching_ || !search_.histories.empty());
|
||||
ImGui::TextUnformatted("Signal");
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGui::TextUnformatted("Size");
|
||||
ImGui::SameLine(80);
|
||||
ImGui::SetNextItemWidth(70);
|
||||
if (ImGui::InputInt("##min_size", &min_size_, 1, 10)) min_size_ = std::clamp(min_size_, 1, 64);
|
||||
ImGui::SameLine();
|
||||
ImGui::TextUnformatted("-");
|
||||
ImGui::SameLine();
|
||||
ImGui::SetNextItemWidth(70);
|
||||
if (ImGui::InputInt("##max_size", &max_size_, 1, 10)) max_size_ = std::clamp(max_size_, 1, 64);
|
||||
ImGui::SameLine();
|
||||
checkBox("Little endian", &little_endian_);
|
||||
ImGui::SameLine();
|
||||
checkBox("Signed", &is_signed_);
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGui::TextUnformatted("Factor");
|
||||
ImGui::SameLine(80);
|
||||
ImGui::SetNextItemWidth(100);
|
||||
validatedText("##factor", &factor_, validateDouble);
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGui::TextUnformatted("Offset");
|
||||
ImGui::SameLine(80);
|
||||
ImGui::SetNextItemWidth(100);
|
||||
validatedText("##offset", &offset_, validateDouble);
|
||||
ImGui::EndDisabled();
|
||||
}
|
||||
|
||||
void FindSignalDlg::drawFindGroup() {
|
||||
static const char *compare_items[] = {"=", ">", ">=", "!=", "<", "<=", "between"};
|
||||
const int compare_count = IM_ARRAYSIZE(compare_items);
|
||||
ImGui::TextUnformatted("Find signal");
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGui::TextUnformatted("Value");
|
||||
ImGui::SameLine();
|
||||
ImGui::SetNextItemWidth(90);
|
||||
ImGui::Combo("##compare", &compare_, compare_items, compare_count);
|
||||
ImGui::SameLine();
|
||||
ImGui::SetNextItemWidth(80);
|
||||
if (ImGui::IsWindowAppearing()) ImGui::SetKeyboardFocusHere();
|
||||
validatedText("##value1", &value1_, validateDouble);
|
||||
if (compare_ == compare_count - 1) {
|
||||
ImGui::SameLine();
|
||||
ImGui::TextUnformatted("-");
|
||||
ImGui::SameLine();
|
||||
ImGui::SetNextItemWidth(80);
|
||||
validatedText("##value2", &value2_, validateDouble);
|
||||
}
|
||||
ImGui::SameLine();
|
||||
const bool first = !searching_ && search_.histories.empty();
|
||||
ImGui::BeginDisabled(searching_ || search_.histories.size() <= 1);
|
||||
if (ImGui::Button("Undo prev find")) {
|
||||
search_.undo();
|
||||
searched_ = true;
|
||||
}
|
||||
ImGui::EndDisabled();
|
||||
ImGui::SameLine();
|
||||
ImGui::BeginDisabled(searching_ || (search_.filtered_signals.empty() && !first));
|
||||
if (ImGui::Button(searching_ ? "Finding ...." : (first ? "Find" : "Find Next"))) search();
|
||||
ImGui::EndDisabled();
|
||||
ImGui::SameLine();
|
||||
ImGui::BeginDisabled(searching_ || first);
|
||||
if (ImGui::Button("Reset")) {
|
||||
search_.reset();
|
||||
searched_ = true;
|
||||
}
|
||||
ImGui::EndDisabled();
|
||||
|
||||
if (searching_) {
|
||||
ImGui::BeginChild("view", ImVec2(0, 0), ImGuiChildFlags_Borders);
|
||||
ImGui::EndChild();
|
||||
} else {
|
||||
drawTable();
|
||||
}
|
||||
}
|
||||
|
||||
void FindSignalDlg::drawTable() {
|
||||
static const char *titles[] = {"Id", "Start Bit, size", "(time, value)"};
|
||||
const int columns = IM_ARRAYSIZE(titles);
|
||||
const ImGuiTableFlags flags = ImGuiTableFlags_Borders | ImGuiTableFlags_ScrollY | ImGuiTableFlags_Resizable | ImGuiTableFlags_NoSavedSettings;
|
||||
if (!ImGui::BeginTable("view", columns + 1, flags, ImVec2(0, 0))) return;
|
||||
ImGui::TableSetupScrollFreeze(0, 1);
|
||||
const int rows = std::min<int>(search_.filtered_signals.size(), MAX_ROWS);
|
||||
|
||||
ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthFixed | (rows ? 0 : ImGuiTableColumnFlags_Disabled), 40.0f);
|
||||
for (int c = 0; c < columns; ++c) {
|
||||
auto column_flags = c == columns - 1 ? ImGuiTableColumnFlags_WidthStretch : ImGuiTableColumnFlags_WidthFixed;
|
||||
ImGui::TableSetupColumn(titles[c], column_flags, c == 0 ? 80.0f : 120.0f);
|
||||
}
|
||||
tableHeadersRow();
|
||||
for (int row = 0; row < rows; ++row) {
|
||||
const auto &s = search_.filtered_signals[row];
|
||||
ImGui::TableNextRow();
|
||||
ImGui::TableSetColumnIndex(0);
|
||||
ImGui::PushID(row);
|
||||
if (ImGui::Selectable(std::to_string(row + 1).c_str(), false, ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_AllowDoubleClick)) {
|
||||
if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) openMessage(s.id);
|
||||
}
|
||||
drawContextMenu(row);
|
||||
ImGui::PopID();
|
||||
ImGui::TableSetColumnIndex(1);
|
||||
ImGui::TextUnformatted(s.id.toString().c_str());
|
||||
ImGui::TableSetColumnIndex(2);
|
||||
ImGui::Text("%d, %d", s.sig.start_bit, s.sig.size);
|
||||
ImGui::TableSetColumnIndex(3);
|
||||
std::string values;
|
||||
for (size_t i = 0; i < s.values.size(); ++i) {
|
||||
if (i) values += " ";
|
||||
values += s.values[i];
|
||||
}
|
||||
ImGui::TextUnformatted(values.c_str());
|
||||
}
|
||||
ImGui::EndTable();
|
||||
}
|
||||
|
||||
void FindSignalDlg::search() {
|
||||
if (search_.histories.empty()) {
|
||||
setInitialSignals();
|
||||
}
|
||||
auto v1 = utils::toDouble(value1_);
|
||||
auto v2 = utils::toDouble(value2_);
|
||||
std::function<bool(double)> cmp = nullptr;
|
||||
switch (compare_) {
|
||||
case 0: cmp = [v1](double v) { return v == v1;}; break;
|
||||
case 1: cmp = [v1](double v) { return v > v1;}; break;
|
||||
case 2: cmp = [v1](double v) { return v >= v1;}; break;
|
||||
case 3: cmp = [v1](double v) { return v != v1;}; break;
|
||||
case 4: cmp = [v1](double v) { return v < v1;}; break;
|
||||
case 5: cmp = [v1](double v) { return v <= v1;}; break;
|
||||
case 6: cmp = [v1, v2](double v) { return v >= v1 && v <= v2;}; break;
|
||||
}
|
||||
searched_ = false;
|
||||
|
||||
search_future_ = std::async(std::launch::async, [this, cmp = std::move(cmp)]() { search_.search(cmp); });
|
||||
searching_ = true;
|
||||
}
|
||||
|
||||
void FindSignalDlg::setInitialSignals() {
|
||||
std::set<unsigned short> buses;
|
||||
for (auto bus : utils::split(utils::trimmed(bus_), ',')) {
|
||||
bus = utils::trimmed(bus);
|
||||
if (!bus.empty()) buses.insert((unsigned short)utils::toULong(bus));
|
||||
}
|
||||
|
||||
std::set<uint32_t> addresses;
|
||||
for (auto addr : utils::split(utils::trimmed(address_), ',')) {
|
||||
addr = utils::trimmed(addr);
|
||||
if (!addr.empty()) addresses.insert(utils::toULong(addr, 16));
|
||||
}
|
||||
|
||||
cabana::Signal sig{};
|
||||
sig.is_little_endian = little_endian_;
|
||||
sig.is_signed = is_signed_;
|
||||
sig.factor = utils::toDouble(factor_);
|
||||
sig.offset = utils::toDouble(offset_);
|
||||
|
||||
double first_time_val = utils::toDouble(first_time_);
|
||||
double last_time_val = utils::toDouble(last_time_);
|
||||
auto [first_sec, last_sec] = std::minmax(first_time_val, last_time_val);
|
||||
uint64_t first_time = can->toMonoTime(first_sec);
|
||||
search_.last_time = std::numeric_limits<uint64_t>::max();
|
||||
if (last_sec > 0) {
|
||||
search_.last_time = can->toMonoTime(last_sec);
|
||||
}
|
||||
search_.initial_signals.clear();
|
||||
|
||||
for (const auto &[id, m] : can->lastMessages()) {
|
||||
if ((buses.empty() || buses.count(id.source)) && (addresses.empty() || addresses.count(id.address))) {
|
||||
const auto &events = can->events(id);
|
||||
auto e = std::lower_bound(events.cbegin(), events.cend(), first_time, CompareCanEvent());
|
||||
if (e != events.cend()) {
|
||||
const int total_size = m.dat.size() * 8;
|
||||
for (int size = min_size_; size <= max_size_; ++size) {
|
||||
for (int start = 0; start <= total_size - size; ++start) {
|
||||
SignalSearch::SearchSignal s{.id = id, .mono_time = first_time, .sig = sig};
|
||||
s.sig.start_bit = start;
|
||||
s.sig.size = size;
|
||||
updateMsbLsb(s.sig);
|
||||
s.value = get_raw_value((*e)->dat, (*e)->size, s.sig);
|
||||
search_.initial_signals.push_back(s);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FindSignalDlg::drawContextMenu(int row) {
|
||||
if (ImGui::BeginPopupContextItem("menu")) {
|
||||
if (ImGui::MenuItem("Create Signal")) {
|
||||
auto &s = search_.filtered_signals[row];
|
||||
UndoStack::instance()->push(new AddSigCommand(s.id, s.sig));
|
||||
openMessage(s.id);
|
||||
}
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
}
|
||||
59
iqpilot/tools/cabana/ui/tools/findsignal.h
Normal file
59
iqpilot/tools/cabana/ui/tools/findsignal.h
Normal file
@@ -0,0 +1,59 @@
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
#include <future>
|
||||
#include <limits>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "tools/cabana/commands.h"
|
||||
#include "tools/cabana/settings.h"
|
||||
#include "tools/cabana/ui/tools/tooldialog.h"
|
||||
|
||||
struct SignalSearch {
|
||||
struct SearchSignal {
|
||||
MessageId id = {};
|
||||
uint64_t mono_time = 0;
|
||||
cabana::Signal sig = {};
|
||||
double value = 0.;
|
||||
std::vector<std::string> values;
|
||||
};
|
||||
|
||||
void search(const std::function<bool(double)> &cmp);
|
||||
void reset();
|
||||
void undo();
|
||||
|
||||
std::vector<SearchSignal> filtered_signals;
|
||||
std::vector<SearchSignal> initial_signals;
|
||||
std::vector<std::vector<SearchSignal>> histories;
|
||||
uint64_t last_time = std::numeric_limits<uint64_t>::max();
|
||||
};
|
||||
|
||||
class FindSignalDlg : public ToolDialog {
|
||||
public:
|
||||
FindSignalDlg();
|
||||
~FindSignalDlg() override;
|
||||
bool draw() override;
|
||||
|
||||
Observable<const MessageId &> openMessage;
|
||||
|
||||
private:
|
||||
void search();
|
||||
void setInitialSignals();
|
||||
void drawContextMenu(int row);
|
||||
void drawMessageGroup();
|
||||
void drawPropertiesGroup();
|
||||
void drawFindGroup();
|
||||
void drawTable();
|
||||
|
||||
std::string value1_, value2_, factor_ = "1.0", offset_ = "0.0";
|
||||
std::string bus_, address_, first_time_ = "0", last_time_ = "MAX";
|
||||
int compare_ = 0;
|
||||
int min_size_ = 8, max_size_ = 8;
|
||||
bool little_endian_ = true, is_signed_ = false;
|
||||
bool searched_ = false;
|
||||
SignalSearch search_;
|
||||
std::future<void> search_future_;
|
||||
bool searching_ = false;
|
||||
};
|
||||
178
iqpilot/tools/cabana/ui/tools/findsimilarbits.cc
Normal file
178
iqpilot/tools/cabana/ui/tools/findsimilarbits.cc
Normal file
@@ -0,0 +1,178 @@
|
||||
#include "tools/cabana/ui/tools/findsimilarbits.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "imgui.h"
|
||||
#include "tools/cabana/dbc/dbcmanager.h"
|
||||
#include "tools/cabana/streams/abstractstream.h"
|
||||
#include "tools/cabana/ui/util.h"
|
||||
|
||||
FindSimilarBitsDlg::FindSimilarBitsDlg() {
|
||||
setTitle("Find similar bits");
|
||||
|
||||
for (int bus : can->sources) {
|
||||
bus_items_.push_back(bus);
|
||||
}
|
||||
updateMessages();
|
||||
}
|
||||
|
||||
void FindSimilarBitsDlg::updateMessages() {
|
||||
msg_items_.clear();
|
||||
msg_names_.clear();
|
||||
for (auto &[address, msg] : dbc()->getMessages(busAt(src_bus_))) {
|
||||
msg_items_.push_back({msg.name, address});
|
||||
}
|
||||
std::sort(msg_items_.begin(), msg_items_.end(), [](auto &l, auto &r) { return l.first < r.first; });
|
||||
for (auto &[name, _] : msg_items_) msg_names_.push_back(name);
|
||||
msg_index_ = 0;
|
||||
}
|
||||
|
||||
bool FindSimilarBitsDlg::draw() {
|
||||
if (begin(ImVec2(700, 500))) {
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGui::TextUnformatted("Find From:");
|
||||
ImGui::SameLine(90);
|
||||
ImGui::TextUnformatted("Bus");
|
||||
ImGui::SameLine();
|
||||
ImGui::SetNextItemWidth(60);
|
||||
if (comboBox("##src_bus", &src_bus_, bus_items_.data(), (int)bus_items_.size())) updateMessages();
|
||||
ImGui::SameLine();
|
||||
ImGui::SetNextItemWidth(200);
|
||||
comboBox("##msg", &msg_index_, msg_names_);
|
||||
ImGui::SameLine();
|
||||
ImGui::TextUnformatted("Byte Index");
|
||||
ImGui::SameLine();
|
||||
ImGui::SetNextItemWidth(80);
|
||||
if (ImGui::InputInt("##byte_idx", &byte_idx_, 1, 10)) byte_idx_ = std::clamp(byte_idx_, 0, 63);
|
||||
ImGui::SameLine();
|
||||
ImGui::TextUnformatted("Bit Index");
|
||||
ImGui::SameLine();
|
||||
ImGui::SetNextItemWidth(80);
|
||||
if (ImGui::InputInt("##bit_idx", &bit_idx_, 1, 10)) bit_idx_ = std::clamp(bit_idx_, 0, 7);
|
||||
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGui::TextUnformatted("Find In:");
|
||||
ImGui::SameLine(90);
|
||||
ImGui::TextUnformatted("Bus");
|
||||
ImGui::SameLine();
|
||||
ImGui::SetNextItemWidth(60);
|
||||
comboBox("##find_bus", &find_bus_, bus_items_.data(), (int)bus_items_.size());
|
||||
ImGui::SameLine();
|
||||
ImGui::TextUnformatted("Equal");
|
||||
ImGui::SameLine();
|
||||
ImGui::SetNextItemWidth(60);
|
||||
ImGui::Combo("##equal", &equal_, "Yes\0No\0");
|
||||
ImGui::SameLine();
|
||||
ImGui::TextUnformatted("Min msg count");
|
||||
ImGui::SameLine();
|
||||
ImGui::SetNextItemWidth(80);
|
||||
if (ImGui::InputInt("##min_msgs", &min_msgs_, 1, 10)) min_msgs_ = std::max(min_msgs_, 0);
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Find")) find();
|
||||
|
||||
drawTable();
|
||||
}
|
||||
return end();
|
||||
}
|
||||
|
||||
void FindSimilarBitsDlg::drawTable() {
|
||||
|
||||
if (!table_has_columns_) {
|
||||
ImGui::BeginChild("table", ImVec2(0, 0), ImGuiChildFlags_Borders);
|
||||
ImGui::EndChild();
|
||||
return;
|
||||
}
|
||||
const ImGuiTableFlags flags = ImGuiTableFlags_Borders | ImGuiTableFlags_ScrollY | ImGuiTableFlags_Resizable | ImGuiTableFlags_NoSavedSettings;
|
||||
if (!ImGui::BeginTable("table", 7, flags, ImVec2(0, 0))) return;
|
||||
ImGui::TableSetupScrollFreeze(0, 1);
|
||||
static const char *headers[] = {"address", "byte idx", "bit idx", "mismatches", "total msgs", "% mismatched"};
|
||||
|
||||
const float padding = ImGui::GetStyle().CellPadding.x * 2;
|
||||
ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthFixed, 40.0f - padding);
|
||||
for (int c = 0; c < 6; ++c) {
|
||||
ImGui::TableSetupColumn(headers[c], c == 5 ? ImGuiTableColumnFlags_WidthStretch : ImGuiTableColumnFlags_WidthFixed,
|
||||
100.0f - padding);
|
||||
}
|
||||
tableHeadersRow();
|
||||
ImGuiListClipper clipper;
|
||||
clipper.Begin((int)table_.size());
|
||||
while (clipper.Step()) {
|
||||
for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; ++i) {
|
||||
auto &m = table_[i];
|
||||
ImGui::TableNextRow();
|
||||
ImGui::TableSetColumnIndex(0);
|
||||
ImGui::PushID(i);
|
||||
if (ImGui::Selectable(std::to_string(i + 1).c_str(), false, ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_AllowDoubleClick)) {
|
||||
if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) {
|
||||
openMessage(MessageId{.source = busAt(find_bus_), .address = m.address});
|
||||
}
|
||||
}
|
||||
ImGui::PopID();
|
||||
ImGui::TableSetColumnIndex(1);
|
||||
ImGui::Text("%x", m.address);
|
||||
ImGui::TableSetColumnIndex(2);
|
||||
ImGui::Text("%u", m.byte_idx);
|
||||
ImGui::TableSetColumnIndex(3);
|
||||
ImGui::Text("%u", m.bit_idx);
|
||||
ImGui::TableSetColumnIndex(4);
|
||||
ImGui::Text("%u", m.mismatches);
|
||||
ImGui::TableSetColumnIndex(5);
|
||||
ImGui::Text("%u", m.total);
|
||||
ImGui::TableSetColumnIndex(6);
|
||||
ImGui::Text("%.2f", m.perc);
|
||||
}
|
||||
}
|
||||
ImGui::EndTable();
|
||||
}
|
||||
|
||||
void FindSimilarBitsDlg::find() {
|
||||
const uint32_t selected_address = msg_index_ < (int)msg_items_.size() ? msg_items_[msg_index_].second : 0;
|
||||
table_ = calcBits(busAt(src_bus_), selected_address, byte_idx_, bit_idx_, busAt(find_bus_), equal_ == 0, min_msgs_);
|
||||
table_has_columns_ = true;
|
||||
}
|
||||
|
||||
std::vector<FindSimilarBitsDlg::Mismatch> FindSimilarBitsDlg::calcBits(uint8_t bus, uint32_t selected_address, int byte_idx,
|
||||
int bit_idx, uint8_t find_bus, bool equal, int min_msgs_cnt) {
|
||||
std::unordered_map<uint32_t, std::vector<uint32_t>> mismatches;
|
||||
std::unordered_map<uint32_t, uint32_t> msg_count;
|
||||
const auto &events = can->allEvents();
|
||||
int bit_to_find = -1;
|
||||
for (const CanEvent *e : events) {
|
||||
if (e->src == bus) {
|
||||
if (e->address == selected_address && e->size > byte_idx) {
|
||||
bit_to_find = ((e->dat[byte_idx] >> (7 - bit_idx)) & 1) != 0;
|
||||
}
|
||||
}
|
||||
if (e->src == find_bus) {
|
||||
++msg_count[e->address];
|
||||
if (bit_to_find == -1) continue;
|
||||
|
||||
auto &mismatched = mismatches[e->address];
|
||||
if (mismatched.size() < e->size * 8) {
|
||||
mismatched.resize(e->size * 8);
|
||||
}
|
||||
for (int i = 0; i < e->size; ++i) {
|
||||
for (int j = 0; j < 8; ++j) {
|
||||
int bit = ((e->dat[i] >> (7 - j)) & 1) != 0;
|
||||
mismatched[i * 8 + j] += equal ? (bit != bit_to_find) : (bit == bit_to_find);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<Mismatch> result;
|
||||
result.reserve(mismatches.size());
|
||||
for (auto it = mismatches.begin(); it != mismatches.end(); ++it) {
|
||||
if (auto cnt = msg_count[it->first]; cnt > (uint32_t)min_msgs_cnt) {
|
||||
auto &mismatched = it->second;
|
||||
for (int i = 0; i < (int)mismatched.size(); ++i) {
|
||||
if (float perc = (mismatched[i] / (double)cnt) * 100; perc < 50) {
|
||||
result.push_back({it->first, (uint32_t)i / 8, (uint32_t)i % 8, mismatched[i], cnt, perc});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
std::sort(result.begin(), result.end(), [](auto &l, auto &r) { return l.perc < r.perc; });
|
||||
return result;
|
||||
}
|
||||
40
iqpilot/tools/cabana/ui/tools/findsimilarbits.h
Normal file
40
iqpilot/tools/cabana/ui/tools/findsimilarbits.h
Normal file
@@ -0,0 +1,40 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "tools/cabana/dbc/dbcmanager.h"
|
||||
#include "tools/cabana/ui/tools/tooldialog.h"
|
||||
|
||||
class FindSimilarBitsDlg : public ToolDialog {
|
||||
public:
|
||||
FindSimilarBitsDlg();
|
||||
bool draw() override;
|
||||
|
||||
Observable<const MessageId &> openMessage;
|
||||
|
||||
private:
|
||||
struct Mismatch {
|
||||
uint32_t address, byte_idx, bit_idx, mismatches, total;
|
||||
float perc;
|
||||
};
|
||||
std::vector<Mismatch> calcBits(uint8_t bus, uint32_t selected_address, int byte_idx, int bit_idx, uint8_t find_bus,
|
||||
bool equal, int min_msgs_cnt);
|
||||
uint8_t busAt(int index) const { return index < (int)bus_items_.size() ? bus_items_[index] : 0; }
|
||||
void updateMessages();
|
||||
void find();
|
||||
void drawTable();
|
||||
|
||||
std::vector<Mismatch> table_;
|
||||
bool table_has_columns_ = false;
|
||||
std::vector<int> bus_items_;
|
||||
int src_bus_ = 0, find_bus_ = 0;
|
||||
std::vector<std::pair<std::string, uint32_t>> msg_items_;
|
||||
std::vector<std::string> msg_names_;
|
||||
int msg_index_ = 0;
|
||||
int equal_ = 0;
|
||||
int byte_idx_ = 0, bit_idx_ = 0;
|
||||
int min_msgs_ = 100;
|
||||
};
|
||||
50
iqpilot/tools/cabana/ui/tools/routeinfo.cc
Normal file
50
iqpilot/tools/cabana/ui/tools/routeinfo.cc
Normal file
@@ -0,0 +1,50 @@
|
||||
#include "tools/cabana/ui/tools/routeinfo.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
|
||||
#include "imgui.h"
|
||||
#include "tools/cabana/streams/replaystream.h"
|
||||
#include "tools/cabana/ui/util.h"
|
||||
|
||||
RouteInfoDlg::RouteInfoDlg() {
|
||||
replay_ = dynamic_cast<ReplayStream *>(can)->getReplay();
|
||||
setTitle("Route: " + replay_->route().name());
|
||||
}
|
||||
|
||||
bool RouteInfoDlg::draw() {
|
||||
static const char *headers[] = {"", "rlog", "road", "wide road", "driver", "qlog", "qcam"};
|
||||
auto yn = [](const std::string &s) { return s.empty() ? "--" : "Yes"; };
|
||||
const auto &segments = replay_->route().segments();
|
||||
|
||||
float row_h = ImGui::GetTextLineHeightWithSpacing();
|
||||
float min_h = row_h * (std::min((int)segments.size(), 13) + 1) + ImGui::GetFrameHeightWithSpacing() + ImGui::GetStyle().WindowPadding.y * 2;
|
||||
if (begin(ImVec2(520, min_h))) {
|
||||
const ImGuiTableFlags flags = ImGuiTableFlags_Borders | ImGuiTableFlags_ScrollY | ImGuiTableFlags_SizingFixedFit;
|
||||
if (ImGui::BeginTable("table", 7, flags, ImVec2(0, 0))) {
|
||||
ImGui::TableSetupScrollFreeze(0, 1);
|
||||
for (int c = 0; c < 7; ++c) ImGui::TableSetupColumn(headers[c]);
|
||||
tableHeadersRow();
|
||||
int row = 0;
|
||||
for (const auto &[seg_num, seg] : segments) {
|
||||
ImGui::TableNextRow();
|
||||
ImGui::TableSetColumnIndex(0);
|
||||
ImGui::PushID(row);
|
||||
if (ImGui::Selectable(std::to_string(seg_num).c_str(), false, ImGuiSelectableFlags_SpanAllColumns)) {
|
||||
can->seekTo(row * 60.0);
|
||||
}
|
||||
ImGui::SetItemTooltip("Click on a row to seek to the corresponding segment.");
|
||||
ImGui::PopID();
|
||||
const char *cells[] = {yn(seg.rlog), yn(seg.road_cam), yn(seg.wide_road_cam),
|
||||
yn(seg.driver_cam), yn(seg.qlog), yn(seg.qcamera)};
|
||||
for (int c = 1; c < 7; ++c) {
|
||||
ImGui::TableSetColumnIndex(c);
|
||||
ImGui::TextUnformatted(cells[c - 1]);
|
||||
}
|
||||
++row;
|
||||
}
|
||||
ImGui::EndTable();
|
||||
}
|
||||
}
|
||||
return end();
|
||||
}
|
||||
14
iqpilot/tools/cabana/ui/tools/routeinfo.h
Normal file
14
iqpilot/tools/cabana/ui/tools/routeinfo.h
Normal file
@@ -0,0 +1,14 @@
|
||||
#pragma once
|
||||
|
||||
#include "tools/cabana/ui/tools/tooldialog.h"
|
||||
|
||||
class Replay;
|
||||
|
||||
class RouteInfoDlg : public ToolDialog {
|
||||
public:
|
||||
RouteInfoDlg();
|
||||
bool draw() override;
|
||||
|
||||
private:
|
||||
Replay *replay_ = nullptr;
|
||||
};
|
||||
52
iqpilot/tools/cabana/ui/tools/tooldialog.h
Normal file
52
iqpilot/tools/cabana/ui/tools/tooldialog.h
Normal file
@@ -0,0 +1,52 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
|
||||
#include "imgui.h"
|
||||
#include "tools/cabana/core/observable.h"
|
||||
#include "tools/cabana/ui/util.h"
|
||||
|
||||
|
||||
class ToolDialog {
|
||||
public:
|
||||
virtual ~ToolDialog() = default;
|
||||
virtual bool draw() = 0;
|
||||
|
||||
Connections connections_;
|
||||
|
||||
protected:
|
||||
void setTitle(const std::string &name) {
|
||||
char buf[32];
|
||||
snprintf(buf, sizeof(buf), "###tooldialog%p", (void *)this);
|
||||
title_ = name + buf;
|
||||
}
|
||||
|
||||
|
||||
bool begin(const ImVec2 &size) {
|
||||
if (!open_) return false;
|
||||
ImGui::SetNextWindowSize(size, ImGuiCond_Appearing);
|
||||
setNextWindowFloatsOut();
|
||||
began_ = true;
|
||||
return visible_ = ImGui::Begin(title_.c_str(), &open_, ImGuiWindowFlags_NoSavedSettings);
|
||||
}
|
||||
|
||||
bool end() {
|
||||
if (!began_) return false;
|
||||
|
||||
if (visible_ && ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows) &&
|
||||
ImGui::IsKeyPressed(ImGuiKey_Escape, false) &&
|
||||
!ImGui::IsPopupOpen(nullptr, ImGuiPopupFlags_AnyPopupId | ImGuiPopupFlags_AnyPopupLevel)) {
|
||||
open_ = false;
|
||||
}
|
||||
ImGui::End();
|
||||
began_ = false;
|
||||
return open_;
|
||||
}
|
||||
|
||||
std::string title_;
|
||||
bool open_ = true;
|
||||
|
||||
private:
|
||||
bool began_ = false, visible_ = false;
|
||||
};
|
||||
Reference in New Issue
Block a user