diff --git a/photon/engine/photon.cpp b/photon/engine/photon.cpp index 0fb933d0..e2ee8f9a 100644 --- a/photon/engine/photon.cpp +++ b/photon/engine/photon.cpp @@ -54,6 +54,9 @@ void Photon::initThreads(){ logs("[?] Cache line size (constructive): " << std::hardware_constructive_interference_size); logs("[?] Usable Hardware Threads: " << std::thread::hardware_concurrency()); #endif + DbcWatcher watcher(network.getDbcManager(), "dbc", 3); + watcher.start(); + network.printDBCMap(); std::thread producer_t(&Network::producer, &network); producer_t.detach(); std::thread parser_t(&Network::parser, &network); diff --git a/photon/gui/ui.cpp b/photon/gui/ui.cpp index 2b917723..35830a8b 100644 --- a/photon/gui/ui.cpp +++ b/photon/gui/ui.cpp @@ -11,6 +11,7 @@ void UI::build(){ ImPlot::ShowDemoWindow(); ImPlot3D::ShowDemoWindow(); fpsWindow(); + slcanWindow(); customShaderWindow(); showVideoDisplay(); networkSamplePlot(); @@ -231,6 +232,62 @@ void UI::networkSamplePlot(){ ImGui::End(); } +void UI::slcanWindow() { + ImGui::SetNextWindowSize(ImVec2(460.0f, 300.0f), ImGuiCond_FirstUseEver); + if (!ImGui::Begin("SLCAN Monitor")) { + ImGui::End(); + return; + } + + if (!networkINTF) { + ImGui::Text("Network not connected."); + ImGui::End(); + return; + } + + auto& net = *networkINTF; + + if (ImGui::BeginTable("slcan_table", 3, + ImGuiTableFlags_ScrollY | + ImGuiTableFlags_Borders | + ImGuiTableFlags_RowBg)) { + + ImGui::TableSetupColumn("CAN ID"); + ImGui::TableSetupColumn("Raw"); + ImGui::TableSetupColumn("Interpretation"); + ImGui::TableHeadersRow(); + + std::lock_guard lock(net.decodedHistoryMutex); + + for (const auto& entry : net.decodedHistory) { + + ImGui::TableNextRow(); + + // CAN ID + ImGui::TableSetColumnIndex(0); + ImGui::Text("%u (0x%X)", entry.canId, entry.canId); + + // Raw Value + ImGui::TableSetColumnIndex(1); + ImGui::Text("0x%016llX", + (unsigned long long)entry.rawValue); + + // Interpretation (multiline) + ImGui::TableSetColumnIndex(2); + for (const std::string& line : entry.lines) { + ImGui::TextUnformatted(line.c_str()); + } + } + + ImGui::EndTable(); + } + + ImGui::End(); +} + + + + void UI::customBackground(){ ImGuiIO &io = ImGui::GetIO(); ImVec2 displaySize = io.DisplaySize; diff --git a/photon/gui/ui.hpp b/photon/gui/ui.hpp index acc67748..3d1ac1c1 100644 --- a/photon/gui/ui.hpp +++ b/photon/gui/ui.hpp @@ -56,4 +56,5 @@ struct UI{ void showVideoDisplay(); void customBackground(); void networkSamplePlot(); + void slcanWindow(); }; diff --git a/photon/network/dbc_manager.cpp b/photon/network/dbc_manager.cpp new file mode 100644 index 00000000..575c5710 --- /dev/null +++ b/photon/network/dbc_manager.cpp @@ -0,0 +1,248 @@ +#include "dbc_manager.hpp" +#include +#include +#include + +// ---------- DbcManager Implementation ---------- + +bool DbcManager::loadFromFile(const std::string& path) { + std::ifstream file(path); + if (!file.is_open()) { + std::cerr << "[DBC Loader] Failed to open " << path << "\n"; + return false; + } + + std::string line; + uint32_t currentId = 0; + int messageCountLocal = 0; + int signalCountLocal = 0; + + // === Main parsing loop === + while (std::getline(file, line)) { + // trim leading spaces/tabs + line.erase(0, line.find_first_not_of(" \t\r\n")); + if (line.empty()) continue; + + // --- Parse BO_ lines --- + if (line.rfind("BO_", 0) == 0) { + uint32_t canId = 0; + std::string name, sender; + uint8_t dlc = 0; + + std::istringstream iss(line); + std::string tag; + iss >> tag >> canId; + + std::string tmp; + iss >> tmp; + auto colon = tmp.find(':'); + if (colon == std::string::npos) + continue; + name = tmp.substr(0, colon); + + std::string dlcStr; + iss >> dlcStr; + try { + dlc = static_cast(std::stoi(dlcStr)); + } catch (...) { + continue; + } + + iss >> sender; + + { + std::lock_guard lock(mapMutex); + DbcMessage& msg = dbcMap[static_cast(canId)]; + msg.canId = static_cast(canId); + msg.name = name; + msg.dlc = dlc; + msg.transmitter = sender; + msg.signals.clear(); + } + + currentId = canId; + messageCountLocal++; + std::cerr << "[DBC] Registered message: " << name + << " (ID=" << canId << ")\n"; + } + + // --- Parse SG_ lines --- + else if (line.find("SG_") != std::string::npos && currentId != 0) { + std::istringstream iss(line); + std::string tag, sigName; + iss >> tag >> sigName; // SG_ + + DbcSignal sig{}; + char c = 0; + + // find the colon + while (iss >> c) { + if (c == ':') break; + } + if (c != ':') continue; + + // parse 0|32@1+ + iss >> sig.startBit; + iss.ignore(1, '|'); + iss >> sig.length; + iss.ignore(1, '@'); + iss >> sig.endianness; + iss >> c; + sig.isSigned = (c == '-'); + + // parse (scale,offset) + if (iss >> c && c == '(') { + iss >> sig.scale; + iss.ignore(1, ','); + iss >> sig.offset; + iss.ignore(1, ')'); + } + + // parse [min|max] + if (iss >> c && c == '[') { + iss >> sig.min; + iss.ignore(1, '|'); + iss >> sig.max; + iss.ignore(1, ']'); + } + + // parse "unit" + if (iss >> std::ws && iss.peek() == '"') { + iss.get(); // consume " + std::getline(iss, sig.unit, '"'); + } + + // receiver (may or may not be in brackets) + std::string receiver; + if (iss >> std::ws) { + if (iss.peek() == '[') { + iss.get(); // [ + std::getline(iss, receiver, ']'); + } else { + iss >> receiver; // plain token (Vector__XXX) + } + sig.receiver = receiver; + } + + { + std::lock_guard lock(mapMutex); + auto it = dbcMap.find(static_cast(currentId)); + if (it != dbcMap.end()) + it->second.signals[sigName] = sig; + } + + signalCountLocal++; + std::cerr << "[DBC] Registered signal: " << sigName + << " (ID=" << currentId << ")\n"; + } + } + + // --- Summary + dump --- + { + std::lock_guard lock(mapMutex); + std::cerr << "[DBC Loader] Parsed " << messageCountLocal + << " messages and " << signalCountLocal + << " signals from " << path << "\n"; + std::cerr << "[DBC Loader] Current total messages in map: " + << dbcMap.size() << "\n"; + } + + dump(); + return (messageCountLocal > 0); +} + +// ---------- Utility Methods ---------- + +bool DbcManager::hasMessages() { + std::lock_guard lock(mapMutex); + return !dbcMap.empty(); +} + +size_t DbcManager::messageCount() { + std::lock_guard lock(mapMutex); + return dbcMap.size(); +} + +void DbcManager::dump() { + std::lock_guard lock(mapMutex); + std::cout << "========== DBC MAP ==========\n"; + for (const auto& [id, msg] : dbcMap) { + std::cout << "CAN ID: " << id + << " | Name: " << msg.name + << " | DLC: " << msg.dlc + << " | Sender: " << msg.transmitter << "\n"; + for (const auto& [sigName, s] : msg.signals) { + std::cout << " SG_ " << sigName + << " start=" << s.startBit + << " len=" << s.length + << " endian=" << s.endianness + << (s.isSigned ? " signed" : " unsigned") + << "\n"; + } + } + std::cout << "=============================\n"; +} + +// ---------- DbcWatcher Implementation ---------- + +DbcWatcher::DbcWatcher(DbcManager& manager, + const std::string& directory, + int intervalSeconds) + : dbcManager(manager), + directoryPath(directory), + pollInterval(intervalSeconds), + running(false) {} + +DbcWatcher::~DbcWatcher() { + stop(); +} + +void DbcWatcher::start() { + if (running.load()) return; + running.store(true); + workerThread = std::thread(&DbcWatcher::monitorLoop, this); + std::cerr << "[DBC Watcher] Monitoring started on: " + << directoryPath << "\n"; +} + +void DbcWatcher::stop() { + running.store(false); + if (workerThread.joinable()) + workerThread.join(); + std::cerr << "[DBC Watcher] Monitoring stopped.\n"; +} + +void DbcWatcher::monitorLoop() { + using namespace std::chrono_literals; + + while (running.load()) { + try { + for (const auto& entry : fs::directory_iterator(directoryPath)) { + if (!entry.is_regular_file()) continue; + if (entry.path().extension() != ".dbc") continue; + + std::string path = entry.path().string(); + auto lastWrite = fs::last_write_time(entry.path()); + + if (fileTimestamps.find(path) == fileTimestamps.end() + || fileTimestamps[path] != lastWrite) { + + fileTimestamps[path] = lastWrite; + std::cerr << "[DBC Watcher] Detected new/modified: " + << path << "\n"; + + if (dbcManager.loadFromFile(path)) + std::cerr << "[DBC Watcher] Loaded DBC: " + << path << "\n"; + else + std::cerr << "[DBC Watcher] Failed to load: " + << path << "\n"; + } + } + } catch (const std::exception& e) { + std::cerr << "[DBC Watcher] Error: " << e.what() << "\n"; + } + + std::this_thread::sleep_for(std::chrono::seconds(pollInterval)); + } +} diff --git a/photon/network/dbc_manager.hpp b/photon/network/dbc_manager.hpp new file mode 100644 index 00000000..02ebcb35 --- /dev/null +++ b/photon/network/dbc_manager.hpp @@ -0,0 +1,70 @@ +#pragma once +#include +#include +#include +#include +#include +#include +#include + +namespace fs = std::filesystem; + +// ---------- DBC data structures ---------- + +struct DbcSignal { + int startBit = 0; + int length = 0; + int endianness = 0; + bool isSigned = false; + double scale = 1.0; + double offset = 0.0; + double min = 0.0; + double max = 0.0; + std::string unit; + std::string receiver; +}; + + +struct DbcMessage { + int canId = 0; + std::string name; + int dlc = 0; + std::string transmitter; + std::unordered_map signals; +}; + +// ---------- DbcManager ---------- + +class DbcManager { +public: + bool loadFromFile(const std::string& path); + void dump(); + + bool hasMessages(); + size_t messageCount(); + + // Exposed for direct access if needed + std::unordered_map dbcMap; + std::mutex mapMutex; +}; + +// ---------- DbcWatcher ---------- + +class DbcWatcher { +public: + DbcWatcher(DbcManager& manager, const std::string& directory, int intervalSeconds = 5); + ~DbcWatcher(); + + void start(); + void stop(); + +private: + void monitorLoop(); + + DbcManager& dbcManager; + std::string directoryPath; + int pollInterval; + std::atomic running; + std::thread workerThread; + std::unordered_map fileTimestamps; +}; diff --git a/photon/network/network.cpp b/photon/network/network.cpp index 6a20e239..b714540f 100644 --- a/photon/network/network.cpp +++ b/photon/network/network.cpp @@ -1,82 +1,88 @@ -/*[ξ] the photon network interface*/ +#include +#define NOMINMAX +#include #include "network.hpp" #include "tcp.hpp" -#include "spsc.hpp" -#include -#include #include -#include #include -#include "../engine/include.hpp" +#include +#include -int hexValue(char c) { - if (c >= '0' && c <= '9') { - return c - '0'; - } - if (c >= 'a' && c <= 'f') { - return 10 + (c - 'a'); - } - if (c >= 'A' && c <= 'F') { - return 10 + (c - 'A'); - } +#define QUEUE_CAPACITY 2048 +#define BUFFER_CAPACITY 1024 + +static int hexValue(char c) { + if (c >= '0' && c <= '9') return c - '0'; + if (c >= 'a' && c <= 'f') return 10 + (c - 'a'); + if (c >= 'A' && c <= 'F') return 10 + (c - 'A'); return -1; } -#define QUEUE_CAPACITY 2048 -Network::Network() : spscQueue(QUEUE_CAPACITY){ +Network::Network() + : spscQueue(QUEUE_CAPACITY) {} -} +// ---------------------- producer ---------------------- + +void Network::producer() { + std::cerr << "[DEBUG] Using IP=" << IP + << " PORT=" << PORT << "\n"; -#define BUFFER_CAPACITY 1024 -void Network::producer(){ TcpSocket socket(IP, PORT); std::vector buffer(BUFFER_CAPACITY); - while(1){ + std::cerr << "[+] Attempting connection to " + << IP << ":" << PORT << "...\n"; + std::cerr << "[+] Connected (TcpSocket constructor succeeded).\n"; + + while (true) { auto bytesRead = socket.read(buffer.data(), buffer.size()); if (bytesRead > 0) { - for (std::size_t i = 0; i < static_cast(bytesRead); ++i) { - while (!spscQueue.try_push(buffer[i])) { + for (size_t i = 0; i < (size_t)bytesRead; ++i) { + while (!spscQueue.try_push(buffer[i])) std::this_thread::yield(); - } } + } else { + std::this_thread::sleep_for( + std::chrono::milliseconds(100)); } } } -void Network::parser(){ +// ---------------------- parser ---------------------- + +void Network::parser() { std::string frame; - frame.reserve(32); + frame.reserve(256); bool collecting = false; - while(1){ - if(auto* byte = spscQueue.front()){ + while (true) { + if (auto* byte = spscQueue.front()) { char ch = static_cast(*byte); spscQueue.pop(); - if (ch == 't') { - frame.clear(); - frame.push_back(ch); + if (!collecting && (std::isprint(ch) || ch == '\r')) collecting = true; + if (!collecting) continue; - } - - if (!collecting) { - continue; - } if (ch == '\r') { - frame.push_back(ch); - handleFrame(frame); + if (!frame.empty()) { + while (!frame.empty() && + (frame.back() == '\r' || + frame.back() == '\n')) { + frame.pop_back(); + } + + if (!frame.empty() && + frame.rfind("t", 0) == 0) { + handleFrame(frame); + } + } frame.clear(); collecting = false; continue; } - if (ch == '\n') { - continue; - } - frame.push_back(ch); } else { std::this_thread::yield(); @@ -84,94 +90,179 @@ void Network::parser(){ } } -Network::sample& Network::ensureSample(uint16_t canId) { - std::lock_guard guard(sampleMapMutex); - auto it = sampleMap.find(canId); - if (it == sampleMap.end()) { - auto inserted = sampleMap.emplace(canId, std::make_unique()); - it = inserted.first; - } - return *(it->second); -} +// ---------------------- decodeFrame ---------------------- -void Network::writeSample(uint16_t canId, uint64_t value) { - sample& entry = ensureSample(canId); - std::lock_guard valueGuard(entry.lock); - entry.point = value; -} +bool Network::decodeFrame(const std::string& frame, + uint16_t& canId, + uint64_t& value) { + if (frame.empty() || frame.front() != 't' + || frame.size() < 5) + return false; -bool Network::readSample(uint16_t canId, uint64_t& outValue) { - std::unique_lock mapLock(sampleMapMutex); - auto it = sampleMap.find(canId); - if (it == sampleMap.end()) { + int dataLength = hexValue(frame[4]); + if (dataLength < 0 || dataLength > 8) + return false; + + const size_t expectedLength = + 5 + static_cast(dataLength) * 2; + if (frame.size() != expectedLength) return false; + + canId = 0; + for (size_t i = 1; i <= 3; ++i) { + int nibble = hexValue(frame[i]); + if (nibble < 0) return false; + canId = static_cast((canId << 4) | nibble); + } + + value = 0; + for (int i = 0; i < dataLength; ++i) { + int hi = hexValue(frame[5 + i * 2]); + int lo = hexValue(frame[6 + i * 2]); + if (hi < 0 || lo < 0) return false; + uint8_t byte = static_cast((hi << 4) | lo); + value = (value << 8) | byte; } - sample* entry = it->second.get(); - mapLock.unlock(); - std::lock_guard valueGuard(entry->lock); - outValue = entry->point; return true; } +// ---------------------- handleFrame ---------------------- + void Network::handleFrame(const std::string& frame) { - uint16_t canId = 0; - uint64_t value = 0; + uint16_t canId; + uint64_t value; + if (!decodeFrame(frame, canId, value)) { - logs("[parser] Invalid SLCAN frame encountered"); + std::cerr << "[parser] Invalid SLCAN frame → " + << frame << "\n"; return; } - writeSample(canId, value); - //logs("[parser] CAN 0x" << std::hex << canId << " value 0x" << value << std::dec); -} - -bool Network::decodeFrame(const std::string& frame, uint16_t& canId, uint64_t& value) { - if (frame.empty() || frame.front() != 't') { - return false; - } - if (frame.back() != '\r') { - return false; + { + std::lock_guard guard(sampleMapMutex); + auto& entry = sampleMap[canId]; + std::lock_guard entryLock(entry.lock); + entry.point = value; } - if (frame.size() < 5) { - return false; - } + std::cerr << "[SLCAN] CAN ID = " << canId + << " (0x" << std::uppercase << std::hex << canId << std::dec << ")" + << " | DLC = " << int(frame[4] - '0') + << " | DATA = 0x" << std::uppercase << std::hex << value << std::dec + << "\n"; - const std::size_t payloadLength = frame.size() - 1; - if (payloadLength < 5) { - return false; + + // Interpret using DBC if available + if (dbcManager.hasMessages()) { + interpretDBCFrame(canId, value, frame[4] - '0'); // DLC from frame[4] + } else { + std::cerr << "[DBC] Map is empty.\n"; } - int dataLength = hexValue(frame[4]); - if (dataLength < 0 || dataLength > 8) { +} + +// ---------------------- sample helpers ---------------------- + +Network::sample& Network::ensureSample(uint16_t canId) { + std::lock_guard guard(sampleMapMutex); + return sampleMap[canId]; +} + +bool Network::readSample(uint16_t canId, uint64_t& outValue) { + std::lock_guard guard(sampleMapMutex); + auto it = sampleMap.find(canId); + if (it == sampleMap.end()) return false; + + std::lock_guard lock(it->second.lock); + outValue = it->second.point; + return true; +} + +// --------------------------------------------------------- +// Decode raw CAN payload using the DBC map +// --------------------------------------------------------- +void Network::interpretDBCFrame(uint16_t canId, uint64_t rawValue, int dlc) { + std::lock_guard lock(dbcManager.mapMutex); + + auto it = dbcManager.dbcMap.find(canId); + if (it == dbcManager.dbcMap.end()) { + std::cerr << "[DBC Decode] No DBC entry for CAN ID " << canId << "\n"; + return; } - const std::size_t expectedLength = 5 + static_cast(dataLength) * 2; - if (payloadLength != expectedLength) { - return false; + const DbcMessage& msg = it->second; + + // ---- COLLECT INTO A VECTOR ---- + std::vector collected; + collected.push_back(msg.name); + + // Convert rawValue → bytes + uint8_t bytes[8] = {0}; + uint64_t tmp = rawValue; + for (int i = dlc - 1; i >= 0; --i) { + bytes[i] = static_cast(tmp & 0xFF); + tmp >>= 8; } - canId = 0; - for (std::size_t i = 1; i <= 3; ++i) { - int nibble = hexValue(frame[i]); - if (nibble < 0) { - return false; + // Iterate signals + for (const auto& [sigName, sig] : msg.signals) { + + uint64_t rawSignal = 0; + int byteIndex = sig.startBit / 8; + int bitOffset = sig.startBit % 8; + int bitsLeft = sig.length; + int dstPos = 0; + + while (bitsLeft > 0 && byteIndex < dlc) { + int bitsInByte = std::min(8 - bitOffset, bitsLeft); + uint8_t mask = ((1 << bitsInByte) - 1) << bitOffset; + uint8_t extracted = (bytes[byteIndex] & mask) >> bitOffset; + rawSignal |= (uint64_t(extracted) << dstPos); + + bitsLeft -= bitsInByte; + dstPos += bitsInByte; + byteIndex++; + bitOffset = 0; } - canId = static_cast((canId << 4) | static_cast(nibble)); + + double physValue = sig.scale * rawSignal + sig.offset; + + // ---- MAKE A STRING FOR THIS SIGNAL ---- + std::ostringstream ss; + ss << sigName << " = " << physValue; + if (!sig.unit.empty()) + ss << " " << sig.unit; + + collected.push_back(ss.str()); + + // ---- Print OUT (original behavior) ---- + std::cerr << " " << ss.str() << "\n"; } - value = 0; - for (int i = 0; i < dataLength; ++i) { - int hi = hexValue(frame[5 + i * 2]); - int lo = hexValue(frame[6 + i * 2]); - if (hi < 0 || lo < 0) { - return false; - } - uint8_t byte = static_cast((hi << 4) | lo); - value = (value << 8) | byte; + // ---- STORE IN HISTORY ---- + { + std::lock_guard histLock(decodedHistoryMutex); + decodedHistory.push_back({canId, rawValue, collected}); + if (decodedHistory.size() > MAX_HISTORY) + decodedHistory.pop_front(); } +} - return true; +void Network::writeSample(uint16_t canId, uint64_t value) { + std::lock_guard guard(sampleMapMutex); + auto& s = sampleMap[canId]; + std::lock_guard lock(s.lock); + s.point = value; +} + +// ---------------------- DBC wrappers ---------------------- + +bool Network::loadDBC(const std::string& path) { + return dbcManager.loadFromFile(path); +} + +void Network::printDBCMap() { + dbcManager.dump(); } diff --git a/photon/network/network.hpp b/photon/network/network.hpp index 30ff3acb..304a3e54 100644 --- a/photon/network/network.hpp +++ b/photon/network/network.hpp @@ -1,47 +1,68 @@ -/*[ξ] the photon network interface*/ #pragma once + #include -#include +#include #include #include #include -#include +#include #include "spsc.hpp" +#include "dbc_manager.hpp" +#include -class Network{ -private: +// forward declaration +class TcpSocket; +class Network { public: Network(); + + // --- Threads --- void producer(); void parser(); + // --- Debug / DBC --- + void printDBCMap(); + DbcManager dbcManager; + DbcManager& getDbcManager() { return dbcManager; } + bool loadDBC(const std::string& path); + + // --- CAN sample interface --- bool readSample(uint16_t canId, uint64_t& outValue); void writeSample(uint16_t canId, uint64_t value); - SPSCQueue spscQueue; - std::string IP ="3.141.38.115"; + // --- Network configuration --- + std::string IP = "127.0.0.1";//"3.141.38.115"; unsigned PORT = 8187; + SPSCQueue spscQueue; + struct DecodedEntry { + uint16_t canId; + uint64_t rawValue; + std::vector lines; // e.g. "Temp=25°C, Mode=3, Enabled=1" + }; + + static constexpr size_t MAX_HISTORY = 100; + std::mutex decodedHistoryMutex; + std::deque decodedHistory; private: struct sample { - sample() = default; - sample(const sample&) = delete; - sample& operator=(const sample&) = delete; - sample(sample&&) = delete; - sample& operator=(sample&&) = delete; - std::mutex lock; uint64_t point = 0; }; - sample& ensureSample(uint16_t canId); - bool decodeFrame(const std::string& frame, uint16_t& canId, uint64_t& value); - void handleFrame(const std::string& frame); - std::mutex sampleMapMutex; - std::unordered_map> sampleMap; + -/* end of network class */ + // --- Maps --- + std::unordered_map sampleMap; + std::mutex sampleMapMutex;; + // --- Helpers --- + sample& ensureSample(uint16_t canId); + bool decodeFrame(const std::string& frame, + uint16_t& canId, + uint64_t& value); + void handleFrame(const std::string& frame); + void interpretDBCFrame(uint16_t canId, uint64_t rawValue, int dlc); };