diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml new file mode 100644 index 00000000..6eda8ee7 --- /dev/null +++ b/.github/workflows/cmake.yml @@ -0,0 +1,332 @@ +name: Build CANgaroo + +on: + push: + branches: [ "main", "master" ] + pull_request: + branches: [ "main", "master" ] + workflow_dispatch: + +jobs: + build-ubuntu: + name: Build CANgaroo (Ubuntu / Qt6) + runs-on: ubuntu-22.04 + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Install dependencies + run: | + sudo apt update + sudo apt install -y build-essential git cmake \ + qt6-base-dev qt6-base-private-dev qt6-tools-dev \ + libqt6serialport6-dev libqt6charts6-dev \ + libnl-3-dev libnl-route-3-dev libgl1-mesa-dev fuse libfuse2 wget + + echo "=== Qt6 Version ===" + qmake6 --version + + echo "=== Qt6 Plugin Path ===" + QT6_PLUGIN_PATH=$(qmake6 -query QT_INSTALL_PLUGINS) + echo "Qt6 Plugins: $QT6_PLUGIN_PATH" + + - name: Run qmake6 and make + run: | + qmake6 + make -j$(nproc) + + - name: Prepare AppDir + run: | + mkdir -p AppDir/usr/bin + mkdir -p AppDir/usr/share/applications + mkdir -p AppDir/usr/share/icons/hicolor/256x256/apps + mkdir -p AppDir/usr/plugins/styles + mkdir -p AppDir/usr/plugins/platformthemes + + sed -i 's|Exec=/usr/bin/cangaroo %f|Exec=cangaroo %f|' cangaroo.desktop + + cp bin/cangaroo AppDir/usr/bin/ + cp cangaroo.desktop AppDir/usr/share/applications/ + cp src/assets/cangaroo.png AppDir/usr/share/icons/hicolor/256x256/apps/ + + - name: Create qt.conf + run: | + cat > AppDir/usr/bin/qt.conf << 'EOF' + [Paths] + Plugins = ../plugins + Libraries = ../lib + Prefix = ../ + EOF + + echo "=== qt.conf created ===" + cat AppDir/usr/bin/qt.conf + + - name: Download linuxdeploy + run: | + wget -q https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage + wget -q https://github.com/linuxdeploy/linuxdeploy-plugin-qt/releases/download/continuous/linuxdeploy-plugin-qt-x86_64.AppImage + chmod +x linuxdeploy*.AppImage + + - name: Build AppImage + env: + QMAKE: qmake6 + QML_SOURCES_PATHS: ./src + run: | + ./linuxdeploy-x86_64.AppImage \ + --appdir AppDir \ + --plugin qt \ + --output appimage + + - name: Upload AppImage + uses: actions/upload-artifact@v6 + with: + name: CANgaroo-Linux-AppImage + path: "CAN*.AppImage" + + - name: Upload Binary + uses: actions/upload-artifact@v6 + with: + name: CANgaroo-Linux-Binary + path: bin/cangaroo + + build-windows: + name: Build CANgaroo (Windows / MinGW / Qt6) + runs-on: windows-latest + + defaults: + run: + shell: msys2 {0} + + steps: + - name: Checkout repository + uses: actions/checkout@v5 + + - name: Setup MSYS2 (without install) + uses: msys2/setup-msys2@v2 + with: + msystem: MINGW64 + update: true + + - name: Cache MinGW and MSYS2 packages + uses: actions/cache@v4 + with: + path: | + C:/msys64/mingw64 + C:/msys64/var/cache/pacman/pkg + key: > + msys2-mingw64-qt6-${{ runner.os }}-${{ hashFiles('.github/workflows/cmake.yml') }} + restore-keys: | + msys2-mingw64-qt6-${{ runner.os }}- + + - name: Install MinGW + Qt6 (cached) + run: | + pacman -S --needed --noconfirm \ + mingw-w64-x86_64-toolchain \ + mingw-w64-x86_64-qt6 \ + mingw-w64-x86_64-qt6-tools \ + mingw-w64-x86_64-make \ + mingw-w64-x86_64-ntldd \ + git + + - name: Verify Qt6 installation + run: | + qmake6 --version + which qmake6 + + - name: Configure build (Qt6 qmake) + run: | + qmake6 CONFIG+=release + + - name: Build + run: | + mingw32-make -j$(nproc) + + - name: List build output + run: | + echo "==== Project root ====" + ls -lah + + echo "==== Executables ====" + find . -maxdepth 2 -name "*.exe" -type f -print + + - name: Prepare distribution directory + run: | + mkdir -p dist + cp ./bin/*.exe dist/ + + - name: Deploy Qt6 runtime (windeployqt) + run: | + EXE=$(ls dist/*.exe | head -n 1) + windeployqt6 \ + --release \ + --no-system-d3d-compiler \ + "$EXE" + + - name: Add MinGW runtime DLLs + run: | + cp /mingw64/bin/libgcc_s_seh-1.dll dist/ + cp /mingw64/bin/libstdc++-6.dll dist/ + cp /mingw64/bin/libwinpthread-1.dll dist/ + cp /mingw64/bin/libmd4c.dll dist/ + cp /mingw64/bin/libharfbuzz-0.dll dist/ + cp /mingw64/bin/libfreetype-6.dll dist/ + cp /mingw64/bin/libpng16-16.dll dist/ + cp /mingw64/bin/zlib1.dll dist/ + cp /mingw64/bin/libdouble-conversion.dll dist/ + cp /mingw64/bin/libb2-1.dll dist/ + cp /mingw64/bin/libicuin78.dll dist/ + cp /mingw64/bin/libbrotlidec.dll dist/ + cp /mingw64/bin/libzstd.dll dist/ + cp /mingw64/bin/libicuuc78.dll dist/ + cp /mingw64/bin/libpcre2-16-0.dll dist/ + + - name: Deploy MinGW runtime dependencies + run: | + EXE=$(ls dist/*.exe | head -n 1) + + echo "Resolving MinGW runtime dependencies for $EXE" + + ntldd -R "$EXE" \ + | grep mingw64 \ + | awk '{print $3}' \ + | sort -u \ + | while IFS= read -r file; do + # Convert Windows path to Linux path + linux_path=$(echo "$file" | sed -E 's|([A-Za-z]):\\|/\L\1/|; s|\\|/|g') + + echo "Copying $linux_path" + cp "$linux_path" dist/ || echo "Skipped $linux_path" + done + + - name: Verify missing DLLs + run: | + EXE=$(ls dist/*.exe | head -n 1) + ntldd "$EXE" || true + - name: Upload Windows Qt6 binaries + uses: actions/upload-artifact@v6 + with: + name: CANgaroo-Windows-MinGW-Qt6 + path: dist/* + + release: + name: Create GitHub Release + needs: [build-ubuntu, build-windows] + if: github.event_name == 'workflow_dispatch' || github.ref_name == 'main' || github.ref_name == 'master' + runs-on: ubuntu-latest + permissions: + contents: write + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Determine New Version + id: versioning + run: | + latest_tag=$(git describe --tags --abbrev=0 2>/dev/null || echo "v0.4.0") + echo "Latest tag: $latest_tag" + + # Remove 'v' prefix if exists + clean_tag=${latest_tag#v} + + major=$(echo $clean_tag | cut -d. -f1) + minor=$(echo $clean_tag | cut -d. -f2) + patch=$(echo $clean_tag | cut -d. -f3) + + new_minor=$((minor+1)) + new_tag="v$major.$new_minor.0" + + echo "Next tag: $new_tag" + echo "version=$new_tag" >> $GITHUB_OUTPUT + + - name: Create Git Tag + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git tag ${{ steps.versioning.outputs.version }} + git push origin ${{ steps.versioning.outputs.version }} + + - name: Download Linux Binary + uses: actions/download-artifact@v4 + with: + name: CANgaroo-Linux-Binary + path: bin + + - name: Download Linux AppImage + uses: actions/download-artifact@v4 + with: + name: CANgaroo-Linux-AppImage + path: appimage + + - name: Download Windows Artifacts + uses: actions/download-artifact@v4 + with: + name: CANgaroo-Windows-MinGW-Qt6 + path: windows-bins + + - name: Package Release + run: | + VERSION=${{ steps.versioning.outputs.version }} + mkdir -p release-pkg + + # Linux packaging + LINUX_DIR="cangaroo-${VERSION}-linux-x86_64" + mkdir -p "$LINUX_DIR" + + cp bin/cangaroo "$LINUX_DIR/" + cp appimage/CAN*.AppImage "$LINUX_DIR/CANgaroo-x86_64.AppImage" + cp README.md "$LINUX_DIR/" + cp LICENSE "$LINUX_DIR/" + cp CONTRIBUTING.md "$LINUX_DIR/" + cp setup_release.sh "$LINUX_DIR/" + + tar -czf "release-pkg/${LINUX_DIR}.tar.gz" "$LINUX_DIR" + + # Windows packaging (Zip the dist folder) + WINDOWS_ZIP="cangaroo-${VERSION}-windows-x64.zip" + cd windows-bins + zip -r "../release-pkg/${WINDOWS_ZIP}" . + cd .. + + # Generate Checksums + cd release-pkg + for file in *; do + sha256sum "$file" > "${file}.sha256" + done + cd .. + + - name: Generate Changelog + id: changelog + run: | + latest_tag=$(git describe --tags --abbrev=0 2>/dev/null || echo "") + if [ -z "$latest_tag" ]; then + log=$(git log --oneline -n 20) + else + log=$(git log ${latest_tag}..HEAD --oneline) + fi + + # Set output using multi-line delimiter + { + echo "body<> "$GITHUB_OUTPUT" + + - name: Create GitHub Release + uses: softprops/action-gh-release@v2 + with: + tag_name: ${{ steps.versioning.outputs.version }} + name: Release ${{ steps.versioning.outputs.version }} + body: ${{ steps.changelog.outputs.body }} + files: | + release-pkg/*.tar.gz + release-pkg/*.zip + release-pkg/*.sha256 + draft: true + prerelease: false \ No newline at end of file diff --git a/.gitignore b/.gitignore index 604ee40f..c17ac095 100644 --- a/.gitignore +++ b/.gitignore @@ -35,3 +35,12 @@ Makefile* test.dbc + +.vscode/ + +build/ + +bin/ + +# Release artifacts +releases/ diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index c5ebfeb8..00000000 --- a/.travis.yml +++ /dev/null @@ -1,23 +0,0 @@ -language: cpp - -sudo: required - -services: - - docker - -compiler: - - clang - - gcc - -before_install: - - sudo apt-get update -qq - - sudo apt-get install --yes qtbase5-dev qtdeclarative5-dev - - sudo apt-get install --yes qt5-default qttools5-dev-tools libnl-route-3-dev - - qmake -version - -script: - - qmake cangaroo.pro - - make - -git: - depth: 3 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..7c19d32a --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,34 @@ +# Contributing to Cangaroo + +Thank you for your interest in contributing to Cangaroo! We welcome contributions from engineers, developers, and enthusiasts. + +## How Can I Contribute? + +### Reporting Bugs +- Check the [Issues Tab](https://github.com/OpenAutoDiagLabs/cangaroo/issues) to see if the bug has already been reported. +- If not, create a new issue. Include as much detail as possible, such as: + - Your OS and version. + - The CAN hardware you are using. + - Steps to reproduce the issue. + - Any error logs or screenshots. + +### Suggesting Features +- We love hearing new ideas! Please start a [Discussion](https://github.com/OpenAutoDiagLabs/cangaroo/discussions) to talk about your feature request. + +### Submitting Pull Requests +1. **Fork the repository** and create your branch from `master`. +2. **Ensure your code builds** and follows the existing coding style (Qt/C++). +3. **Document your changes**: Update the README or headers if necessary. +4. **Open a Pull Request**: Provide a clear description of the problem you are solving or the feature you are adding. + +## Coding Standards +- We use **Qt 6** (targeted at v6.10+). +- Prefer range-based `for` loops over `foreach`. +- Keep memory safety in mind (use smart pointers where appropriate). + +## Community +Join the conversation in the [GitHub Discussions](https://github.com/OpenAutoDiagLabs/cangaroo/discussions). + +--- + +By contributing, you agree that your contributions will be licensed under the project's **GPL-3.0+** license. diff --git a/README.md b/README.md index 015ca5b0..118a8da3 100644 --- a/README.md +++ b/README.md @@ -1,123 +1,230 @@ -# cangaroo -open source can bus analyzer - -written by Hubert Denkmair - -## building on linux -* to install all required packages in a vanilla ubuntu 16.04: - * sudo apt-get install build-essential git qt5-qmake qtbase5-dev libnl-3-dev libnl-route-3-dev -* build with: - * qmake -qt=qt5 - * make - * make install - -## building on windows -* Qt Creator (Community Version is okay) brings everything you need -* except for the PCAN libraries. - * Get them from http://www.peak-system.com/fileadmin/media/files/pcan-basic.zip - * Extract to .zip to src/driver/PeakCanDriver/pcan-basic-api - * Make sure PCANBasic.dll (the one from pcan-basic-api/Win32 on a "normal" 32bit Windows build) - is found when running cangaroo, e.g. by putting it in the same folder as the .exe file. -* if you don't want Peak support, you can just disable the driver: - remove the line "win32:include($$PWD/driver/PeakCanDriver/PeakCanDriver.pri)" - from src/src.pro -* if you want to deploy the cangaroo app, make sure to also include the needed Qt Libraries. - for a normal release build, these are: Qt5Core.dll Qt5Gui.dll Qt5Widgets.dll Qt5Xml.dll - -## changelog - -### v0.2.1 unreleased -* make logging easier -* refactorings -* scroll trace view per pixel, not per item (always show last message when autoscroll is on) - -### v0.2.0 released 2016-01-24 -* docking windows system instead of MDI interface -* windows build -* windows PCAN-basic driver -* handle muxed signals in backend and trace window -* do not try to extract signals from messages when DLC too short -* can status window -* bugfixes in setup dialog -* show timestamps, log level etc. in log window - -### v0.1.3 released 2016-01-16 -* new can interface configuration GUI (missing a suid binary to actually set the config) -* use libnl-route-3 for socketcan device config read -* query socketcan interfaces for supported config options -* new logging subsystem, do not use QDebug any more -* some performance improvements when receiving lots of messages -* bugfix with time-delta view: timestamps not shown when no previous message avail - -### v0.1.2 released 2016-01-12 -* fix device re-scan ("could not bind" console message) -* fix some dbc parsing issues (signed signals, ...) -* implement big endian signals - -### v0.1.1 released 2016-01-11 -* change source structure to better fit debian packaging -* add debian packaging info - -### v0.1 released 2016-01-10 -initial release \o/ - - - -## todo - -### backend -* allow for canfd frames -* support non-message frames in traces (e.g. markers) -* implement plugin API -* embed python for scripting - -### can drivers -* allow socketcan interface config through suid binary -* socketcan: use hardware timestamps (SIOCSHWTSTAMP) if possible -* cannelloni support -* windows vector driver - -### import / export -* export to other file formats (e.g. Vector ASC, BLF, MDF) -* import CAN-Traces - -### general ui -* give some style to dock windows -* load/save docks from/to config - -### log window -* filter log messages by level - -### can status window -* display #warnings, #passive, #busoff, #restarts of socketcan devices - -### trace window -* message filtering -* assign colors to can interfaces / messages -* limit displayed number of messages -* show error frames and other non-message frames -* sort signals by startbit, name or position in candb - -### raw message generator -* provide a simple way to generate raw can messages - -### CanDB based generator -* generate can messages from candbs -* set signals according to value tables etc. -* provide generator functions for signal values -* allow scripting of signal values - -### replay window -* replay can traces -* map interfaces in traces to available networks - -### graph window -* test QCustomPlot -* allow for graphing of interface stats, message stats and signals - -### packaging / deployment -* provide clean debian package -* gentoo ebuild script -* provide static linked binary -* add windows installer +# Cangaroo +**Open-source cross-platform CAN bus analyzer for Automotive, Robotics, and Industrial Applications** + +[![Try Cangaroo Now](https://img.shields.io/badge/Try-Cangaroo-blue?style=for-the-badge)](https://github.com/OpenAutoDiagLabs/cangaroo/releases/latest) + +[![Build CANgaroo](https://github.com/OpenAutoDiagLabs/CANgaroo/actions/workflows/cmake.yml/badge.svg)](https://github.com/OpenAutoDiagLabs/CANgaroo/actions/workflows/cmake.yml) +![GitHub stars](https://img.shields.io/github/stars/OpenAutoDiagLabs/cangaroo?style=social) +![GitHub forks](https://img.shields.io/github/forks/OpenAutoDiagLabs/cangaroo?style=social) +![GitHub issues](https://img.shields.io/github/issues/OpenAutoDiagLabs/cangaroo) +![License](https://img.shields.io/github/license/OpenAutoDiagLabs/cangaroo) +![Release](https://img.shields.io/github/v/release/OpenAutoDiagLabs/cangaroo) + +Cangaroo is a professional-grade CAN bus analyzer designed for engineers in **Automotive**, **Robotics**, and **Industrial Automation**. It facilitates real-time capture, decoding, and analysis of CAN and CAN‑FD traffic. + +> **Works natively with SocketCAN on Linux and WinUSB/CandleAPI on Windows for immediate real hardware connections.** + +--- + +## 🎥 Demo Gallery +*Real-time capture and decoding of CAN traffic using DBC databases.* +
![Cangaroo Trace View](img/trace_view.gif)
+ +*Simulate CAN traffic with customizable periodic and manual transmissions.* +
![Cangaroo Generator View](img/generator_view.gif)
+ +*Seamlessly reassemble and decode UDS (Unified Diagnostic Services) messages over the ISO-TP transport layer.* +
![Cangaroo UDS ISO-TP](img/uds_iso_tp.gif)
+ +*Robust analysis of J1939 heavy-duty protocols, supporting Multi-frame (BAM) reassembly and PGN identification.* +
![Cangaroo J1939](img/j1939.gif)
+ +*Visualize CAN signals in real-time with Time-series, Scatter charts, Text view, and interactive Gauges.* +
![Cangaroo Graph View](img/graph_view.gif)
+ +--- + +## 🚀 Key Features + +* **Real-time CAN/CAN-FD Decoding**: Support for standard and high-speed flexible data-rate frames. +* **Wide Hardware Compatibility**: Seamlessly works with **SocketCAN** on Linux (supports PCAN, Kvaser, etc.), **WinUSB/CandleAPI** on Windows (gs_usb/CandleLight adapters), **SLCAN**, and **CANblaster** (UDP). +* **DBC Database Support**: Load multiple `.dbc` files to instantly decode frames into human-readable signals. +* **Powerful Data Visualization**: Integrated Graphing tools supporting Time-series, Scatter charts, Text-based monitoring, and interactive Gauge views with zoom and live tooltips. +* **Advanced Filtering & Logging**: Isolate critical data with live filters and export captures for offline analysis. +* **Modern Workspace**: A clean, dockable userinterface optimized for multi-monitor setups. + +--- + +## 🛠️ Installation & Setup (Linux) + +### Option 1: Build from Source +Getting started is as simple as running our automated setup script: + +```bash +git clone https://github.com/OpenAutoDiagLabs/CANgaroo.git +cd CANgaroo +./install_linux.sh +``` +Follow the interactive menu to install dependencies and build the project. + +### Option 2: Using Release Package +If you downloaded a pre-compiled release tarball, use the included setup script to prepare your environment: + +1. Extract the package: `tar -xzvf cangaroo-vX.Y.Z-linux-x86_64.tar.gz` +2. Run the setup script: `./setup_release.sh` +3. Select an option to install dependencies and/or install Cangaroo to `/usr/local/bin`. + +### Manual Dependency Installation + +| Distribution | Command | +| :--- | :--- | +| **Ubuntu / Debian** | `sudo apt install qt6-base-dev libqt6charts6-dev libqt6serialport6-dev build-essential libnl-3-dev libnl-route-3-dev` | +| **Fedora** | `sudo dnf install qt6-qtbase-devel qt6-qtcharts-devel qt6-qtserialport-devel libnl3-devel` | +| **Arch Linux** | `sudo pacman -S qt6-base qt6-charts qt6-serialport libnl` | + +--- + +## 🔌 Hardware Support + +Cangaroo is designed to be cross-platform, offering robust hardware support on both Linux and Windows. + +### Linux (SocketCAN) +Cangaroo leverages the standard Linux **SocketCAN** subsystem. This means it works with virtually any CAN interface supported by the Linux kernel. + +### Windows (WinUSB / CandleAPI) +On Windows, Cangaroo includes native support for adapters running the **gs_usb** protocol (such as CANable and CandleLight) via WinUSB and the CandleAPI driver. + +### Supported Hardware +* **PEAK-System (PCAN)**: + * PCAN-USB, PCAN-USB Pro, PCAN-PCIe, etc. (Native driver: `peak_usb`). +* **Native USB-CAN Adapters**: + * [CANable](https://canable.io/) (with Candlelight firmware) + * Kvaser USB/CAN Leaf + * Candlelight compatible devices (e.g., MKS CANable, cantact) +* **USB SLCAN Adapters**: + * CANable (with set-default SLCAN firmware) + * Arduino-based CAN shields (running SLCAN sketches) +* **Industrial / Embedded CAN**: + * PCIe/mPCIe CAN cards + * Embedded CAN controllers on SoCs (e.g., Raspberry Pi with MCP2515) +* **Remote / Network CAN**: + * [CANblaster](https://github.com/OpenAutoDiagLabs/CANblaster) (UDP) + * tcpcan / candlelight-over-ethernet + +### Setup Instructions + +#### 1. Native Drivers (gs_usb, pcan, etc.) +Most professional hardware is recognized automatically as `can0`, `can1`, etc. To bring up an interface at 500k bitrate: +```bash +sudo ip link set can0 up type can bitrate 500000 +``` + +#### 2. USB SLCAN (Adapters using Serial/COM) +If your device uses SLCAN (like original CANable firmware), use `slcand`: +```bash +# Connect device as /dev/ttyUSB0 and set bitrate (S6 = 500k) +sudo slcand -o -s6 -t hw -S 115200 /dev/ttyUSB0 slcan0 +sudo ip link set slcan0 up +``` + +--- + +## �🚦 Quick Start Guide + +### 1. Zero-Hardware Testing (Virtual CAN) +```bash +sudo modprobe vcan +sudo ip link add dev vcan0 type vcan +sudo ip link set up vcan0 +``` + +### 2. Remote CAN Monitoring (SSH Pipe) +![Remote CAN Monitoring](img/remote_can_monitoring.png) +Monitor traffic from a remote machine (e.g., a Raspberry Pi on your vehicle) on your local PC: +```bash +# On your local machine, setup vcan0 as shown above, then: +ssh user@remote-ip "candump -L can0" | canplayer vcan0=can0 -t +``` +*Now open Cangaroo and connect to `vcan0` to see the remote traffic.* + +#### Nested SSH Tunneling (Multi-hop) +If the target device is behind a jump host or firewall: +![Remote CAN Monitoring](img/nested_remote_can_monitoring.png) +1. **Create an SSH Tunnel**: Expose the remote device's SSH port to your local machine. +```bash +# local-pc -> jump-host -> target-device +sshpass ssh -N -L localhost:9999::22 user@jump-host-ip + +eg: ssh -N -L localhost:9999:10.66.201.60:22 root@10.147.17.225 +``` + +**Breakdown of the command:** +| Item | Description | +| :--- | :--- | +| `localhost:9999` | The local port on your **PC** that will map to the target device. | +| `10.66.201.60` | The Internal IP of the **Remote Linux Device** (Target). | +| `22` | The SSH port on the **Remote Linux Device**. | +| `root@10.147.17.225` | The login details for the **Jump Host / Remote PC** that has access to the target. | + +2. **Stream CAN over the Tunnel**: +```bash +ssh -p 9999 root@localhost "stdbuf -o0 candump -L can0" | canplayer vcan0=can0 -t +``` + +### 3. ARXML to DBC Conversion +Cangaroo natively supports DBC. If you have ARXML files, you can convert them using `canconvert`: +```bash +# Install canconvert +pip install canconvert + +# Convert ARXML to DBC +canconvert TCU.arxml TCU.dbc +``` + +--- + +## 📥 Downloads & Releases + +Download the latest release tarball from the [Releases Page](https://github.com/OpenAutoDiagLabs/cangaroo/releases). + +--- + +## 🤝 Contribution & Community + +### Verifying Your Download +All official releases include a SHA256 checksum. Verify your download using: + +```bash +sha256sum cangaroo-v0.4.3-linux-x86_64.tar.gz +# Expected output: +# abc123def456... cangaroo-v0.4.3-linux-x86_64.tar.gz +``` + +--- + +## 🤝 Contribution & Community + +We welcome contributions! +* **Report Bugs**: Open an issue on our [GitHub Tracker](https://github.com/OpenAutoDiagLabs/cangaroo/issues). +* **Suggest Features**: Start a discussion in the [Discussions Tab](https://github.com/OpenAutoDiagLabs/cangaroo/discussions). +* **Contribute Code**: See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines. +--- + +## 📜 Credits + +* **Original Author**: Hubert Denkmair ([hubert@denkmair.de](mailto:hubert@denkmair.de)) +* **Lead Maintainer**: [Jayachandran Dharuman](https://github.com/OpenAutoDiagLabs/cangaroo) + +--- + +## 📝 Changelog Summary (v0.4.5) +* **New Graph View Feature**: Added a versatile visualization suite including: + * **Time-series Graph**: Smooth real-time signal plotting with interactive cursors and tooltips. + * **Scatter Chart**: Visualize signal correlations and distributions. + * **Text View**: Compact, live-updating text representation of signal values. + * **Gauge View**: High-visibility analog/digital gauges with customizable column layouts. +* **Interactive Analysis Tools**: Integrated zooming (In/Out/Reset), signal color customization, and absolute timestamp cursors. + +## 📝 Changelog Summary (v0.4.4) +* **Unified Protocol Decoding**: Intelligent prioritization between J1939 (29-bit) and UDS/ISO-TP (11-bit) with robust Transport Protocol reassembly. +* **Enhanced J1939 Support**: Auto-labeling for common PGNs (VIN, EEC1) and reassembled multi-frame (BAM/CM) messages. +* **Generator Synchronization**: Global "Stop" now halts all background cyclic transmissions automatically for safe simulation teardown. +* **Responsive State Management**: Replaced unstable signal blocking with a "Safe Flag Pattern" to ensure responsive UI editing without data corruption. +* **Generator Loopback**: Transmitted frames are now visible in the Trace View (TX labels), providing a complete view of bus activity. + +--- + +**Keywords**: CAN bus analyzer Linux, SocketCAN GUI, CAN FD decoder, J1939 analyzer, UDS ISO-TP decoder, automotive diagnostic tool. + +**License**: [GPL-3.0+](LICENSE) diff --git a/VERSION b/VERSION new file mode 100644 index 00000000..78bc1abd --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +0.10.0 diff --git a/cangaroo.desktop b/cangaroo.desktop index f98c9e31..c52786e5 100644 --- a/cangaroo.desktop +++ b/cangaroo.desktop @@ -2,7 +2,7 @@ Version=1.0 Type=Application Terminal=false -Name=cangaroo +Name=CANgaroo Exec=/usr/bin/cangaroo %f Comment=CAN Bus Analyzer Icon=cangaroo diff --git a/cangaroo.pro b/cangaroo.pro index c414d160..5952e4a4 100644 --- a/cangaroo.pro +++ b/cangaroo.pro @@ -1,5 +1,8 @@ -SUBDIRS += src \ - canifconfig -TEMPLATE = subdirs +QT += charts + +# QT += network +SUBDIRS += src +TEMPLATE = subdirs CONFIG += ordered warn_on qt debug_and_release -CONFIG += c++11 +CONFIG += c++20 +LIBS += -lbsd diff --git a/canifconfig/canifconfig b/canifconfig/canifconfig new file mode 100755 index 00000000..fefcd476 Binary files /dev/null and b/canifconfig/canifconfig differ diff --git a/create_release.sh b/create_release.sh new file mode 100755 index 00000000..8d02a8d3 --- /dev/null +++ b/create_release.sh @@ -0,0 +1,56 @@ +#!/bin/bash + +# 1. DETECT VERSION +if [ -f "VERSION" ]; then + VERSION=$(cat VERSION | tr -d '\n') + echo "Detected Version: $VERSION" +else + echo "Error: VERSION file not found!" + exit 1 +fi +APP_NAME="cangaroo" +BINARY_PATH="./bin/${APP_NAME}" +ARCH=$(uname -m) +OS=$(uname -s | tr '[:upper:]' '[:lower:]') +RELEASE_NAME="${APP_NAME}-v${VERSION}-${OS}-${ARCH}" +TAR_FILE="${RELEASE_NAME}.tar.gz" + +# 2. CHECK IF BINARY EXISTS +if [ ! -f "$BINARY_PATH" ]; then + echo "Error: Binary not found at $BINARY_PATH" + echo "Please build the project first." + exit 1 +fi + +echo "Packaging ${APP_NAME} version ${VERSION}..." + +# 3. CREATE STAGING AND RELEASE DIRECTORIES +mkdir -p release_stage +mkdir -p releases +cp "$BINARY_PATH" release_stage/ +cp README.md release_stage/ +cp LICENSE release_stage/ +cp CONTRIBUTING.md release_stage/ +cp setup_release.sh release_stage/ + +# 4. CREATE TARBALL +# -C changes directory so the tar doesn't include the 'release_stage' parent folder path +tar -czf "releases/$TAR_FILE" -C release_stage . + +# 5. GENERATE CHECKSUM +cd releases +sha256sum "$TAR_FILE" > "${TAR_FILE}.sha256" +cd .. + +# 6. CLEANUP & OUTPUT +rm -rf release_stage + +echo "-------------------------------------------------------" +echo "Release Created Successfully:" +echo "Location: releases/" +echo "Package: $TAR_FILE" +echo "Checksum: ${TAR_FILE}.sha256" +echo "-------------------------------------------------------" +echo "SHA256 Output:" +cat "releases/${TAR_FILE}.sha256" +echo "-------------------------------------------------------" diff --git a/examples/demo.dbc b/examples/demo.dbc new file mode 100644 index 00000000..77689b19 --- /dev/null +++ b/examples/demo.dbc @@ -0,0 +1,48 @@ +VERSION "" + +NS_ : + NS_DESC_ + CM_ + BA_DEF_ + BA_ + VAL_ + CAT_DEF_ + CAT_ + FILTER + BA_DEF_DEF_ + EV_DATA_ + ENVVAR_DATA_ + SGTYPE_ + SGTYPE_VAL_ + BA_DEF_SGTYPE_ + BA_SGTYPE_ + SIG_TYPE_REF_ + VAL_TABLE_ + SIG_GROUP_ + SIG_VALTYPE_ + SIG_UNIT_ + SG_MUL_VAL_ + +BS_: + +BU_: EngineECU TransmissionECU DisplayECU + +BO_ 256 EngineData: 8 EngineECU + SG_ EngineSpeed : 0|16@0+ (1,0) [0|8000] "rpm" DisplayECU + SG_ EngineTemp : 16|8@0+ (1,-40) [-40|215] "degC" DisplayECU + SG_ OilPressure : 24|8@0+ (0.1,0) [0|25] "bar" DisplayECU + +BO_ 512 TransmissionData: 4 TransmissionECU + SG_ GearPos : 0|4@0+ (1,0) [0|15] "" DisplayECU + SG_ VehicleSpeed : 4|12@0+ (0.1,0) [0|400] "km/h" DisplayECU + +BO_ 768 AmbientData: 2 SensorECU + SG_ OutsideTemp : 0|8@0+ (1,-40) [-40|100] "degC" DisplayECU + SG_ Humidity : 8|8@0+ (0.5,0) [0|100] "%" DisplayECU + +CM_ BO_ 256 "Engine status information"; +CM_ SG_ 256 EngineSpeed "Current engine rotations per minute"; +CM_ BO_ 512 "Transmission status and vehicle speed"; +CM_ BO_ 768 "Environment sensor data"; + +VAL_ 512 GearPos 0 "Neutral" 1 "First" 2 "Second" 3 "Third" 4 "Fourth" 5 "Fifth" 6 "Sixth" 7 "Reverse" 15 "Error" ; diff --git a/img/generator_view.gif b/img/generator_view.gif new file mode 100644 index 00000000..ca67d73f Binary files /dev/null and b/img/generator_view.gif differ diff --git a/img/graph_view.gif b/img/graph_view.gif new file mode 100644 index 00000000..ce385213 Binary files /dev/null and b/img/graph_view.gif differ diff --git a/img/j1939.gif b/img/j1939.gif new file mode 100644 index 00000000..e9d0e951 Binary files /dev/null and b/img/j1939.gif differ diff --git a/img/nested_remote_can_monitoring.png b/img/nested_remote_can_monitoring.png new file mode 100644 index 00000000..a2c46277 Binary files /dev/null and b/img/nested_remote_can_monitoring.png differ diff --git a/img/remote_can_monitoring.png b/img/remote_can_monitoring.png new file mode 100644 index 00000000..e64120aa Binary files /dev/null and b/img/remote_can_monitoring.png differ diff --git a/img/trace_view.gif b/img/trace_view.gif new file mode 100644 index 00000000..f6d591a0 Binary files /dev/null and b/img/trace_view.gif differ diff --git a/img/uds_iso_tp copy.gif b/img/uds_iso_tp copy.gif new file mode 100644 index 00000000..718e7135 Binary files /dev/null and b/img/uds_iso_tp copy.gif differ diff --git a/img/uds_iso_tp.gif b/img/uds_iso_tp.gif new file mode 100644 index 00000000..718e7135 Binary files /dev/null and b/img/uds_iso_tp.gif differ diff --git a/install_linux.sh b/install_linux.sh new file mode 100755 index 00000000..b9a84fa0 --- /dev/null +++ b/install_linux.sh @@ -0,0 +1,156 @@ +#!/bin/bash + +# CANgaroo Dependency Installer for Linux +# Supports Ubuntu/Debian, Fedora, and Arch Linux + +set -e + +# Setup colors +BLUE='\033[0;34m' +CYAN='\033[0;36m' +GREEN='\033[0;32m' +RED='\033[0;31m' +ORANGE='\033[38;5;208m' +NC='\033[0m' # No Color + +# Print ASCII Art +echo -e "${ORANGE}" +echo " ______ ___ _ __ " +echo " / ____// | / | / /____ _ ____ _ _____ ____ ____ " +echo " / / / /| | / |/ // __ \`// __ \`// ___// __ \ / __ \ " +echo "/ /___ / ___ |/ /| // /_/ // /_/ // / / /_/ // /_/ / " +echo "\____//_/ |_/_/ |_/ \__, / \__,_/_/ \____/ \____/ " +echo " /____/ " +echo -e "${NC}" +echo -e "${BLUE}Open-source CAN bus analyzer setup tool${NC}" +echo "-------------------------------------------------------" + +# Detect Distribution +if [ -f /etc/os-release ]; then + . /etc/os-release + OS=$ID +else + echo -e "${RED}Error: Unsupported Linux distribution.${NC}" + exit 1 +fi + +echo -e "Detected OS: ${GREEN}$OS${NC}" + +install_deps() { + case $OS in + ubuntu|debian|linuxmint) + echo "Installing dependencies for $OS (using apt)..." + sudo apt update + sudo apt install -y \ + build-essential \ + qt6-base-dev \ + libqt6charts6-dev \ + libqt6serialport6-dev \ + libnl-3-dev \ + libnl-route-3-dev \ + libgl1-mesa-dev \ + pkg-config \ + git + ;; + fedora) + echo "Installing dependencies for Fedora (using dnf)..." + sudo dnf install -y \ + gcc-c++ \ + make \ + qt6-qtbase-devel \ + qt6-qtcharts-devel \ + qt6-qtserialport-devel \ + libnl3-devel \ + mesa-libGL-devel \ + pkgconf-pkg-config \ + git + ;; + arch) + echo "Installing dependencies for Arch Linux (using pacman)..." + sudo pacman -S --needed --noconfirm \ + base-devel \ + qt6-base \ + qt6-charts \ + qt6-serialport \ + libnl \ + mesa \ + pkgconf \ + git + ;; + *) + echo -e "${RED}Error: Distribution $OS is not explicitly supported by this script.${NC}" + echo "Please install Qt6 (base, charts, serialport) and libnl3 manually." + exit 1 + ;; + esac +} + +build_cangaroo() { + echo -e "${BLUE}Building CANgaroo...${NC}" + cd src + # Try to find qmake6, fallback to qmake if not found or if it's already qt6 + QMAKE_CMD=$(command -v qmake6 || command -v qmake) + + if [ -z "$QMAKE_CMD" ]; then + echo -e "${RED}Error: qmake not found. Please ensure Qt6 is installed correctly.${NC}" + exit 1 + fi + + # Set PKG_CONFIG_PATH for ubuntu/debian if needed + if [[ "$OS" == "ubuntu" || "$OS" == "debian" || "$OS" == "linuxmint" ]]; then + export PKG_CONFIG_PATH=/usr/lib/$(uname -m)-linux-gnu/pkgconfig:$PKG_CONFIG_PATH + fi + + $QMAKE_CMD + make -j$(nproc) + cd .. +} + +install_to_bin() { + echo -e "${BLUE}Installing CANgaroo to /usr/local/bin...${NC}" + if [ -f "bin/cangaroo" ]; then + sudo cp bin/cangaroo /usr/local/bin/ + echo -e "${GREEN}Cangaroo installed to /usr/local/bin/cangaroo${NC}" + else + echo -e "${RED}Error: Binary not found at bin/cangaroo. Did the build fail?${NC}" + exit 1 + fi +} + +echo "Select an option:" +echo "1) Install only dependencies" +echo "2) Install dependencies and build Cangaroo" +echo "3) Install dependencies, build Cangaroo, and install to /usr/local/bin" +echo "4) Build Cangaroo only" +read -p "Enter choice [1-4]: " choice + +case $choice in + 1) + install_deps + ;; + 2) + install_deps + build_cangaroo + ;; + 3) + install_deps + build_cangaroo + install_to_bin + ;; + 4) + build_cangaroo + ;; + *) + echo -e "${RED}Invalid choice. Exiting.${NC}" + exit 1 + ;; +esac + +echo "-------------------------------------------------------" +echo -e "${GREEN}Setup completed successfully!${NC}" +if [[ "$choice" -eq 2 || "$choice" -eq 4 ]]; then + echo "You can run CANgaroo from: ./bin/cangaroo" +elif [[ "$choice" -eq 3 ]]; then + echo "You can now run CANgaroo by simply typing 'cangaroo' in your terminal." +fi +echo "-------------------------------------------------------" diff --git a/setup_release.sh b/setup_release.sh new file mode 100755 index 00000000..45860d3b --- /dev/null +++ b/setup_release.sh @@ -0,0 +1,115 @@ +#!/bin/bash + +# CANgaroo Release Setup Tool +# Use this script to install dependencies for the pre-compiled CANgaroo binary. + +set -e + +# Setup colors +BLUE='\033[0;34m' +CYAN='\033[0;36m' +GREEN='\033[0;32m' +RED='\033[0;31m' +ORANGE='\033[38;5;208m' +NC='\033[0m' # No Color + +# Print ASCII Art +echo -e "${ORANGE}" +echo " ______ ___ _ __ " +echo " / ____// | / | / /____ _ ____ _ _____ ____ ____ " +echo " / / / /| | / |/ // __ \`// __ \`// ___// __ \ / __ \ " +echo "/ /___ / ___ |/ /| // /_/ // /_/ // / / /_/ // /_/ / " +echo "\____//_/ |_/_/ |_/ \__, / \__,_|_| \___/ \___/ \___/ " +echo " /____/ " +echo -e "${NC}" +echo -e "${BLUE}CANgaroo Release Setup Tool${NC}" +echo "-------------------------------------------------------" + +# Detect Distribution +if [ -f /etc/os-release ]; then + . /etc/os-release + OS=$ID +else + echo -e "${RED}Error: Unsupported Linux distribution.${NC}" + exit 1 +fi + +echo -e "Detected OS: ${GREEN}$OS${NC}" + +install_deps() { + case $OS in + ubuntu|debian|linuxmint) + echo "Installing dependencies for $OS (using apt)..." + sudo apt update + sudo apt install -y \ + qt6-base-dev \ + libqt6charts6-dev \ + libqt6serialport6-dev \ + libnl-3-dev \ + libnl-route-3-dev \ + libgl1-mesa-dev + ;; + fedora) + echo "Installing dependencies for Fedora (using dnf)..." + sudo dnf install -y \ + qt6-qtbase-devel \ + qt6-qtcharts-devel \ + qt6-qtserialport-devel \ + libnl3-devel \ + mesa-libGL-devel + ;; + arch) + echo "Installing dependencies for Arch Linux (using pacman)..." + sudo pacman -S --needed --noconfirm \ + qt6-base \ + qt6-charts \ + qt6-serialport \ + libnl \ + mesa + ;; + *) + echo -e "${RED}Error: Distribution $OS is not explicitly supported by this script.${NC}" + echo "Please install Qt6 (base, charts, serialport) and libnl3 manually." + exit 1 + ;; + esac +} + +install_to_bin() { + echo -e "${BLUE}Installing CANgaroo to /usr/local/bin...${NC}" + if [ -f "cangaroo" ]; then + sudo cp cangaroo /usr/local/bin/ + echo -e "${GREEN}Cangaroo installed to /usr/local/bin/cangaroo${NC}" + else + echo -e "${RED}Error: Binary 'cangaroo' not found in current directory.${NC}" + exit 1 + fi +} + +echo "Select an option:" +echo "1) Install only dependencies" +echo "2) Install dependencies and move CANgaroo to /usr/local/bin" +read -p "Enter choice [1-2]: " choice + +case $choice in + 1) + install_deps + ;; + 2) + install_deps + install_to_bin + ;; + *) + echo -e "${RED}Invalid choice. Exiting.${NC}" + exit 1 + ;; +esac + +echo "-------------------------------------------------------" +echo -e "${GREEN}Setup completed successfully!${NC}" +if [[ "$choice" -eq 1 ]]; then + echo "You can run CANgaroo from the current directory: ./cangaroo" +elif [[ "$choice" -eq 2 ]]; then + echo "You can now run CANgaroo by simply typing 'cangaroo' in your terminal." +fi +echo "-------------------------------------------------------" diff --git a/src/assets/auto-scroll.svg b/src/assets/auto-scroll.svg new file mode 100644 index 00000000..d97d50ce --- /dev/null +++ b/src/assets/auto-scroll.svg @@ -0,0 +1 @@ +auto-scroll \ No newline at end of file diff --git a/src/assets/dark_theme.qss b/src/assets/dark_theme.qss new file mode 100644 index 00000000..a46c5e42 --- /dev/null +++ b/src/assets/dark_theme.qss @@ -0,0 +1,156 @@ +/* Dark Theme QSS */ + +QMainWindow, QDialog, QDockWidget { + background-color: #2d2d30; + color: #dcdcdc; +} + +QMainWindow::separator { + background: #3e3e42; + width: 6px; + height: 6px; +} +QMainWindow::separator:hover { + background: #007acc; +} + +QSplitter::handle { + background: #3e3e42; +} +QSplitter::handle:hover { + background: #007acc; +} + +QMenuBar { + background-color: #2d2d30; + color: #dcdcdc; +} +QMenuBar::item:selected { + background: #3e3e42; +} + +QMenu { + background-color: #1b1b1c; + color: #f1f1f1; + border: 1px solid #434346; +} +QMenu::item:selected { + background-color: #333334; +} + +QToolBar { + background-color: #2d2d30; + border: 1px solid #3e3e42; + spacing: 3px; +} + +QTreeView, QListView, QTableView { + background-color: #1e1e1e; + color: #dcdcdc; + alternate-background-color: #252526; + gridline-color: #333337; + selection-background-color: #264f78; + selection-color: #ffffff; + border: 1px solid #3f3f46; +} + +QHeaderView::section { + background-color: #252526; + color: #dcdcdc; + padding: 4px; + border: 1px solid #333337; +} + +QPushButton { + background-color: #3e3e42; + color: #dcdcdc; + border: 1px solid #555555; + padding: 4px 8px; + border-radius: 2px; +} +QPushButton:hover { + background-color: #4e4e52; +} +QPushButton:pressed { + background-color: #007acc; +} +QPushButton:disabled { + color: #656565; + background-color: #2d2d30; +} + +QPushButton#btnStartMeasurement { + background-color: #2d813d; + color: white; + border-radius: 12px; + padding: 5px 15px; + font-weight: bold; + border: none; +} +QPushButton#btnStartMeasurement:disabled { + background-color: #1d4d28; +} + +QPushButton#btnStopMeasurement { + background-color: #a32b2b; + color: white; + border-radius: 12px; + padding: 5px 15px; + font-weight: bold; + border: none; +} +QPushButton#btnStopMeasurement:disabled { + background-color: #611e1e; +} + +QLineEdit, QComboBox, QSpinBox, QTextEdit { + background-color: #333337; + color: #f1f1f1; + border: 1px solid #434346; + padding: 2px; +} + +QToolTip { + color: #ffffff; + background-color: #4b4b4d; + border: 1px solid #767676; +} + +QScrollBar:vertical { + border: none; + background: #2d2d30; + width: 12px; + margin: 0px 0px 0px 0px; +} +QScrollBar::handle:vertical { + background: #3e3e42; + min-height: 20px; +} +QScrollBar::handle:vertical:hover { + background: #505050; +} +QScrollBar:horizontal { + border: none; + background: #2d2d30; + height: 12px; + margin: 0px 0px 0px 0px; +} +QScrollBar::handle:horizontal { + background: #3e3e42; + min-width: 20px; +} + +QTabWidget::pane { + border: 1px solid #3f3f46; +} +QTabBar::tab { + background: #2d2d30; + color: #dcdcdc; + padding: 6px 12px; + border: 1px solid #3f3f46; + border-bottom: none; +} +QTabBar::tab:selected { + background: #1e1e1e; + border-bottom: 2px solid #007acc; +} diff --git a/src/assets/graph.svg b/src/assets/graph.svg new file mode 100644 index 00000000..eb6233f3 --- /dev/null +++ b/src/assets/graph.svg @@ -0,0 +1 @@ + diff --git a/src/assets/light_theme.qss b/src/assets/light_theme.qss new file mode 100644 index 00000000..b809922d --- /dev/null +++ b/src/assets/light_theme.qss @@ -0,0 +1,46 @@ +/* Light Theme QSS */ + +QMainWindow::separator { + background: transparent; + width: 6px; + height: 6px; +} +QMainWindow::separator:hover { + background: #0078d7; +} +QSplitter::handle { + background: transparent; + width: 6px; + height: 6px; +} +QSplitter::handle:hover { + background: #0078d7; +} + +QPushButton#btnStartMeasurement { + background-color: #28a745; + color: white; + border-radius: 12px; + padding: 5px 15px; + font-weight: bold; +} +QPushButton#btnStartMeasurement:disabled { + background-color: #94d3a2; +} + +QPushButton#btnStopMeasurement { + background-color: #dc3545; + color: white; + border-radius: 12px; + padding: 5px 15px; + font-weight: bold; +} +QPushButton#btnStopMeasurement:disabled { + background-color: #f1aeb5; +} + +QToolTip { + color: black; + background-color: white; + border: 1px solid gray; +} diff --git a/src/cangaroo.ico b/src/cangaroo.ico new file mode 100644 index 00000000..0a380c01 Binary files /dev/null and b/src/cangaroo.ico differ diff --git a/src/cangaroo.qrc b/src/cangaroo.qrc index e1d055c7..24696d63 100644 --- a/src/cangaroo.qrc +++ b/src/cangaroo.qrc @@ -3,5 +3,12 @@ assets/mdibg.png assets/cangaroo.png assets/cangaroo.svg + translations/cangaroo_de_DE.qm + translations/i18n_en_us.qm + translations/i18n_zh_cn.qm + assets/auto-scroll.svg + assets/graph.svg + assets/light_theme.qss + assets/dark_theme.qss diff --git a/src/core/Backend.cpp b/src/core/Backend.cpp index e597d344..2f4f1b11 100644 --- a/src/core/Backend.cpp +++ b/src/core/Backend.cpp @@ -23,6 +23,7 @@ #include "LogModel.h" #include +#include #include #include @@ -44,8 +45,10 @@ Backend::Backend() _logModel = new LogModel(*this); setDefaultSetup(); - _trace = new CanTrace(*this, this, 100); + _trace = new CanTrace(*this, this, 50); + _conditionalLoggingManager = new ConditionalLoggingManager(*this, this); + connect(_trace, SIGNAL(messageEnqueued(int)), this, SLOT(onMessageEnqueued(int))); connect(&_setup, SIGNAL(onSetupChanged()), this, SIGNAL(onSetupChanged())); } @@ -70,7 +73,7 @@ void Backend::addCanDriver(CanDriver &driver) bool Backend::startMeasurement() { - log_info("Starting measurement"); + log_info(tr("Starting measurement")); _measurementStartTime = QDateTime::currentMSecsSinceEpoch(); _timerSinceStart.start(); @@ -84,9 +87,7 @@ bool Backend::startMeasurement() if (intf) { intf->applyConfig(*mi); - log_info(QString("Listening on interface: %1").arg(intf->getName())); - intf->open(); - + log_info(QString(tr("Listening on interface: %1")).arg(intf->getName())); CanListener *listener = new CanListener(0, *this, *intf); listener->startThread(); _listeners.append(listener); @@ -107,15 +108,14 @@ bool Backend::stopMeasurement() } foreach (CanListener *listener, _listeners) { + log_info(QString(tr("Closing interface: %1")).arg(getInterfaceName(listener->getInterfaceId()))); listener->waitFinish(); - log_info(QString("Closing interface: %1").arg(getInterfaceName(listener->getInterfaceId()))); - listener->getInterface().close(); } qDeleteAll(_listeners); _listeners.clear(); - log_info("Measurement stopped"); + log_info(tr("Measurement stopped")); _measurementRunning = false; @@ -138,11 +138,12 @@ void Backend::loadDefaultSetup(MeasurementSetup &setup) driver->update(); foreach (CanInterfaceId intf, driver->getInterfaceIds()) { MeasurementNetwork *network = setup.createNetwork(); - network->setName(QString().sprintf("Network %d", i++)); + network->setName(tr("Network ") + QString("%1").arg(i++)); MeasurementInterface *mi = new MeasurementInterface(); mi->setCanInterface(intf); mi->setBitrate(500000); + mi->setFdBitrate(2000000); network->addInterface(mi); } } @@ -151,6 +152,7 @@ void Backend::loadDefaultSetup(MeasurementSetup &setup) void Backend::setDefaultSetup() { loadDefaultSetup(_setup); + emit onSetupChanged(); } MeasurementSetup &Backend::getSetup() @@ -161,6 +163,7 @@ MeasurementSetup &Backend::getSetup() void Backend::setSetup(MeasurementSetup &new_setup) { _setup.cloneFrom(new_setup); + emit onSetupChanged(); } double Backend::currentTimeStamp() const @@ -176,6 +179,7 @@ CanTrace *Backend::getTrace() void Backend::clearTrace() { _trace->clear(); + emit onClearTraceRequested(); } CanDbMessage *Backend::findDbMessage(const CanMessage &msg) const @@ -247,13 +251,26 @@ CanInterface *Backend::getInterfaceByDriverAndName(QString driverName, QString d } -pCanDb Backend::loadDbc(QString filename) +pCanDb Backend::loadDbc(QString filename, QString *errorMsg) { DbcParser parser; + QFileInfo info(filename); + if (!info.exists() || !info.isReadable()) { + if (errorMsg) { + *errorMsg = tr("File not found or not readable."); + } + return pCanDb(); + } + QFile *dbc = new QFile(filename); + pCanDb candb(new CanDb()); - parser.parseFile(dbc, *candb); + if (!parser.parseFile(dbc, *candb)) { + if (errorMsg) { + *errorMsg = tr("Failed to parse DBC file. Please check the log for details."); + } + } delete dbc; return candb; @@ -293,3 +310,10 @@ void Backend::logMessage(const QDateTime dt, const log_level_t level, const QStr { emit onLogMessage(dt, level, msg); } + +void Backend::onMessageEnqueued(int idx) +{ + if (_conditionalLoggingManager->isEnabled()) { + _conditionalLoggingManager->processMessage(_trace->getMessage(idx)); + } +} diff --git a/src/core/Backend.h b/src/core/Backend.h index 9e2a40e1..70f02bd4 100644 --- a/src/core/Backend.h +++ b/src/core/Backend.h @@ -31,6 +31,7 @@ #include #include #include +#include class MeasurementNetwork; class CanTrace; @@ -71,6 +72,8 @@ class Backend : public QObject CanTrace *getTrace(); void clearTrace(); + ConditionalLoggingManager *getConditionalLoggingManager() const { return _conditionalLoggingManager; } + CanDbMessage *findDbMessage(const CanMessage &msg) const; CanInterfaceIdList getInterfaceList(); @@ -82,7 +85,7 @@ class Backend : public QObject CanDriver *getDriverByName(QString driverName); CanInterface *getInterfaceByDriverAndName(QString driverName, QString deviceName); - pCanDb loadDbc(QString filename); + pCanDb loadDbc(QString filename, QString *errorMsg = 0); void clearLog(); LogModel &getLogModel() const; @@ -93,11 +96,14 @@ class Backend : public QObject void onSetupChanged(); + void onClearTraceRequested(); + void onLogMessage(const QDateTime dt, const log_level_t level, const QString msg); void onSetupDialogCreated(SetupDialog &dlg); public slots: + void onMessageEnqueued(int idx); private: static Backend *_instance; @@ -111,4 +117,5 @@ public slots: QList _listeners; LogModel *_logModel; + ConditionalLoggingManager *_conditionalLoggingManager; }; diff --git a/src/core/CanDb.cpp b/src/core/CanDb.cpp index b8dd89ac..af40306b 100644 --- a/src/core/CanDb.cpp +++ b/src/core/CanDb.cpp @@ -54,6 +54,11 @@ CanDbNode *CanDb::getOrCreateNode(QString node_name) } } +size_t CanDb::getNumberOfMessages() +{ + return _messages.size(); +} + CanDbMessage *CanDb::getMessageById(uint32_t raw_id) { if (_messages.contains(raw_id)) { @@ -63,6 +68,11 @@ CanDbMessage *CanDb::getMessageById(uint32_t raw_id) } } +CanDbMessageList CanDb::getMessageList() +{ + return _messages; +} + void CanDb::addMessage(CanDbMessage *msg) { _messages[msg->getRaw_id()] = msg; @@ -87,3 +97,47 @@ bool CanDb::saveXML(Backend &backend, QDomDocument &xml, QDomElement &root) return true; } +void CanDb::updateFrom(CanDb *other) +{ + this->setVersion(other->getVersion()); + this->setComment(other->getComment()); + + for (CanDbMessage *otherMsg : other->getMessageList()) { + CanDbMessage *myMsg = this->getMessageById(otherMsg->getRaw_id()); + if (!myMsg) { + myMsg = new CanDbMessage(this); + myMsg->setName(otherMsg->getName()); + myMsg->setRaw_id(otherMsg->getRaw_id()); + myMsg->setDlc(otherMsg->getDlc()); + myMsg->setComment(otherMsg->getComment()); + this->addMessage(myMsg); + } else { + myMsg->setName(otherMsg->getName()); + myMsg->setDlc(otherMsg->getDlc()); + myMsg->setComment(otherMsg->getComment()); + } + + for (CanDbSignal *otherSig : otherMsg->getSignals()) { + CanDbSignal *mySig = myMsg->getSignalByName(otherSig->name()); + if (!mySig) { + mySig = new CanDbSignal(myMsg); + mySig->setName(otherSig->name()); + myMsg->addSignal(mySig); + } + mySig->setStartBit(otherSig->startBit()); + mySig->setLength(otherSig->length()); + mySig->setFactor(otherSig->getFactor()); + mySig->setOffset(otherSig->getOffset()); + mySig->setMinimumValue(otherSig->getMinimumValue()); + mySig->setMaximumValue(otherSig->getMaximumValue()); + mySig->setUnit(otherSig->getUnit()); + mySig->setUnsigned(otherSig->isUnsigned()); + mySig->setIsBigEndian(otherSig->isBigEndian()); + mySig->setIsMuxer(otherSig->isMuxer()); + mySig->setIsMuxed(otherSig->isMuxed()); + mySig->setMuxValue(otherSig->getMuxValue()); + mySig->setComment(otherSig->comment()); + } + } +} + diff --git a/src/core/CanDb.h b/src/core/CanDb.h index 48175f18..0e5d29d6 100644 --- a/src/core/CanDb.h +++ b/src/core/CanDb.h @@ -55,7 +55,11 @@ class CanDb CanDbNode *getOrCreateNode(QString node_name); + size_t getNumberOfMessages(); + CanDbMessage *getMessageById(uint32_t raw_id); + CanDbMessageList getMessageList(); + void addMessage(CanDbMessage *msg); QString getComment() const; @@ -63,6 +67,8 @@ class CanDb bool saveXML(Backend &backend, QDomDocument &xml, QDomElement &root); + void updateFrom(CanDb *other); + private: QString _path; QString _version; diff --git a/src/core/CanDbSignal.cpp b/src/core/CanDbSignal.cpp index 2ae83a28..82c68b67 100644 --- a/src/core/CanDbSignal.cpp +++ b/src/core/CanDbSignal.cpp @@ -75,7 +75,7 @@ void CanDbSignal::setComment(const QString &comment) _comment = comment; } -QString CanDbSignal::getValueName(const uint32_t value) const +QString CanDbSignal::getValueName(const uint64_t value) const { if (_valueTable.contains(value)) { return _valueTable[value]; @@ -84,22 +84,23 @@ QString CanDbSignal::getValueName(const uint32_t value) const } } -void CanDbSignal::setValueName(const uint32_t value, const QString &name) +void CanDbSignal::setValueName(const uint64_t value, const QString &name) { _valueTable[value] = name; } -double CanDbSignal::convertRawValueToPhysical(const uint32_t rawValue) +double CanDbSignal::convertRawValueToPhysical(const uint64_t rawValue) { - int v; if (isUnsigned()) { - v = rawValue; + uint64_t v = rawValue; + return v * _factor + _offset; } else { // TODO check with DBC that actually contains signed values?! - v = (int32_t)(rawValue<<(32-_length)); - v>>=(32-_length); + int64_t v = (int64_t)(rawValue<<(64-_length)); + v>>=(64-_length); + return v * _factor + _offset; + } - return v * _factor + _offset; } double CanDbSignal::extractPhysicalFromMessage(const CanMessage &msg) @@ -208,7 +209,23 @@ void CanDbSignal::setMuxValue(const uint32_t &muxValue) bool CanDbSignal::isPresentInMessage(const CanMessage &msg) { - if ((_startBit + _length)>(8*msg.getLength())) { + if (msg.getRawId() != _parent->getRaw_id()) { + return false; + } + + uint32_t max_byte = 0; + if (!_isBigEndian) { + max_byte = (_startBit + _length - 1) / 8; + } else { + int bits_in_first_byte = (_startBit % 8) + 1; + if (_length <= bits_in_first_byte) { + max_byte = _startBit / 8; + } else { + max_byte = (_startBit / 8) + 1 + (_length - bits_in_first_byte - 1) / 8; + } + } + + if (max_byte >= msg.getLength()) { return false; } @@ -220,7 +237,7 @@ bool CanDbSignal::isPresentInMessage(const CanMessage &msg) return _muxValue == muxer->extractRawDataFromMessage(msg); } -uint32_t CanDbSignal::extractRawDataFromMessage(const CanMessage &msg) +uint64_t CanDbSignal::extractRawDataFromMessage(const CanMessage &msg) { return msg.extractRawSignal(startBit(), length(), isBigEndian()); } diff --git a/src/core/CanDbSignal.h b/src/core/CanDbSignal.h index debdd9f3..107c1dd8 100644 --- a/src/core/CanDbSignal.h +++ b/src/core/CanDbSignal.h @@ -28,7 +28,7 @@ class CanDbMessage; -typedef QMap CanDbValueTable; +typedef QMap CanDbValueTable; class CanDbSignal { @@ -36,6 +36,8 @@ class CanDbSignal CanDbSignal(CanDbMessage *parent); QString name() const; void setName(const QString &name); + + CanDbMessage* getParentMessage() const { return _parent; } uint8_t startBit() const; void setStartBit(uint8_t startBit); @@ -46,8 +48,8 @@ class CanDbSignal QString comment() const; void setComment(const QString &comment); - QString getValueName(const uint32_t value) const; - void setValueName(const uint32_t value, const QString &name); + QString getValueName(const uint64_t value) const; + void setValueName(const uint64_t value, const QString &name); double getFactor() const; void setFactor(double factor); @@ -80,9 +82,9 @@ class CanDbSignal void setMuxValue(const uint32_t &muxValue); bool isPresentInMessage(const CanMessage &msg); - uint32_t extractRawDataFromMessage(const CanMessage &msg); + uint64_t extractRawDataFromMessage(const CanMessage &msg); - double convertRawValueToPhysical(const uint32_t rawValue); + double convertRawValueToPhysical(const uint64_t rawValue); double extractPhysicalFromMessage(const CanMessage &msg); diff --git a/src/core/CanLogParser.cpp b/src/core/CanLogParser.cpp new file mode 100644 index 00000000..617b452f --- /dev/null +++ b/src/core/CanLogParser.cpp @@ -0,0 +1,228 @@ +/* + + Copyright (c) 2026 Jayachandran Dharuman + + This file is part of CANgaroo. + + cangaroo is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + cangaroo is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with cangaroo. If not, see . + +*/ + +#include "CanLogParser.h" +#include +#include +#include +#include +#include + +bool CanLogParser::parseCanDump(const QString& filename, QVector& outMessages) +{ + QFile file(filename); + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) + return false; + + QTextStream in(&file); + while (!in.atEnd()) { + QString line = in.readLine().trimmed(); + if (line.isEmpty() || line.startsWith("#") || line.startsWith(";")) + continue; + + bool ok = false; + CanMessage msg = parseCanDumpLine(line, ok); + if (ok) { + outMessages.append(msg); + } + } + + return true; +} + +CanMessage CanLogParser::parseCanDumpLine(const QString& line, bool& ok) +{ + // candump format: (timestamp) interface ID#DATA + // e.g. (1710842000.123456) vcan0 123#1122334455667788 + // e.g. (1710842000.123456) vcan0 00000123#1122334455667788 + + CanMessage msg; + ok = false; + + QRegularExpression re("^\\((?\\d+\\.\\d+)\\)\\s+(?\\S+)\\s+(?[0-9A-Fa-f]+)#(?[0-9A-Fa-f]*)$"); + QRegularExpressionMatch match = re.match(line); + + if (match.hasMatch()) { + double timestamp = match.captured("timestamp").toDouble(); + + // Split timestamp into seconds and microseconds + uint64_t seconds = static_cast(timestamp); + uint32_t micro_seconds = static_cast((timestamp - seconds) * 1000000.0); + msg.setTimestamp(seconds, micro_seconds); + + QString idStr = match.captured("id"); + bool idOk; + uint32_t id = idStr.toUInt(&idOk, 16); + if (!idOk) return msg; + + // In candump, standard vs extended is determined implicitly by interface or ID length length, + // but typically 3 chars vs 8 chars. We will set extended if ID > 0x7FF or id string length > 3 + if (id > 0x7FF || idStr.length() > 3) { + msg.setExtended(true); + msg.setId(id & 0x1FFFFFFF); + } else { + msg.setExtended(false); + msg.setId(id & 0x7FF); + } + + QString dataStr = match.captured("data"); + uint8_t dlc = dataStr.length() / 2; + msg.setLength(dlc); + for (int i = 0; i < dlc && i < 64; ++i) { + QString byteStr = dataStr.mid(i * 2, 2); + msg.setByte(i, byteStr.toUInt(nullptr, 16)); + } + + ok = true; + } else { + // Try readable date format with DLC: (YYYY-MM-DD HH:MM:SS.uuuuuu) interface ID [DLC] DATA... + QRegularExpression re2("^\\s*\\((?\\d{4}-\\d{2}-\\d{2})\\s+(?