diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..fb5ba84 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,26 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Remote Debug on Raspberry Pi", + "type": "cppdbg", + "request": "launch", + "program": "/Users/ruipedropires/SEA:ME/QTonRaspberryPi/HelloQt6", // Be sure that this is the path where binary is exist on host + "stopAtEntry": false, + "cwd": "/Users/ruipedropires/SEA:ME/QTonRaspberryPi", // should be host path not target + "externalConsole": false, + "MIMode": "gdb", + "miDebuggerPath": "/usr/bin/gdb-multiarch", // gdb-multiacrh should be used not gdb + "miDebuggerServerAddress": "192.168.178.21:2000", // Replace with your Raspberry Pi's IP and the gdbserver port + "setupCommands": [ + { + "description": "Enable pretty-printing for gdb", + "text": "-enable-pretty-printing", + "ignoreFailures": true + } + ], + "preLaunchTask": "", // Optional: Specify a task to run before debugging starts + "postDebugTask": "" // Optional: Specify a task to run after debugging ends + } + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index 37cb614..f02b29a 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -4,7 +4,7 @@ "editor.defaultFormatter": "xaver.clang-format", "editor.formatOnSave": true }, - "clang-format.executable": "C:\\Program Files\\LLVM\\bin\\clang-format.exe", + "clang-format.executable": "/opt/homebrew/bin/clang-format", "C_Cpp.codeAnalysis.clangTidy.enabled": true, "C_Cpp.codeAnalysis.clangTidy.path": "C:\\Program Files\\LLVM\\bin\\clang-tidy.exe" } \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json index f97e9e6..abaef7e 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -1,26 +1,29 @@ { - // See https://go.microsoft.com/fwlink/?LinkId=733558 - // for the documentation about the tasks.json format "version": "2.0.0", "tasks": [ { - "label": "build", + "label": "Build with Docker", "type": "shell", - "command": "msbuild", - "args": [ - // Ask msbuild to generate full paths for file names. - "/property:GenerateFullPaths=true", - "/t:build", - // Do not generate summary otherwise it leads to duplicate errors in Problems panel - "/consoleloggerparameters:NoSummary" - ], - "group": "build", - "presentation": { - // Reveal the output only if unrecognized errors occur. - "reveal": "silent" - }, - // Use the standard MS compiler pattern to detect errors, warnings and infos - "problemMatcher": "$msCompile" - } + "command": "./helperTasks.sh build_docker_image", + "group": "build" + }, + { + "label": "Copy binary from tmp container", + "type": "shell", + "command": "./helperTasks.sh create_binary_and_copy", + "group": "build" + }, + { + "label": "Send binary to Rasp", + "type": "shell", + "command": "./helperTasks.sh send_binary_to_rasp", + "group": "build" + }, + { + "label": "Run gdbserver on Rasp", + "type": "shell", + "command": "./helperTasks.sh run_gdb_server_on_rasp", + "group": "build" + } ] } \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 16845c5..4f428e0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,47 +1,27 @@ -cmake_minimum_required(VERSION 3.16) - -project(Speedometer VERSION 0.1 LANGUAGES CXX) - -set(CMAKE_CXX_STANDARD_REQUIRED ON) - -find_package(Qt6 6.5 REQUIRED COMPONENTS Quick) - -qt_standard_project_setup(REQUIRES 6.5) - -qt_add_executable(appSpeedometer - ./src/main.cpp -) - -qt_add_qml_module(appSpeedometer - URI Speedometer - VERSION 1.0 - QML_FILES - ./src/Main.qml -) - -# Qt for iOS sets MACOSX_BUNDLE_GUI_IDENTIFIER automatically since Qt 6.1. -# If you are developing for iOS or macOS you should consider setting an -# explicit, fixed bundle identifier manually though. -set_target_properties(appSpeedometer PROPERTIES -# MACOSX_BUNDLE_GUI_IDENTIFIER com.example.appSpeedometer - MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION} - MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR} - MACOSX_BUNDLE TRUE - WIN32_EXECUTABLE TRUE -) - -target_link_libraries(appSpeedometer - PRIVATE - Qt6::Quick - Qt6::Core - Qt6::Gui - Qt6::Qml - Qt6::Network -) - -include(GNUInstallDirs) -install(TARGETS appSpeedometer - BUNDLE DESTINATION . - LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} - RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} -) +cmake_minimum_required(VERSION 3.5) + +project(HelloQt6 LANGUAGES CXX) +message("CMAKE_SYSROOT " ${CMAKE_SYSROOT}) +message("CMAKE_LIBRARY_ARCHITECTURE " ${CMAKE_LIBRARY_ARCHITECTURE}) +set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +find_package(Qt6 COMPONENTS Core Quick DBus SerialBus REQUIRED) + +set(CMAKE_C_FLAGS "${CMAKE_CXX_FLAGS} -fPIC -Wl,-rpath-link, ${CMAKE_SYSROOT}/usr/lib/${CMAKE_LIBRARY_ARCHITECTURE} -L${CMAKE_SYSROOT}/usr/lib/${CMAKE_LIBRARY_ARCHITECTURE}") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC -Wl,-rpath-link,${CMAKE_SYSROOT}/usr/lib/${CMAKE_LIBRARY_ARCHITECTURE} -L${CMAKE_SYSROOT}/usr/lib/${CMAKE_LIBRARY_ARCHITECTURE}") +# Enable automatic MOC, UIC, and RCC processing +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTOUIC ON) +set(CMAKE_AUTORCC ON) + +qt_add_resources(QT_RESOURCES ./src/resources.qrc) + +add_executable(speedometer ${QT_RESOURCES} + ./src/main.cpp + ./src/CANBusHandler.cpp + ./include/CANBusHandler.hpp +) + +target_link_libraries(speedometer -lm -ldl Qt6::Core Qt6::DBus Qt6::Quick Qt6::SerialBus) + diff --git a/include/.keep b/include/.keep deleted file mode 100644 index e69de29..0000000 diff --git a/include/CANBusHandler.hpp b/include/CANBusHandler.hpp new file mode 100644 index 0000000..0fb3df9 --- /dev/null +++ b/include/CANBusHandler.hpp @@ -0,0 +1,35 @@ +#ifndef CANBUSHANDLER_HPP +#define CANBUSHANDLER_HPP + +#include + +class CANBusHandler : public QObject +{ + Q_OBJECT + Q_PROPERTY(int speed READ getSpeed WRITE setSpeed NOTIFY speedChanged) + Q_PROPERTY( + int battery READ getBattery WRITE setBattery NOTIFY batteryChanged) + + public: + explicit CANBusHandler(QObject* parent = nullptr); + ~CANBusHandler(); + + int getSpeed() const; + void setSpeed(int speed); + int getBattery() const; + void setBattery(int battery); + + signals: + void speedChanged(int speed); + void batteryChanged(int battery); + + private slots: + void readFrames(); + + private: + int canSocket; + int m_speed; + int m_battery; +}; + +#endif // CANBUSHANDLER_HPP diff --git a/src/CANBusHandler.cpp b/src/CANBusHandler.cpp new file mode 100644 index 0000000..23fb595 --- /dev/null +++ b/src/CANBusHandler.cpp @@ -0,0 +1,112 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../include/CANBusHandler.hpp" + +CANBusHandler::CANBusHandler(QObject* parent) + : QObject(parent), canSocket(-1), m_speed(0), m_battery(0) +{ + struct ifreq ifr; + struct sockaddr_can addr; + + canSocket = socket(PF_CAN, SOCK_RAW, CAN_RAW); + if (canSocket < 0) + { + qWarning() << "Cannot create CAN socket!"; + return; + } + + strcpy(ifr.ifr_name, "can0"); + ioctl(canSocket, SIOCGIFINDEX, &ifr); + + addr.can_family = AF_CAN; + addr.can_ifindex = ifr.ifr_ifindex; + if (bind(canSocket, (struct sockaddr*)&addr, sizeof(addr)) < 0) + { + qWarning() << "Cannot bind CAN socket!"; + close(canSocket); + return; + } + + qDebug() << "CAN socket bound to can0 interface successfully."; + + // Set up a timer to periodically read frames + QTimer* timer = new QTimer(this); + connect(timer, &QTimer::timeout, this, [this]() { readFrames(); }); + timer->start(10); // Adjust the interval as needed +} + +CANBusHandler::~CANBusHandler() +{ + if (canSocket >= 0) + { + close(canSocket); // Close the CAN socket + } +} + +int CANBusHandler::getSpeed() const +{ + return m_speed; +} + +void CANBusHandler::setSpeed(int speed) +{ + if (m_speed != speed) + { + m_speed = speed; + emit speedChanged(m_speed); + } +} + +int CANBusHandler::getBattery() const +{ + return m_battery; +} + +void CANBusHandler::setBattery(int battery) +{ + if (m_battery != battery) + { + m_battery = battery; + emit batteryChanged(m_battery); + } +} + +void CANBusHandler::readFrames() +{ + struct can_frame frame; + int nbytes = read(canSocket, &frame, sizeof(struct can_frame)); + + if (nbytes < 0) + { + qWarning() << "Error reading CAN frame!"; + return; + } + + if (frame.can_id == 0x01) + { + int speed; + memcpy(&speed, frame.data, sizeof(int)); + + speed = ntohl(speed); + + // qDebug() << "Speed:" << speed; // Print the speed value + setSpeed(speed); + } + else if (frame.can_id == 0x02) + { + int battery; + memcpy(&battery, frame.data, sizeof(int)); + + battery = ntohl(battery); + + // qDebug() << "Battery:" << battery; // Print the speed value + setBattery(battery); + } +} diff --git a/src/Main.qml b/src/Main.qml index 1f02e03..e7f8c01 100644 --- a/src/Main.qml +++ b/src/Main.qml @@ -1,20 +1,23 @@ import QtQuick 2.15 import QtQuick.Controls 2.15 +import canbus 1.0 ApplicationWindow { visible: true - width: 1000 - height: 400 + width: Screen.width + height: Screen.height title: "Instrument Cluster with Smooth Transitions" - // Initial values declared here - property int speedValue: 0 - property int batteryValue: 284 + CANBusHandler { + id: canBusHandler + onSpeedChanged: speedGauge.requestPaint() + onBatteryChanged: batteryGauge.requestPaint() + } Rectangle { anchors.fill: parent - // Background gradient + // Background Gradient Canvas { anchors.fill: parent onPaint: { @@ -57,48 +60,6 @@ ApplicationWindow { } } - Canvas { - id: roadCanvas - anchors.fill: parent - - onPaint: { - var ctx = getContext("2d"); - ctx.clearRect(0, 0, width, height); - - var centerX = width / 2; - var roadWidth = 200; - var roadHeight = height; - - // Perspective variables - var topRoadWidth = 50; // Simulated narrow top width for 3D effect - var bottomRoadWidth = roadWidth; - - // Road boundaries - var leftTop = centerX - topRoadWidth / 2; - var rightTop = centerX + topRoadWidth / 2; - var leftBottom = centerX - bottomRoadWidth / 2; - var rightBottom = centerX + bottomRoadWidth / 2; - - // Draw road edges - ctx.strokeStyle = "white"; - ctx.lineWidth = 3; - - // Left edge - ctx.beginPath(); - ctx.moveTo(leftTop, 0); - ctx.lineTo(leftBottom, roadHeight); - ctx.stroke(); - - // Right edge - ctx.beginPath(); - ctx.moveTo(rightTop, 0); - ctx.lineTo(rightBottom, roadHeight); - ctx.stroke(); - } - } - - - // Left Speedometer Gauge Canvas { id: speedGauge @@ -108,58 +69,20 @@ ApplicationWindow { anchors.left: parent.left anchors.leftMargin: 20 - property real angle: 0.75 * Math.PI // Initial angle for the arc - onPaint: { var ctx = getContext("2d"); ctx.clearRect(0, 0, width, height); - // Background gradient for the circular gauge - var bgGradient = ctx.createRadialGradient(width / 2, height / 2, 50, width / 2, height / 2, 120); - bgGradient.addColorStop(0, "#001848"); - bgGradient.addColorStop(1, "#002060"); - ctx.fillStyle = bgGradient; - ctx.beginPath(); - ctx.arc(width / 2, height / 2, 120, 0, 2 * Math.PI); - ctx.fill(); - - // Draw Speed Arc based on speed value - var arcStartAngle = 0.75 * Math.PI; // Start at 270 degrees (left) - var arcEndAngle = arcStartAngle + (Math.PI * speedValue / 180); // Map speedValue to an arc from 0 to 180 degrees - var arcGradient = ctx.createLinearGradient(0, 0, width, height); - arcGradient.addColorStop(0, "#00ff00"); - arcGradient.addColorStop(1, "#004000"); - ctx.lineWidth = 15; - ctx.strokeStyle = arcGradient; - ctx.beginPath(); - ctx.arc(width / 2, height / 2, 100, arcStartAngle, arcEndAngle); - ctx.stroke(); - // Speed Value - ctx.font = "36px Arial"; + ctx.font = "36px sans-serif"; ctx.fillStyle = "white"; ctx.textAlign = "center"; - ctx.fillText(speedValue + " km/h", width / 2, height / 2); + ctx.fillText(canBusHandler.speed, width / 2, height / 2); // Label - ctx.font = "18px Arial"; + ctx.font = "18px sans-serif"; ctx.fillStyle = "lightgray"; - ctx.fillText("km/h", width / 2, height / 2 + 40); - } - - // Speed angle animation with smoother transition - NumberAnimation { - id: speedAnimation - target: speedGauge - property: "angle" - from: speedGauge.angle - to: 0.75 * Math.PI + (Math.PI * speedValue / 180) - duration: 1000 // 1 second transition for smoother effect - easing.type: Easing.InOutQuad // Smoother easing - running: true - onRunningChanged: { - speedGauge.requestPaint(); // Trigger repaint when animation is running - } + ctx.fillText("Speed", width / 2, height / 2 + 40); } } @@ -172,85 +95,21 @@ ApplicationWindow { anchors.right: parent.right anchors.rightMargin: 20 - property real angle: 0.75 * Math.PI // Initial angle for the arc - onPaint: { var ctx = getContext("2d"); ctx.clearRect(0, 0, width, height); - // Background gradient for the circular gauge - var bgGradient = ctx.createRadialGradient(width / 2, height / 2, 50, width / 2, height / 2, 120); - bgGradient.addColorStop(0, "#001848"); - bgGradient.addColorStop(1, "#002060"); - ctx.fillStyle = bgGradient; - ctx.beginPath(); - ctx.arc(width / 2, height / 2, 120, 0, 2 * Math.PI); - ctx.fill(); - - // Draw Battery Arc based on battery value - var arcStartAngle = 0.75 * Math.PI; // Start at 270 degrees (left) - var arcEndAngle = arcStartAngle + (Math.PI * batteryValue / 500); // Map batteryValue to an arc from 0 to 180 degrees - var arcGradient = ctx.createLinearGradient(0, 0, width, height); - arcGradient.addColorStop(0, "#ffcc00"); // Yellow arc for battery status - arcGradient.addColorStop(1, "#e0b100"); - ctx.lineWidth = 15; - ctx.strokeStyle = arcGradient; - ctx.beginPath(); - ctx.arc(width / 2, height / 2, 100, arcStartAngle, arcEndAngle); - ctx.stroke(); - // Battery Value - ctx.font = "36px Arial"; + ctx.font = "36px sans-serif"; ctx.fillStyle = "white"; ctx.textAlign = "center"; - ctx.fillText(batteryValue + " km", width / 2, height / 2); + ctx.fillText(canBusHandler.battery + " %", width / 2, height / 2); // Label - ctx.font = "18px Arial"; + ctx.font = "18px sans-serif"; ctx.fillStyle = "lightgray"; ctx.fillText("Battery", width / 2, height / 2 + 40); } - - // Battery angle animation with smoother transition - NumberAnimation { - id: batteryAnimation - target: batteryGauge - property: "angle" - from: batteryGauge.angle - to: 0.75 * Math.PI + (Math.PI * batteryValue / 500) - duration: 1000 // 1 second transition for smoother effect - easing.type: Easing.InOutQuad // Smoother easing - running: true - onRunningChanged: { - batteryGauge.requestPaint(); // Trigger repaint when animation is running - } - } - } - - // Dynamic Speed Timer - Timer { - id: speedTimer - interval: 500 // 500 milliseconds for more frequent updates - repeat: true - running: true - onTriggered: { - speedValue = Math.floor(Math.random() * 180); // Simulate random speed between 0 and 180 km/h - speedAnimation.stop(); // Stop any ongoing animation - speedAnimation.start(); // Start a new animation - } - } - - // Dynamic Battery Timer - Timer { - id: batteryTimer - interval: 1000 // 1 second for more frequent updates - repeat: true - running: true - onTriggered: { - batteryValue = Math.floor(Math.random() * 500); // Simulate random battery value between 0 and 500 km - batteryAnimation.stop(); // Stop any ongoing animation - batteryAnimation.start(); // Start a new animation - } } } } diff --git a/src/main.cpp b/src/main.cpp index f98b0fa..925258e 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,18 +1,32 @@ #include #include +#include +#include +#include +#include "../include/CANBusHandler.hpp" -int main(int argc, char *argv[]) +int main(int argc, char* argv[]) { QGuiApplication app(argc, argv); QQmlApplicationEngine engine; + + qmlRegisterType("canbus", 1, 0, "CANBusHandler"); + + CANBusHandler canBusHandler; + engine.rootContext()->setContextProperty("canBusHandler", &canBusHandler); + + // Load QML file + const QUrl url(QStringLiteral("qrc:/Main.qml")); QObject::connect( - &engine, - &QQmlApplicationEngine::objectCreationFailed, - &app, - []() { QCoreApplication::exit(-1); }, + &engine, &QQmlApplicationEngine::objectCreated, &app, + [url](QObject* obj, const QUrl& objUrl) + { + if (!obj && url == objUrl) + QCoreApplication::exit(-1); + }, Qt::QueuedConnection); - engine.loadFromModule("Speedometer", "Main"); + engine.load(url); return app.exec(); } diff --git a/src/resources.qrc b/src/resources.qrc new file mode 100644 index 0000000..f907b18 --- /dev/null +++ b/src/resources.qrc @@ -0,0 +1,5 @@ + + + Main.qml + +