Index: .gitignore =================================================================== diff -u -r3a0f07301c96f929e3e1a8e391cf7f09648473aa -rcfc0df719cb5033078d0cac45ce0f6243810f2e7 --- .gitignore (.../.gitignore) (revision 3a0f07301c96f929e3e1a8e391cf7f09648473aa) +++ .gitignore (.../.gitignore) (revision cfc0df719cb5033078d0cac45ce0f6243810f2e7) @@ -48,3 +48,14 @@ *.mov *.wmv +__pycache__ +.DS_Store +._* +.qtcreator/ +tags +*.swp +*.o +*.a +*.so +bin/ +CMakeLists.txt.user Index: CMakeLists.txt =================================================================== diff -u --- CMakeLists.txt (revision 0) +++ CMakeLists.txt (revision cfc0df719cb5033078d0cac45ce0f6243810f2e7) @@ -0,0 +1,45 @@ +cmake_minimum_required(VERSION 3.16) + +project(CloudSync + LANGUAGES CXX +) + +include(cmake/Debug.cmake) + +option(BUILD_DEBUG "Build debug" OFF) + +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +set(CMAKE_DISABLE_IN_SOURCE_BUILD ON) + +# if(CMAKE_CONFIGURATION_TYPES) +# set(CMAKE_CONFIGURATION_TYPES Debug Profile Release) +# set(CMAKE_CONFIGURATION_TYPES +# "${CMAKE_CONFIGURATION_TYPES}" +# CACHE STRING "Reset the configurations to what we need" FORCE) +# endif() + +add_compile_options(-Wall -Werror -Wextra) + +# set(CMAKE_BINARY_DIR $ENV{DIR_BUILD}/${PROJECT_NAME}) +# message("CMAKE_BINARY_DIR=${CMAKE_BINARY_DIR}") +# set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) + +# if(NOT CMAKE_BUILD_TYPE) +# message(STATUS "CMAKE_BUILD_TYPE was not defined. Using Debug as a default.") +# message( +# STATUS "Available options are: ${CMAKE_CONFIGURATION_TYPES}") +# set(CMAKE_BUILD_TYPE +# Debug +# CACHE STRING "Set the type of build" FORCE) +# endif() + +# create the bin directory +# file(MAKE_DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}) + +add_subdirectory(lib) +add_subdirectory(tools) +add_subdirectory(CloudSyncRt) + +# dump_cmake_variables() Index: CloudSyncRt/CMakeLists.txt =================================================================== diff -u --- CloudSyncRt/CMakeLists.txt (revision 0) +++ CloudSyncRt/CMakeLists.txt (revision cfc0df719cb5033078d0cac45ce0f6243810f2e7) @@ -0,0 +1,43 @@ +project(CloudSyncRt + DESCRIPTION "CloudSync Real-time" + LANGUAGES CXX +) + +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) +set(CMAKE_VERBOSE_MAKEFILE ON) + +set(CMAKE_AUTOMOC ON) + +find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Core WebSockets) +find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Core WebSockets) + +find_package(Comms HINTS ${CMAKE_CURRENT_SOURCE_DIR}/../lib/Comms REQUIRED) +find_package(MsgUtils HINTS ${CMAKE_CURRENT_SOURCE_DIR}/../lib/MsgUtils REQUIRED) + +set(INCLUDES + CloudSyncRtController.h +) + +set(SRCS + CloudSyncRtController.cpp + main.cpp +) + +add_executable(${PROJECT_NAME}) + +target_sources(${PROJECT_NAME} PRIVATE ${INCLUDES} ${SRCS}) + +set_target_properties(${PROJECT_NAME} PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/../bin" + ADDITIONAL_CLEAN_FILES "${CMAKE_CURRENT_SOURCE_DIR}/../bin" +) + +target_link_libraries(${PROJECT_NAME} PRIVATE + Comms + MsgUtils + Qt${QT_VERSION_MAJOR}::WebSockets + Qt${QT_VERSION_MAJOR}::Core +) Index: CloudSyncRt/CloudSyncRtController.cpp =================================================================== diff -u --- CloudSyncRt/CloudSyncRtController.cpp (revision 0) +++ CloudSyncRt/CloudSyncRtController.cpp (revision cfc0df719cb5033078d0cac45ce0f6243810f2e7) @@ -0,0 +1,52 @@ +#include +#include // SQ +#include + +#include "AlphaOmega.h" // SQ +#include "CloudSyncRtController.h" +#include "LeahiMsgDefs.h" + +CloudSyncRtController::CloudSyncRtController(QObject *parent) : + QObject(parent), + _canInterface(), + _canThread(this), + _clientSocket(QString(), QWebSocketProtocol::VersionLatest, this), + _msgBuilder(this) + // SQ _protoInterface(), + // SQ _protoThread(this) +{ + _canInterface.init(_canThread); + connect(&_canInterface, &Can::CanInterface::didFrameReceive, this, &CloudSyncRtController::onFrameReceive); + + // SQ _protoInterface.init(_protoThread); + // SQ connect(this, &CloudSyncRtController::didCanMessageReceive, &_protoInterface, &proto::ProtoInterface::onCanMessageReceive); + + connect(&_clientSocket, &QWebSocket::connected, [&]() { qDebug().noquote() << "Socket connected"; }); +} + +CloudSyncRtController::~CloudSyncRtController() +{ + _clientSocket.close(); +} + +void CloudSyncRtController::openSocket(const QString host, const unsigned int port) +{ + AO_BEGIN_END_CLASS(ao) // SQ + _clientSocket.close(); + _clientSocket.open(QUrl(QString("ws://%1:%2").arg(host).arg(port))); +} + +void CloudSyncRtController::onFrameReceive(const QCanBusFrame frame) +{ + const Can::CanId canId = Can::CanId(frame.frameId()); + Can::Message &msg = _messages[canId]; + // construct the message from the received frame and determine if a complete message has been received + if (_msgBuilder.buildMessage(frame.payload(), msg, canId) && msg.isComplete()) { + // SQ Q_EMIT didCanMessageReceive(QDateTime::currentDateTime(), msg); + // if (_clientSocket.isValid()) { + qDebug().noquote() << QString("Received message with MsgId=0x%1").arg(QString("%1").arg(msg.msgId, 4, 16, QChar('0')).toUpper()); + _clientSocket.sendBinaryMessage(leahi::canMessageToProtobufByteArray(QDateTime::currentDateTime(), QStringLiteral("test_device"), msg)); + // } + msg.clear(); + } +} Index: CloudSyncRt/CloudSyncRtController.h =================================================================== diff -u --- CloudSyncRt/CloudSyncRtController.h (revision 0) +++ CloudSyncRt/CloudSyncRtController.h (revision cfc0df719cb5033078d0cac45ce0f6243810f2e7) @@ -0,0 +1,41 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#include "CanInterface.h" +#include "CanMessage.h" +#include "MessageBuilder.h" +// SQ #include "ProtoInterface.h" + +using namespace Can; + +class CloudSyncRtController : public QObject +{ + Q_OBJECT + +public: + explicit CloudSyncRtController(QObject *parent = nullptr); + ~CloudSyncRtController(); + + void openSocket(const QString host, const unsigned int port=80); + +private: + Can::CanInterface _canInterface; + QThread _canThread; + QWebSocket _clientSocket; + Can::MessageBuilder _msgBuilder; + QMap _messages; + // SQ proto::ProtoInterface _protoInterface; + // SQ QThread _protoThread; + +Q_SIGNALS: + void didCanMessageReceive(const QDateTime timestamp, const Can::Message msg); + +private Q_SLOTS: + void onFrameReceive(const QCanBusFrame frame); +}; Index: CloudSyncRt/main.cpp =================================================================== diff -u --- CloudSyncRt/main.cpp (revision 0) +++ CloudSyncRt/main.cpp (revision cfc0df719cb5033078d0cac45ce0f6243810f2e7) @@ -0,0 +1,41 @@ +#include +#include +#include +#include +#include + +#include "CloudSyncRtController.h" + +#define log qDebug().noquote() + +/*! + * \brief signalHandler + * \details When application terminates it quits gracefully. + * \param[in] sig - The Linux signal causes the termination. + */ +void signalHandler(int sig) +{ + if (sig == SIGINT) { + log << "Application terminated by SIGINT"; + qApp->quit(); + } + else if (sig == SIGTERM) { + log << "Application terminated by SIGTERM"; + qApp->quit(); + } +} + +int main(int argc, char *argv[]) +{ + signal(SIGINT , signalHandler); + signal(SIGTERM, signalHandler); + + QCoreApplication app(argc, argv); + app.setApplicationName("CloudSyncRt"); + app.setApplicationVersion("1.0"); + + CloudSyncRtController csrtController; + csrtController.openSocket("localhost", 80); + + return app.exec(); +} Index: SDDs/CloudSyncRt.png =================================================================== diff -u Binary files differ Index: SDDs/CloudSyncRt.puml =================================================================== diff -u --- SDDs/CloudSyncRt.puml (revision 0) +++ SDDs/CloudSyncRt.puml (revision cfc0df719cb5033078d0cac45ce0f6243810f2e7) @@ -0,0 +1,14 @@ +@startuml CloudSync Real-time + + participant CANDumpPlayer as CANDumpPlayer + participant CANBus as CANBus + participant CloudSyncRt as CloudSyncRt + participant CloudSyncRtServer as CloudSyncRtServer + + + -> CANDumpPlayer: Input CAN dump file + CANDumpPlayer -> CANBus: Push messages onto CAN bus + CANBus -> CloudSyncRt: Read message from CAN bus + CloudSyncRt -> CloudSyncRt: Convert CAN messages to Protobuf messages + CloudSyncRt -> CloudSyncRtServer: Send Protobuf messages to CloudSyncRt Server +@enduml Index: cmake/Debug.cmake =================================================================== diff -u --- cmake/Debug.cmake (revision 0) +++ cmake/Debug.cmake (revision cfc0df719cb5033078d0cac45ce0f6243810f2e7) @@ -0,0 +1,16 @@ +# This function is used for debugging and will dump every variable that is set +# in the cmake context. +function(dump_cmake_variables) + get_cmake_property(_variableNames VARIABLES) + list(SORT _variableNames) + foreach(_variableName ${_variableNames}) + if(ARGV0) + unset(MATCHED) + string(REGEX MATCH ${ARGV0} MATCHED ${_variableName}) + if(NOT MATCHED) + continue() + endif() + endif() + message(STATUS "${_variableName}=${${_variableName}}") + endforeach() +endfunction() Index: lib/CMakeLists.txt =================================================================== diff -u --- lib/CMakeLists.txt (revision 0) +++ lib/CMakeLists.txt (revision cfc0df719cb5033078d0cac45ce0f6243810f2e7) @@ -0,0 +1,2 @@ +add_subdirectory(MsgUtils) +add_subdirectory(Comms) Index: lib/Comms/.gitignore =================================================================== diff -u --- lib/Comms/.gitignore (revision 0) +++ lib/Comms/.gitignore (revision cfc0df719cb5033078d0cac45ce0f6243810f2e7) @@ -0,0 +1,2 @@ +cmake/ +lib/ Index: lib/Comms/CMakeLists.txt =================================================================== diff -u --- lib/Comms/CMakeLists.txt (revision 0) +++ lib/Comms/CMakeLists.txt (revision cfc0df719cb5033078d0cac45ce0f6243810f2e7) @@ -0,0 +1,91 @@ +project(Comms + DESCRIPTION "Communications Library" + LANGUAGES CXX +) + +set(CMAKE_VERBOSE_MAKEFILE ON) +set(CMAKE_AUTOMOC ON) + +find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Core) +find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Core Network SerialBus) +find_package(MsgUtils HINTS ${CMAKE_CURRENT_SOURCE_DIR}/../MsgUtils REQUIRED) + +set(INCLUDES + include/CanInterface.h + include/ProtoInterface.h +) + +set(SRCS + src/CanInterface.cpp + src/ProtoInterface.cpp +) + +add_library(${PROJECT_NAME} SHARED) + +target_sources(${PROJECT_NAME} PRIVATE ${INCLUDES} ${SRCS}) + +set_target_properties(${PROJECT_NAME} PROPERTIES + PUBLIC_HEADER "${INCLUDES}" + ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/lib" + LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/lib" + ADDITIONAL_CLEAN_FILES " \ + ${CMAKE_CURRENT_SOURCE_DIR}/bin; \ + ${CMAKE_CURRENT_SOURCE_DIR}/lib; \ + ${CMAKE_CURRENT_SOURCE_DIR}/cmake \ + " +) + +target_link_libraries(${PROJECT_NAME} PUBLIC + MsgUtils + Qt${QT_VERSION_MAJOR}::Core + Qt${QT_VERSION_MAJOR}::Network + Qt${QT_VERSION_MAJOR}::SerialBus +) + +target_include_directories(${PROJECT_NAME} PUBLIC $) +target_compile_definitions(${PROJECT_NAME} PRIVATE COMMS_LIBRARY) + +install( + TARGETS ${PROJECT_NAME} + EXPORT ${PROJECT_NAME}Targets + PUBLIC_HEADER DESTINATION "${CMAKE_CURRENT_SOURCE_DIR}/include" + RUNTIME DESTINATION "${CMAKE_CURRENT_SOURCE_DIR}/bin" + ARCHIVE DESTINATION "${CMAKE_CURRENT_SOURCE_DIR}/lib" + LIBRARY DESTINATION "${CMAKE_CURRENT_SOURCE_DIR}/lib" COMPONENT dev +) + +# set up include-directories +include_directories("${CMAKE_CURRENT_SOURCE_DIR}/include" "${PROJECT_BINARY_DIR}") + +# Add all targets to the build-tree export set +export( + TARGETS ${PROJECT_NAME} + FILE "${PROJECT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/${PROJECT_NAME}Targets.cmake" +) + +# Export the package for use from the build-tree (this registers the build-tree +# with a global CMake-registry) +export(PACKAGE ${PROJECT_NAME}) + +file(RELATIVE_PATH REL_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/cmake" "${CMAKE_CURRENT_SOURCE_DIR}/include") +set(CONF_INCLUDE_DIRS "\${COMMS_CMAKE_DIR}/${REL_INCLUDE_DIR}") + +# Create the ${PROJECT_NAME}Config.cmake and +# ${PROJECT_NAME}ConfigVersion.cmake files ... for the build tree +configure_file(${PROJECT_NAME}Config.cmake.in "${CMAKE_CURRENT_SOURCE_DIR}/cmake/${PROJECT_NAME}Config.cmake" @ONLY) +configure_file(${PROJECT_NAME}ConfigVersion.cmake.in "${CMAKE_CURRENT_SOURCE_DIR}/cmake/${PROJECT_NAME}ConfigVersion.cmake" @ONLY) + +# ... for the install tree +configure_file(${PROJECT_NAME}Config.cmake.in "${PROJECT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/${PROJECT_NAME}Config.cmake" @ONLY) +configure_file( ${PROJECT_NAME}ConfigVersion.cmake.in "${PROJECT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/${PROJECT_NAME}ConfigVersion.cmake" @ONLY) + +# Install the ${PROJECT_NAME}Config.cmake and ${PROJECT_NAME}ConfigVersion.cmake +install( + FILES + "${PROJECT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/${PROJECT_NAME}Config.cmake" + "${PROJECT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/${PROJECT_NAME}ConfigVersion.cmake" + DESTINATION "${CMAKE_CURRENT_SOURCE_DIR}/cmake" +) + +# Install the export set for use with the install-tree +install(EXPORT ${PROJECT_NAME}Targets DESTINATION "${CMAKE_CURRENT_SOURCE_DIR}/cmake") Index: lib/Comms/Comms.pc.in =================================================================== diff -u --- lib/Comms/Comms.pc.in (revision 0) +++ lib/Comms/Comms.pc.in (revision cfc0df719cb5033078d0cac45ce0f6243810f2e7) @@ -0,0 +1,12 @@ +prefix=@CMAKE_CURRENT_BINARY_DIR@ +exec_prefix=@CMAKE_CURRENT_BINARY_DIR@ +libdir=${exec_prefix}/lib +includedir=${prefix}/include + +Name: @PROJECT_NAME@ +Description: @PROJECT_DESCRIPTION@ +Version: @PROJECT_VERSION@ + +Requires: +Libs: -L${libdir} -l@PROJECT_NAME@ +Cflags: -I${includedir} Index: lib/Comms/CommsConfig.cmake.in =================================================================== diff -u --- lib/Comms/CommsConfig.cmake.in (revision 0) +++ lib/Comms/CommsConfig.cmake.in (revision cfc0df719cb5033078d0cac45ce0f6243810f2e7) @@ -0,0 +1,17 @@ +# - Config file for the library package +# It defines the following variables +# COMMS_INCLUDE_DIRS - include directories +# COMMS_LIBRARIES - libraries to link against +# COMMS_EXECUTABLE - binary executable directories + +# Compute paths +get_filename_component(COMMS_CMAKE_DIR "${CMAKE_CURRENT_LIST_FILE}" PATH) +set(COMMS_INCLUDE_DIRS "@CONF_INCLUDE_DIRS@") + +# Our library dependencies (contains definitions for IMPORTED targets) +if(NOT TARGET Comms AND NOT Comms_BINARY_DIR) + include("${COMMS_CMAKE_DIR}/CommsTargets.cmake") +endif() + +# These are IMPORTED targets created by CommsTargets.cmake +set(COMMS_LIBRARIES Comms) Index: lib/Comms/CommsConfigVersion.cmake.in =================================================================== diff -u --- lib/Comms/CommsConfigVersion.cmake.in (revision 0) +++ lib/Comms/CommsConfigVersion.cmake.in (revision cfc0df719cb5033078d0cac45ce0f6243810f2e7) @@ -0,0 +1,11 @@ +set(PACKAGE_VERSION "@PROJECT_VERSION@") + +# Check whether the requested PACKAGE_FIND_VERSION is compatible +if("${PACKAGE_VERSION}" VERSION_LESS "${PACKAGE_FIND_VERSION}") + set(PACKAGE_VERSION_COMPATIBLE FALSE) +else() + set(PACKAGE_VERSION_COMPATIBLE TRUE) + if ("${PACKAGE_VERSION}" VERSION_EQUAL "${PACKAGE_FIND_VERSION}") + set(PACKAGE_VERSION_EXACT TRUE) + endif() +endif() Index: lib/Comms/include/CanInterface.h =================================================================== diff -u --- lib/Comms/include/CanInterface.h (revision 0) +++ lib/Comms/include/CanInterface.h (revision cfc0df719cb5033078d0cac45ce0f6243810f2e7) @@ -0,0 +1,127 @@ +/*! + * + * Copyright (c) 2020-2024 Diality Inc. - All Rights Reserved. + * \copyright + * THIS CODE MAY NOT BE COPIED OR REPRODUCED IN ANY FORM, IN PART OR IN + * WHOLE, WITHOUT THE EXPLICIT PERMISSION OF THE COPYRIGHT OWNER. + * + * \file CanInterface.h + * \author (last) Behrouz NematiPour + * \date (last) 18-Jul-2023 + * \author (original) Behrouz NematiPour + * \date (original) 26-Aug-2020 + * + */ +#pragma once + +// Qt +#include +#include +#include + +// Project +#include "CanMessage.h" + +// namespace +namespace Can +{ + +/*! + * \brief CANBus interface + * \details This class contains the interface to CANBus + * And utilizes Qt QCanBus to interact with the CANBus + * This class works only with the QCanBusFrame frames. + * On the OS side there is a driver installed to convert SPI to CAN + * Since the GUI Board by itself does not contain the CANBus. + * Application would know nothing about the SPI-TO-CAN + */ +class CanInterface : public QObject +{ + Q_OBJECT + +public: + CanInterface(QObject *parent = nullptr); + + void enableConsoleOut(bool vEnabled); + void quitDevice(); + QString status() const; + +public Q_SLOTS: + bool init(); + bool init(QThread &vThread); + void quit(); + +Q_SIGNALS: + /*! + * \brief didFrameError + * \details If and error occurs on CanDevice after the error is processed + * this signal can be used as a notifier. + * \param vStatus - CanDevice status with some extra information. + */ + void didFrameError(const QString &vStatus); + + /*! + * \brief didFrameReceive + * \details This signal will be emitted when a frame has been received + * \param vFrame - The Frame which has been received + */ + void didFrameReceive(const QCanBusFrame vFrame); + + /*! + * \brief didFrameTransmit + * \details After the frame has been transmitted this signal can be used as a notifier. + * \param ok - is true if the frame has been transmitted successfully + */ + void didFrameTransmit(bool ok); + + /*! + * \brief didFrameWritten + * \details After the frame transmission is done + * and acknowledged by a node on the CANBus, + * this signal is emitted. + * \param vCount is the number of frame which has been written + */ + void didFrameWritten(qint64 vCount); + +private: + static QString frameFlags(const QCanBusFrame &vFrame); + + void consoleOut (const QCanBusFrame &vFrame, const QString &vFrameCount); + void initConnections(); + bool initDevice(); + void initThread(QThread &vThread); + void quitThread(); + void status (const QString &vDescription, QString vError = ""); + bool testDevice(); + bool transmit (const QCanBusFrame &vFrame); + + FrameCount rxCount(); + FrameCount txCount(); + FrameCount erCount(); + + // constants + const QString _canType = "socketcan"; + const QString _canInterface = "can0"; + const int _canBitRate = 250000; + const bool _canFDKey = false; + + // member variables + QSharedPointer _canDevice = nullptr; + QString _canStatus = ""; + bool _enableConsoleOut = false; + + // QThread *_thread = nullptr; + bool _init = false; + + FrameCount _rxFrameCount = 0; + FrameCount _txFrameCount = 0; + FrameCount _erFrameCount = 0; + +private Q_SLOTS: + void onFrameTransmit (const QCanBusFrame &vFrame); + void onFrameReceive (); + void onFrameError (QCanBusDevice::CanBusError vError); + void onFrameWritten (qint64 vFramesCount); +}; + +} // namespace Can Index: lib/Comms/include/ProtoInterface.h =================================================================== diff -u --- lib/Comms/include/ProtoInterface.h (revision 0) +++ lib/Comms/include/ProtoInterface.h (revision cfc0df719cb5033078d0cac45ce0f6243810f2e7) @@ -0,0 +1,60 @@ +/*! + * + * Copyright (c) 2020-2024 Diality Inc. - All Rights Reserved. + * \copyright + * THIS CODE MAY NOT BE COPIED OR REPRODUCED IN ANY FORM, IN PART OR IN + * WHOLE, WITHOUT THE EXPLICIT PERMISSION OF THE COPYRIGHT OWNER. + * + * \file ProtoInterface.h + * \author (last) Behrouz NematiPour + * \date (last) 18-Jul-2023 + * \author (original) Behrouz NematiPour + * \date (original) 26-Aug-2020 + * + */ +#pragma once + +// Qt +#include +#include +#include + +#include "CanMessage.h" + +// namespace +namespace proto +{ + +/*! + * \brief Protobuf interface + * \details This class contains the interface to Protobuf + * And utilizes Qt QProtoBus to interact with the CANBus + * This class works only with the QProtoBusFrame frames. + * On the OS side there is a driver installed to convert SPI to CAN + * Since the GUI Board by itself does not contain the CANBus. + * Application would know nothing about the SPI-TO-CAN + */ +class ProtoInterface : public QObject +{ + Q_OBJECT + +public: + ProtoInterface(QObject *parent = nullptr); + + void quitDevice(); + +public Q_SLOTS: + bool init(); + bool init(QThread &vThread); + void onCanMessageReceive(const QDateTime timestamp, const Can::Message msg); + void quit(); + +private: + void initConnections(); + void initThread(QThread &vThread); + void quitThread(); + + bool _init = false; +}; + +} // namespace Proto Index: lib/Comms/src/CanInterface.cpp =================================================================== diff -u --- lib/Comms/src/CanInterface.cpp (revision 0) +++ lib/Comms/src/CanInterface.cpp (revision cfc0df719cb5033078d0cac45ce0f6243810f2e7) @@ -0,0 +1,404 @@ +/*! * + * Copyright (c) 2020-2024 Diality Inc. - All Rights Reserved. + * \copyright + * THIS CODE MAY NOT BE COPIED OR REPRODUCED IN ANY FORM, IN PART OR IN + * WHOLE, WITHOUT THE EXPLICIT PERMISSION OF THE COPYRIGHT OWNER. + * + * \file CanInterface.cpp + * \author (last) Behrouz NematiPour + * \date (last) 18-Jul-2023 + * \author (original) Behrouz NematiPour + * \date (original) 26-Aug-2020 + * + */ +#include "CanInterface.h" + +// Qt +#include +#include +#include +#include +// #include + +// stl +#include + +// Project +// #include "Logger.h" +// #include "FrameInterface.h" + +namespace Can +{ + +static void qRegister() +{ + qRegisterMetaType("QCanBusFrame"); +} +Q_COREAPP_STARTUP_FUNCTION(qRegister) + +/*! + * \brief CanInterface::CanInterface + * \details Constructor + * \param parent - QObject parent owner object. + * Qt handles the children destruction by their parent objects life-cycle. + */ +CanInterface::CanInterface(QObject *parent) : + QObject(parent) +{ +} + +/*! + * \brief CanInterface::enableConsoleOut + * \details Enables or Disables the console output and logs the status + * \param vEnabled - Enable console output if true + */ +void CanInterface::enableConsoleOut(bool vEnabled) +{ + if (_enableConsoleOut != vEnabled) { + _enableConsoleOut = vEnabled; + qDebug().noquote() << (_enableConsoleOut ? "Console out CanInterface enabled" : "Console out CanInterface disabled"); + } +} + +/*! + * \brief CanInterface::deleteDevice + * \details Disconnect the CANBus device and deletes the pointer + */ +void CanInterface::quitDevice() +{ + if (_canDevice) { + _canDevice->disconnectDevice(); + _canDevice.reset(nullptr, &QObject::deleteLater); + status(tr("Disconnected")); + } +} + +/*! + * \brief CanInterface status + * \details CanInterface status description + * \return The current stores status + */ +QString CanInterface::status() const +{ + return _canStatus; +} + +/*! + * \brief CanInterface Initialization + * \details Initializes the CANBus and checks if can be connected + * \return true if connected, false otherwise + */ +bool CanInterface::init() +{ + if (_init) { + return false; + } + _init = true; + + if (! initDevice()) { + return false; + } + if (! testDevice()) { + return false; + } + + initConnections(); + + status(QString("Connected")); + qDebug().noquote() << QString("UI,%1,%2").arg(QString("%1 Initialized").arg(metaObject()->className())).arg(status()); + + return true; +} + +/*! + * \brief CanInterface::init + * \details Initialized the Class by calling the init() method first + * And initializes the thread vThread by calling initThread + * on success init(). + * \param vThread - the thread + * \return returns the return value of the init() method + */ +bool CanInterface::init(QThread &vThread) +{ + qDebug().noquote() << "*** CanInterface::init"; // SQ + if (! init()) { + return false; + } + initThread(vThread); + return true; +} + +/*! + * \brief CanInterface quit + * \details quits the class + * Calls quitThread + */ +void CanInterface::quit() +{ + quitThread(); // verified +} + +/*! + * \brief frameFlags + * \details CANBus message frame type as flags + * \param vFrame - CANBus message frame + * \return Frame flag as QString + */ +QString CanInterface::frameFlags(const QCanBusFrame &vFrame) +{ + QString result = QLatin1String(" --- "); + + if (vFrame.hasBitrateSwitch()) { + result[1] = QLatin1Char('B'); + } + if (vFrame.hasErrorStateIndicator()) { + result[2] = QLatin1Char('E'); + } + if (vFrame.hasLocalEcho()) { + result[3] = QLatin1Char('L'); + } + + return result; +} + +/*! + * \brief CanInterface console Output messaging + * \details Sends out formatted CANBus message to the console + * for debugging purposes. + * \param vFrame - The CANBus frame to be sent out + */ +void CanInterface::consoleOut(const QCanBusFrame &vFrame, const QString &vFrameCount) +{ + if (! _enableConsoleOut) { + return; + } + + const QString time = QString::fromLatin1("%1.%2 ") + .arg(vFrame.timeStamp().seconds(), 10, 10, QLatin1Char(' ')) + .arg(vFrame.timeStamp().microSeconds() / 100, 4, 10, QLatin1Char('0')); + const QString flags = frameFlags(vFrame); + QString view; + if (vFrame.frameType() == QCanBusFrame::ErrorFrame) { + if (_canDevice) { + view = _canDevice->interpretErrorFrame(vFrame); + } + } + else { + view = vFrame.payload().toHex('.').replace(QByteArray("a5"),QByteArray("\033[1;33mA5\033[0m")); + } + // the fprintf is used for the colored output + fprintf(stderr, "%s %s %s %i %s\n", vFrameCount.toLatin1().constData(), time.toLatin1().constData(), flags.toLatin1().constData(), vFrame.frameId(), view.toLatin1().constData()); +} + +/*! + * \brief CanInterface connections definition + * \details Initializes the required signal/slot connection between this class and other objects + * to be able to communicate. + */ +void CanInterface::initConnections() +{ + if (_canDevice) { + connect(_canDevice.get(), &QCanBusDevice::framesReceived, this, &CanInterface::onFrameReceive); + connect(_canDevice.get(), &QCanBusDevice::errorOccurred, this, &CanInterface::onFrameError); + connect(_canDevice.get(), &QCanBusDevice::framesWritten, this, &CanInterface::onFrameWritten); + } + // connect(&_FrameInterface, SIGNAL(didFrameTransmit(QCanBusFrame)), + // this , SLOT( onFrameTransmit(QCanBusFrame))); +} + +/*! + * \brief CanInterface::createDevice + * \details Creates the CANBus device + * \return false if cannot create the device + */ +bool CanInterface::initDevice() +{ + QString mError; + _canDevice.reset(QCanBus::instance()->createDevice(_canType, _canInterface, &mError), &QObject::deleteLater); + if (!_canDevice) { + status(tr("Device Creation"), mError); + qDebug().noquote() << status(); + return false; + } + _canDevice->setConfigurationParameter(QCanBusDevice::CanFdKey , _canFDKey ); + _canDevice->setConfigurationParameter(QCanBusDevice::BitRateKey , _canBitRate ); + return true; +} + +/*! + * \brief CanInterface::initThread + * \details Moves this object into the thread vThread. + * And checks that this method is called from main thread. + * Also connects quitThread to application aboutToQuit. + * \param vThread - the thread + */ +void CanInterface::initThread(QThread &vThread) +{ + qDebug().noquote() << "*** CanInterface::initThread"; // SQ + // runs in main thread + Q_ASSERT_X(QThread::currentThread() == qApp->thread() , __func__, "The Class initialization must be done in Main Thread" ); + vThread.setObjectName(QString("%1_Thread").arg(metaObject()->className())); + connect(qApp, &QCoreApplication::aboutToQuit, this, &CanInterface::quit); + moveToThread(&vThread); + vThread.start(); +} + +/*! + * \brief CanInterface::quitThread + * \details Moves this object to main thread to be handled by QApplication + * And to be destroyed there. + */ +void CanInterface::quitThread() +{ + // runs in thread + if (QThread::currentThread() == qApp->thread()) { + moveToThread(qApp->thread()); // verified + } +} + +/*! + * \brief CanInterface status + * \details Sets the Can interface status description + * \param vDescription - Description about the CANBus Interface errors + * \param vError - Qt CANBus Interface Error + */ +void CanInterface::status(const QString &vDescription, QString vError) +{ + const QString mError = _canDevice ? _canDevice->errorString() + vError : vError; + _canStatus = QString("%1 '%2[%3]', %4").arg(vDescription).arg(_canType).arg(_canInterface).arg(mError); +} + +/*! + * \brief CanInterface::checkDevice + * \details Checks if the device has been connected. + * \return true on successful device test and connection. + */ +bool CanInterface::testDevice() +{ + if (_canDevice && !_canDevice->connectDevice()) { + status(tr("Connection")); + qDebug().noquote() << status(); + _canDevice.reset(nullptr, &QObject::deleteLater); + return false; + } + return true; +} + +/*! + * \brief CanInterface send + * \details send a frame over the CANBus + * \param vFrame - CANBus message frame + */ +bool CanInterface::transmit(const QCanBusFrame &vFrame) +{ + return _canDevice ? _canDevice->writeFrame(vFrame) : false; +} + +/*! + * \brief CanInterface::rxCount + * \details count received frames up the size of the FrameCount type size + * \return frame count + */ +FrameCount CanInterface::rxCount() +{ + Types::safeIncrement(_rxFrameCount); + return _rxFrameCount; +} + +/*! + * \brief CanInterface::txCount + * \details count transmitted frames up the size of the FrameCount type size + * \return frame count + */ +FrameCount CanInterface::txCount() +{ + Types::safeIncrement(_txFrameCount); + return _txFrameCount; +} + +/*! + * \brief CanInterface::erCount + * \details count errors happened + * \return error count + */ +FrameCount CanInterface::erCount() +{ + Types::safeIncrement(_erFrameCount); + return _erFrameCount; +} + +/*! + * \brief CanInterface onError + * \details CANBus error handler which sets the can status description + * \param vError - CANBus error + */ +void CanInterface::onFrameError(QCanBusDevice::CanBusError vError) +{ + erCount(); + switch (vError) { + case QCanBusDevice::ReadError: + case QCanBusDevice::WriteError: + case QCanBusDevice::ConnectionError: + case QCanBusDevice::ConfigurationError: + case QCanBusDevice::UnknownError: + // log the error each 100k frame and if error is different + if (_canStatus != (_canDevice ? _canDevice->errorString() : "") || !(_erFrameCount % 100000)) { + _canStatus = _canDevice ? _canDevice->errorString() : ""; + qDebug().noquote() << QString("%1 - %2").arg(_erFrameCount).arg(_canStatus); + } + break; + default: + break; + + } + emit didFrameError(_canStatus); +} + +/*! + * \brief CanInterface::onFrameWritten + * \details This is the slot connected to the signal + * which is emitted every time a payload of frames + * has been written to the CANBus bus. + * \param vFramesCount - The framesCount argument is set to the number of frames + * that were written in this payload. + */ +void CanInterface::onFrameWritten(qint64 vFramesCount) +{ + static FrameCount mFrameCount = 0; + Types::safeIncrement(mFrameCount, vFramesCount); + emit didFrameWritten(vFramesCount); +} + +/*! + * \brief CanInterface onFrameReceived + * \details CANBus message read handler + */ +void CanInterface::onFrameReceive() +{ + while (_canDevice && _canDevice->framesAvailable()) { + const QCanBusFrame frame = _canDevice->readFrame(); + rxCount(); + if (_enableConsoleOut) { + consoleOut(frame, QString("Rx:%1").arg(_rxFrameCount)); + } + emit didFrameReceive(frame); + } +} + +/*! + * \brief CanInterface onActionPerform + * \details sends a CANBus message frame of the CANBus message of the performed action + * This is a response from application UI to HD device + * \param vFrame - CANBus message frame + */ +void CanInterface::onFrameTransmit(const QCanBusFrame &vFrame) +{ + const bool ok = transmit(vFrame); + txCount(); + if (_enableConsoleOut) { + consoleOut(vFrame, QString("Tx:%1").arg(_txFrameCount)); + } + emit didFrameTransmit(ok); +} + +} // namespace Can Index: lib/Comms/src/ProtoInterface.cpp =================================================================== diff -u --- lib/Comms/src/ProtoInterface.cpp (revision 0) +++ lib/Comms/src/ProtoInterface.cpp (revision cfc0df719cb5033078d0cac45ce0f6243810f2e7) @@ -0,0 +1,145 @@ +/*! + * + * Copyright (c) 2020-2024 Diality Inc. - All Rights Reserved. + * \copyright + * THIS CODE MAY NOT BE COPIED OR REPRODUCED IN ANY FORM, IN PART OR IN + * WHOLE, WITHOUT THE EXPLICIT PERMISSION OF THE COPYRIGHT OWNER. + * + * \file ProtoInterface.cpp + * \author (last) Behrouz NematiPour + * \date (last) 18-Jul-2023 + * \author (original) Behrouz NematiPour + * \date (original) 26-Aug-2020 + * + */ +#include "LeahiMsgDefs.h" +#include "ProtoInterface.h" + +// Qt +#include +#include +#include + +namespace proto +{ + +static void qRegister() +{ + qRegisterMetaType("Can::Message"); +} +Q_COREAPP_STARTUP_FUNCTION(qRegister) + +/*! + * \brief ProtoInterface::ProtoInterface + * \details Constructor + * \param parent - QObject parent owner object. + * Qt handles the children destruction by their parent objects life-cycle. + */ +ProtoInterface::ProtoInterface(QObject *parent) : + QObject(parent) +{ +} + +/*! + * \brief ProtoInterface::deleteDevice + * \details Disconnect the CANBus device and deletes the pointer + */ +void ProtoInterface::quitDevice() +{ +} + +/*! + * \brief ProtoInterface Initialization + * \details Initializes the CANBus and checks if can be connected + * \return true if connected, false otherwise + */ +bool ProtoInterface::init() +{ + if (_init) { + return false; + } + _init = true; + + initConnections(); + + return true; +} + +/*! + * \brief ProtoInterface::init + * \details Initialized the Class by calling the init() method first + * And initializes the thread vThread by calling initThread + * on success init(). + * \param vThread - the thread + * \return returns the return value of the init() method + */ +bool ProtoInterface::init(QThread &vThread) +{ + if (! init()) { + return false; + } + initThread(vThread); + return true; +} + +void ProtoInterface::onCanMessageReceive(const QDateTime timestamp, const Can::Message msg) +{ + msg.dump(); + QByteArray proto_buffer = leahi::canMessageToProtobufByteArray(timestamp, QStringLiteral("test_device"), msg); + QString proto_string; + for (int i = 0; i < proto_buffer.size(); i++) { + proto_string.append(QString(" 0x%1").arg(QString("%1").arg(quint8(proto_buffer[i]), 2, 16, QChar('0')).toUpper())); + } + qDebug().noquote() << QString("protobuf(%1) =%2\n").arg(proto_buffer.length(), 0, 10, QChar('0')).arg(proto_string); +} + +/*! + * \brief ProtoInterface quit + * \details quits the class + * Calls quitThread + */ +void ProtoInterface::quit() +{ + quitThread(); // verified +} + +/*! + * \brief ProtoInterface connections definition + * \details Initializes the required signal/slot connection between this class and other objects + * to be able to communicate. + */ +void ProtoInterface::initConnections() +{ +} + +/*! + * \brief ProtoInterface::initThread + * \details Moves this object into the thread vThread. + * And checks that this method is called from main thread. + * Also connects quitThread to application aboutToQuit. + * \param vThread - the thread + */ +void ProtoInterface::initThread(QThread &vThread) +{ + // runs in main thread + Q_ASSERT_X(QThread::currentThread() == qApp->thread() , __func__, "The Class initialization must be done in Main Thread" ); + vThread.setObjectName(QString("%1_Thread").arg(metaObject()->className())); + connect(qApp, &QCoreApplication::aboutToQuit, this, &ProtoInterface::quit); + moveToThread(&vThread); + vThread.start(); +} + +/*! + * \brief ProtoInterface::quitThread + * \details Moves this object to main thread to be handled by QApplication + * And to be destroyed there. + */ +void ProtoInterface::quitThread() +{ + // runs in thread + if (QThread::currentThread() == qApp->thread()) { + moveToThread(qApp->thread()); // verified + } +} + +} // namespace proto Index: lib/MsgUtils/.gitignore =================================================================== diff -u --- lib/MsgUtils/.gitignore (revision 0) +++ lib/MsgUtils/.gitignore (revision cfc0df719cb5033078d0cac45ce0f6243810f2e7) @@ -0,0 +1,3 @@ +cmake/MsgUtilsConfig.cmake +cmake/MsgUtilsConfigVersion.cmake +lib/ Index: lib/MsgUtils/CMakeLists.txt =================================================================== diff -u --- lib/MsgUtils/CMakeLists.txt (revision 0) +++ lib/MsgUtils/CMakeLists.txt (revision cfc0df719cb5033078d0cac45ce0f6243810f2e7) @@ -0,0 +1,149 @@ +project(MsgUtils + DESCRIPTION "Message Utilities Library" + LANGUAGES CXX +) + +set(CMAKE_VERBOSE_MAKEFILE ON) + +set(CMAKE_AUTOMOC ON) + +if(POLICY CMP0167) + cmake_policy(SET CMP0167 NEW) +endif() + +find_package(Protobuf COMPONENTS REQUIRED) +find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Core) +find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Core Network SerialBus) + +set(MSGUTILS_SCRIPTS_DIR ${CMAKE_CURRENT_SOURCE_DIR}/scripts) +include(cmake/MsgUtils.cmake) + +# set(DENALI_MSG_CSV ${CMAKE_CURRENT_SOURCE_DIR}/../../data/FW_Messages_List.csv) + +set(LEAHI_MSG_CSV ${CMAKE_CURRENT_SOURCE_DIR}/../../data/Leahi_Staging_FW_Messages_List.csv) + +set(INCLUDES + include/CanMessage.h + include/crc.h + # include/DenaliLogUtils.h + # include/encryption.h + include/format.h + include/FrameInterface.h + include/main.h + include/MessageBuilder.h + # include/MessageDispatcher.h + # include/MessageGlobals.h + # include/MessageInterpreter.h + # include/qrcodegen.h + include/types.h +) + +set(SRCS + src/crc.cpp + # src/DenaliLogUtils.cpp + # src/encryption.cpp + src/format.cpp + src/FrameInterface.cpp + src/MessageBuilder.cpp + # src/MessageDispatcher.cpp + # src/MessageInterpreter.cpp + # src/qrcodegen.cpp + src/types.cpp +) + +set(GENERATED_INCLUDES + # include/DenaliMsgDefs.h + # include/DenaliMsgDefs.pb.h + include/LeahiMsgDefs.h + include/LeahiMsgDefs.pb.h +) + +set(GENERATED_SRCS + # src/DenaliMsgDefs.cpp + # src/DenaliMsgDefs.pb.cc + src/LeahiMsgDefs.cpp + src/LeahiMsgDefs.pb.cc +) + +# generate_msg_defs_cpp(DENALI_MSG_CSV Denali ${CMAKE_CURRENT_SOURCE_DIR}/include ${CMAKE_CURRENT_SOURCE_DIR}/src denali) +# generate_protobuf(DENALI_MSG_CSV DenaliMsgDefs.proto ${CMAKE_CURRENT_BINARY_DIR}/data denali) +# generate_protobuf_cpp(DenaliMsgDefs.proto ${CMAKE_CURRENT_BINARY_DIR}/data ${CMAKE_CURRENT_SOURCE_DIR}/include ${CMAKE_CURRENT_SOURCE_DIR}/src) +generate_msg_defs_cpp(LEAHI_MSG_CSV Leahi ${CMAKE_CURRENT_SOURCE_DIR}/include ${CMAKE_CURRENT_SOURCE_DIR}/src leahi) +generate_protobuf(LEAHI_MSG_CSV LeahiMsgDefs.proto ${CMAKE_CURRENT_BINARY_DIR}/data leahi) +generate_protobuf_cpp(LeahiMsgDefs.proto ${CMAKE_CURRENT_BINARY_DIR}/data ${CMAKE_CURRENT_SOURCE_DIR}/include ${CMAKE_CURRENT_SOURCE_DIR}/src) + +add_library(${PROJECT_NAME} SHARED) + +target_sources(${PROJECT_NAME} PRIVATE ${INCLUDES} ${GENERATED_INCLUDES} ${SRCS} ${GENERATED_SRCS}) + +set_target_properties(${PROJECT_NAME} PROPERTIES + PUBLIC_HEADER "${INCLUDES}" + ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/lib" + LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/lib" + ADDITIONAL_CLEAN_FILES " \ + ${CMAKE_CURRENT_SOURCE_DIR}/bin; \ + ${CMAKE_CURRENT_SOURCE_DIR}/lib; \ + ${CMAKE_CURRENT_SOURCE_DIR}/cmake; \ + ${GENERATED_INCLUDES}; \ + ${GENERATED_SRCS} \ + " +) + +target_link_libraries(${PROJECT_NAME} PUBLIC + ${Protobuf_LIBRARIES} + Qt${QT_VERSION_MAJOR}::Core + Qt${QT_VERSION_MAJOR}::Network + Qt${QT_VERSION_MAJOR}::SerialBus +) + +target_include_directories(${PROJECT_NAME} PUBLIC + $ + ${Protobuf_INCLUDE_DIR} +) + +file(MAKE_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/include ${CMAKE_CURRENT_SOURCE_DIR}/src) + +install( + TARGETS ${PROJECT_NAME} + EXPORT ${PROJECT_NAME}Targets + PUBLIC_HEADER DESTINATION "${CMAKE_CURRENT_SOURCE_DIR}/include" + RUNTIME DESTINATION "${CMAKE_CURRENT_SOURCE_DIR}/bin" + ARCHIVE DESTINATION "${CMAKE_CURRENT_SOURCE_DIR}/lib" + LIBRARY DESTINATION "${CMAKE_CURRENT_SOURCE_DIR}/lib" COMPONENT dev +) + +# set up include-directories +include_directories("${CMAKE_CURRENT_SOURCE_DIR}/include" "${PROJECT_BINARY_DIR}") + +# Add all targets to the build-tree export set +export( + TARGETS ${PROJECT_NAME} + FILE "${PROJECT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/${PROJECT_NAME}Targets.cmake" +) + +# Export the package for use from the build-tree (this registers the build-tree +# with a global CMake-registry) +export(PACKAGE ${PROJECT_NAME}) + +file(RELATIVE_PATH REL_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/cmake" "${CMAKE_CURRENT_SOURCE_DIR}/include") +set(CONF_INCLUDE_DIRS "\${MSGUTILS_CMAKE_DIR}/${REL_INCLUDE_DIR}") + +# Create the ${PROJECT_NAME}Config.cmake and +# ${PROJECT_NAME}ConfigVersion.cmake files ... for the build tree +configure_file(${PROJECT_NAME}Config.cmake.in "${CMAKE_CURRENT_SOURCE_DIR}/cmake/${PROJECT_NAME}Config.cmake" @ONLY) +configure_file(${PROJECT_NAME}ConfigVersion.cmake.in "${CMAKE_CURRENT_SOURCE_DIR}/cmake/${PROJECT_NAME}ConfigVersion.cmake" @ONLY) + +# ... for the install tree +configure_file(${PROJECT_NAME}Config.cmake.in "${PROJECT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/${PROJECT_NAME}Config.cmake" @ONLY) +configure_file( ${PROJECT_NAME}ConfigVersion.cmake.in "${PROJECT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/${PROJECT_NAME}ConfigVersion.cmake" @ONLY) + +# Install the ${PROJECT_NAME}Config.cmake and ${PROJECT_NAME}ConfigVersion.cmake +install( + FILES + "${PROJECT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/${PROJECT_NAME}Config.cmake" + "${PROJECT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/${PROJECT_NAME}ConfigVersion.cmake" + DESTINATION "${CMAKE_CURRENT_SOURCE_DIR}/cmake" +) + +# Install the export set for use with the install-tree +install(EXPORT ${PROJECT_NAME}Targets DESTINATION "${CMAKE_CURRENT_SOURCE_DIR}/cmake") Index: lib/MsgUtils/MsgUtils.pc.in =================================================================== diff -u --- lib/MsgUtils/MsgUtils.pc.in (revision 0) +++ lib/MsgUtils/MsgUtils.pc.in (revision cfc0df719cb5033078d0cac45ce0f6243810f2e7) @@ -0,0 +1,12 @@ +prefix=@CMAKE_CURRENT_BINARY_DIR@ +exec_prefix=@CMAKE_CURRENT_BINARY_DIR@ +libdir=${exec_prefix}/lib +includedir=${prefix}/include + +Name: @PROJECT_NAME@ +Description: @PROJECT_DESCRIPTION@ +Version: @PROJECT_VERSION@ + +Requires: +Libs: -L${libdir} -l@PROJECT_NAME@ +Cflags: -I${includedir} Index: lib/MsgUtils/MsgUtilsConfig.cmake.in =================================================================== diff -u --- lib/MsgUtils/MsgUtilsConfig.cmake.in (revision 0) +++ lib/MsgUtils/MsgUtilsConfig.cmake.in (revision cfc0df719cb5033078d0cac45ce0f6243810f2e7) @@ -0,0 +1,19 @@ +# - Config file for the library package +# It defines the following variables +# MSGUTILS_INCLUDE_DIRS - include directories for this library +# MSGUTILS_SCRIPTS_DIR - scripts directories for this library +# MSGUTILS_LIBRARIES - other libraries to link against +# MSGUTILS_EXECUTABLE - any executables produced for this library (e.g. examples, unit tests, etc.) +# Compute paths +get_filename_component(MSGUTILS_CMAKE_DIR "${CMAKE_CURRENT_LIST_FILE}" PATH) +set(MSGUTILS_INCLUDE_DIRS "@CONF_INCLUDE_DIRS@") +set(MSGUTILS_SCRIPTS_DIR "${MSGUTILS_SOURCE_DIR}/scripts") + +# Our library dependencies (contains definitions for IMPORTED targets) +if(NOT TARGET MsgUtils AND NOT MsgUtils_BINARY_DIR) + include("${MSGUTILS_CMAKE_DIR}/MsgUtilsTargets.cmake") +endif() + +# These are IMPORTED targets created by MsgUtilsTargets.cmake +set(MSGUTILS_LIBRARIES MsgUtils) +set(MSGUTILS_EXECUTABLE MsgUtilsTest) Index: lib/MsgUtils/MsgUtilsConfigVersion.cmake.in =================================================================== diff -u --- lib/MsgUtils/MsgUtilsConfigVersion.cmake.in (revision 0) +++ lib/MsgUtils/MsgUtilsConfigVersion.cmake.in (revision cfc0df719cb5033078d0cac45ce0f6243810f2e7) @@ -0,0 +1,11 @@ +set(PACKAGE_VERSION "@PROJECT_VERSION@") + +# Check whether the requested PACKAGE_FIND_VERSION is compatible +if("${PACKAGE_VERSION}" VERSION_LESS "${PACKAGE_FIND_VERSION}") + set(PACKAGE_VERSION_COMPATIBLE FALSE) +else() + set(PACKAGE_VERSION_COMPATIBLE TRUE) + if ("${PACKAGE_VERSION}" VERSION_EQUAL "${PACKAGE_FIND_VERSION}") + set(PACKAGE_VERSION_EXACT TRUE) + endif() +endif() Index: lib/MsgUtils/cmake/MsgUtils.cmake =================================================================== diff -u --- lib/MsgUtils/cmake/MsgUtils.cmake (revision 0) +++ lib/MsgUtils/cmake/MsgUtils.cmake (revision cfc0df719cb5033078d0cac45ce0f6243810f2e7) @@ -0,0 +1,146 @@ +# \brief This function adds a custom command to generate message defintions from inputted CSV files +# \param[in] _input_csvs list of CSV files to use to generate the header +# \param[in] _device_name name of device these files are being generated for +# \param[in] _header_dir directory where the header file will be outputted +# \param[in] _source_dir directory where the source file will be outputted +# \param[in] _namespace C++ namespace for the outputted message definitions +function(generate_msg_defs_cpp _input_csvs _device_name _header_dir _source_dir _namespace) + find_package(Python3 COMPONENTS Interpreter Development REQUIRED) + + # create a variable with the full path output header file + get_filename_component(_header_dir ${_header_dir} ABSOLUTE) # cmake < 3.20 + # cmake >= 3.20: cmake_path(ABSOLUTE_PATH _header_path) + string(JOIN "/" _header_path ${_header_dir} ${_device_name}MsgDefs.h) + + # create a variable with the full path output header file + get_filename_component(_source_dir ${_source_dir} ABSOLUTE) # cmake < 3.20 + # cmake >= 3.20: cmake_path(ABSOLUTE_PATH _source_path) + string(JOIN "/" _source_path ${_source_dir} ${_device_name}MsgDefs.cpp) + + # generate a pretty string containing a list of the relative paths of all csv files + foreach(_path ${${_input_csvs}}) + # cmake >= 3.20: cmake_path(ABSOLUTE_PATH _path) + get_filename_component(_path ${_path} ABSOLUTE) + list(APPEND _files ${_path}) + endforeach() + list(JOIN _files ", " _file_list) + + add_custom_command( + DEPENDS + ${MSGUTILS_SCRIPTS_DIR}/MsgData.py + ${MSGUTILS_SCRIPTS_DIR}/MsgCpp.py + ${MSGUTILS_SCRIPTS_DIR}/GenerateMsgDefsCpp.py + ${MSGUTILS_SCRIPTS_DIR}/templates/MsgDefs_h.jinja + ${MSGUTILS_SCRIPTS_DIR}/templates/MsgDefs_cpp.jinja + ${${_input_csvs}} + OUTPUT + ${_header_path} + ${_source_path} + COMMAND + ${Python3_EXECUTABLE} ${MSGUTILS_SCRIPTS_DIR}/GenerateMsgDefsCpp.py + --namespace ${_namespace} + --header_dir ${_header_dir} + --source_dir ${_source_dir} + ${${_input_csvs}} + ${_device_name} + COMMENT "Generating MsgDefs C++ header ${_header_path} and source ${_source_path} from input ${_file_list}" + VERBATIM + ) +endfunction() + +# \brief This function adds a custom command to generate a Protobuf file from inputted CSV files +# \param[in] _input_csvs list of CSV files to use to generate the header +# \param[in] _protobuf_filename name of the generated protobuf file +# \param[in] _output_path directory where the header file will be outputted +# \param[in] _namespace C++ namespace for the outputted message definitions +function(generate_protobuf _input_csvs _protobuf_filename _output_path _namespace) + find_package(Python3 COMPONENTS Interpreter Development REQUIRED) + + # create a variable with the full path output header file + string(JOIN "/" _protobuf_abs_filename ${_output_path} ${_protobuf_filename}) + # cmake >= 3.20: cmake_path(ABSOLUTE_PATH _protobuf_abs_filename) + get_filename_component(_protobuf_abs_filename ${_protobuf_abs_filename} ABSOLUTE) # cmake < 3.20 + + # generate a pretty string containing a list of the relative paths of all csv files + foreach(_path ${${_input_csvs}}) + # cmake >= 3.20: cmake_path(ABSOLUTE_PATH _path) + get_filename_component(_path ${_path} ABSOLUTE) # cmake < 3.20 + list(APPEND _files ${_path}) + endforeach() + list(JOIN _files ", " _file_list) + + add_custom_command( + DEPENDS + ${MSGUTILS_SCRIPTS_DIR}/MsgData.py + ${MSGUTILS_SCRIPTS_DIR}/MsgProtobuf.py + ${MSGUTILS_SCRIPTS_DIR}/GenerateProtobuf.py + ${MSGUTILS_SCRIPTS_DIR}/templates/MsgDefs_proto.jinja + ${${_input_csvs}} + OUTPUT + ${_protobuf_abs_filename} + COMMAND + ${Python3_EXECUTABLE} ${MSGUTILS_SCRIPTS_DIR}/GenerateProtobuf.py + --namespace ${_namespace} + --output_dir ${_output_path} + ${${_input_csvs}} + ${_protobuf_filename} + COMMENT "Generating Protobuf definitions ${_protobuf_abs_filename} from input ${_file_list}" + VERBATIM + ) +endfunction() + +# \brief This function adds a custom command to generate C++ heder and source files from an inputted Protobuf file +# \param[in] _protobuf_filename name of the input Protobuf file +# \param[in] _protobuf_path directory containing the input Protobuf file +# \param[in] _header_path directory where the generated C++ header file will be outputted +# \param[in] _source_path directory where the generated C++ source file will be outputted +function(generate_protobuf_cpp _protobuf_filename _protobuf_path _header_path _source_path) + find_package(Protobuf REQUIRED) + find_program(PROTOBUF_PROTOC protoc REQUIRED) + + # cmake >= 3.20: cmake_path(ABSOLUTE_PATH _protobuf_path) + get_filename_component(_protobuf_path ${_protobuf_path} ABSOLUTE) # cmake < 3.20 + + # cmake >= 3.20: cmake_path(GET _protobuf_filename STEM LAST_ONLY _protobuf_filename_laststem) + get_filename_component(_protobuf_filename_laststem ${_protobuf_filename} NAME) + string(REGEX REPLACE "\\.[^.]*$" "" _protobuf_filename_laststem ${_protobuf_filename_laststem}) + + string(JOIN "/" _protobuf_abs_filename ${_protobuf_path} ${_protobuf_filename}) + # cmake >= 3.20: cmake_path(ABSOLUTE_PATH _protobuf_abs_filename) + get_filename_component(_protobuf_abs_filename ${_protobuf_abs_filename} ABSOLUTE) # cmake < 3.20 + + string(JOIN "/" _header_abs_filename ${_header_path} ${_protobuf_filename_laststem}.pb.h) + # cmake >= 3.20: cmake_path(ABSOLUTE_PATH _header_abs_filename) + get_filename_component(_header_abs_filename ${_header_abs_filename} ABSOLUTE) # cmake < 3.20 + + string(JOIN "/" _source_abs_filename ${_source_path} ${_protobuf_filename_laststem}.pb.cc) + # cmake >= 3.20: cmake_path(ABSOLUTE_PATH _source_abs_filename) + get_filename_component(_source_abs_filename ${_source_abs_filename} ABSOLUTE) # cmake < 3.20 + + # uncomment this line and the _proto_srcs and _proto_hdrs lines in DEPENDS to use the + # cmake built-in function to generate the protobuf cpp files + # NOTE: the built-in function seems to not realize if the input proto file has changed + # and will always force the .h/.cpp files to regenerate and trigger a recompile, + # the add_custom_command below, does not trigger a recompile if the proto is unchanged. + # protobuf_generate_cpp(_proto_srcs _proto_hdrs ${_protobuf_abs_filename}) + add_custom_command( + DEPENDS + ${_protobuf_abs_filename} + # ${_proto_hdrs} + # ${_proto_srcs} + OUTPUT + ${_header_abs_filename} + ${_source_abs_filename} + COMMAND + ${PROTOBUF_PROTOC} + --proto_path ${_protobuf_path} + --cpp_out ${CMAKE_CURRENT_BINARY_DIR} + ${_protobuf_abs_filename} + COMMAND + ${CMAKE_COMMAND} -E rename ${CMAKE_CURRENT_BINARY_DIR}/${_protobuf_filename_laststem}.pb.h ${_header_abs_filename} + COMMAND + ${CMAKE_COMMAND} -E rename ${CMAKE_CURRENT_BINARY_DIR}/${_protobuf_filename_laststem}.pb.cc ${_source_abs_filename} + COMMENT "Generating C++ files ${_header_abs_filename} and ${_source_abs_filename} from Protobuf file ${_protobuf_abs_filename}" + VERBATIM + ) +endfunction() Index: lib/MsgUtils/include/CanMessage.h =================================================================== diff -u --- lib/MsgUtils/include/CanMessage.h (revision 0) +++ lib/MsgUtils/include/CanMessage.h (revision cfc0df719cb5033078d0cac45ce0f6243810f2e7) @@ -0,0 +1,198 @@ +/*! + * + * Copyright (c) 2020-2024 Diality Inc. - All Rights Reserved. + * \copyright + * THIS CODE MAY NOT BE COPIED OR REPRODUCED IN ANY FORM, IN PART OR IN + * WHOLE, WITHOUT THE EXPLICIT PERMISSION OF THE COPYRIGHT OWNER. + * + * \file MessageGlobals.h + * \author (last) Dara Navaei + * \date (last) 08-May-2024 + * \author (original) Behrouz NematiPour + * \date (original) 26-Aug-2020 + * + */ +#pragma once + +// Qt +#include +#include +#include + +// Project +#include "types.h" + +namespace Can { + +/*! + * \brief Sequence + * \details the messages sequence type + */ +using Sequence = qint16; +using Sequence_Bytes = Types::S16; +#define SEQUENCE_MAX INT16_MAX + +/*! + * \brief MsgId + * \details the messages message ID type + */ +using MsgId = quint16; +using MsgId_Bytes = Types::U16; +#define MSGID_MAX UINT16_MAX + +/*! + * \brief FrameCount + * \details The maximum unsigned integer value to be used as the frame count + */ +using FrameCount = quint64; + +/*! + * \brief The Payload_Data enum + * \details Global information for message packet. + */ +enum Payload_Data : quint8 { + ePayload_None = 0x00, + ePayload_Sync = 0xA5, +}; + +/*! + * \brief The Frame_Data enum + * \details Global information for each message frame. + */ +enum Frame_Data : quint8 { + eLenCanFrame = 8, ///< The length of each can frame. Should be padded by 0x00 if is less. + eLenHeaderInfo = 5, ///< The Header length witch is included in CRC after Sync byte (it's the sum of eLenSequence + eLenActionId + eLenLength) + eLenMaxHeaderData = 3, ///< Maximum data byte can be in one frame as header data portion + eLenMaxData = 255, ///< Maximum data length in a Can Message since data length value kept in one byte + + eLenSyncByte = 1, ///< The length of Sync byte at the beginning of each header frame + eLenSequence = 2, ///< The length of Sequence number bytes (2) at the beginning of each header frame after sync + eLenActionId = 2, ///< The length of MessageID bytes + eLenLength = 1, ///< The length of data length value byte at the beginning of each header frame after MessageID + + eLenCRCDigits = 2, ///< The length of CRC value byte in each Denali message + eLenChannelDigits = 3, ///< The length of channel value byte in each Denali message + eLenMessageIDDigits = 4, ///< The length of message id value byte in each Denali message +}; + +/*! + * \brief The Can_Id enum + * \details The Valid CANBus MessageID of each frame + */ +enum CanId : quint16 { + eChlid_LOWEST = 0x7FF, + eChlid_NONE = 0x7FF, + + // Broadcasts + //// Alarm + eChlid_TD_Alarm = 0x001, ///< TD alarm broadcast + eChlid_DD_Alarm = 0x002, ///< DD alarm broadcast + eChlid_FP_Alarm = 0x003, ///< FP alarm broadcast + eChlid_UI_Alarm = 0x004, ///< UI alarm broadcast [Out] + //// Sync + eChlid_TD_Sync = 0x100, ///< HD sync broadcast + eChlid_DD_Sync = 0x101, ///< DD sync broadcast + eChlid_FP_Sync = 0x102, ///< DD sync broadcast + eChlid_UI_Sync = 0x103, ///< UI sync broadcast [Out] + + // UI not listening + eChlid_TD_DD = 0x010, ///< TD => DD + eChlid_DD_TD = 0x011, ///< DD => TD + eChlid_DD_FP = 0x021, ///< DD => FP + eChlid_FP_DD = 0x020, ///< FP => DD + + // UI is listening + eChlid_TD_UI = 0x040, ///< TD => UI + eChlid_UI_TD = 0x041, ///< UI => TD [Out] + + // UI listens occasionally + eChlid_DD_UI = eChlid_DD_Sync , ///< DD => UI + eChlid_FP_UI = eChlid_FP_Sync , ///< FP => UI + eChlid_UI_DD = eChlid_UI_Sync , ///< UI => DD [Out] + + // Dialing channel has been requested by V&V team for CANBus testing + // and clarify the source of the unexpected channel message + // UI still does not do anything with the messages on these channels. + eDialin_TD = 0x400, ///< dialin => TD + eTD_Dialin = 0x401, ///< TD => dialin + eDialin_DD = 0x402, ///< dialin => DD + eDD_Dialin = 0x403, ///< DD => dialin + eDialin_FP = 0x404, ///< dialin => FP + eFP_Dialin = 0x405, ///< FP => dialin + eDialin_UI = 0x406, ///< dialin => UI + eUI_Dialin = 0x407, ///< UI => dialin +}; + +/*! + * \brief The Can_Source enum + * \details The allowable sources of the CANBus messages. + */ +enum Can_Source { + eCan_Unknown = -1, + eCan_TD = 0, + eCan_DD = 1, + eCan_FP = 2, + eCan_DI = 3, +}; + +/*! + * \brief The Message struct + * \details The message structure after it's been converted form hex bytes to meaningful struct for UI. + */ +struct Message { // TODO : Should be converted to MessageModel class // no time left for now !!! + CanId canId = eChlid_NONE; + Sequence sequence = 0; // seq 0 is invalid + MsgId msgId = 0; + quint8 length = 0; + QByteArray head; + QByteArray data; + bool initialized = false; + + void clear() { + canId = eChlid_NONE; + sequence = 0; + msgId = 0; + length = 0; + head = QByteArray(); + data = QByteArray(); + initialized = false; + } + + void dump() const { + qDebug().noquote() << "Message {"; + qDebug().noquote() << QString(" canId = 0x%1").arg(QString("%1").arg(canId, 3, 16, QChar('0')).toUpper()); + qDebug().noquote() << QString(" sequence = %1").arg(sequence); + qDebug().noquote() << QString(" msgId = 0x%1").arg(QString("%1").arg(msgId, 4, 16, QChar('0')).toUpper()); + qDebug().noquote() << QString(" length = %1").arg(length); + QString header; + for (int i = 0; i < head.size(); i++) { + header.append(QString(" 0x%1").arg(QString("%1").arg(quint8(head[i]), 2, 16, QChar('0')).toUpper())); + } + qDebug().noquote() << QString(" header(%1) =%2").arg(head.length(), 2, 10, QChar('0')).arg(header); + QString payload; + for (int i = 0; i < data.size(); i++) { + payload.append(QString(" 0x%1").arg(QString("%1").arg(quint8(data[i]), 2, 16, QChar('0')).toUpper())); + } + qDebug().noquote() << QString(" data(%1) =%2").arg(data.length(), 2, 10, QChar('0')).arg(payload); + qDebug().noquote() << "}"; + } + + bool isComplete() { + // Since the crc is part of the data and there is no message without crc + // then a message would never be empty. + // It is preferred to keep it as is so that the initialization is independent of data. + return !isEmpty() && data.length() == length; + } + + bool isEmpty () { + // Since the crc is part of the data and there is no message without crc + // initialized flag and data.length() == 0 became the same. + // It is preferred to keep it as is so that the initialization is independent of data. + return !initialized || !data.length(); + } +}; + +using MessageList = QList; +using FrameList = QList; + +} // namespace Can Index: lib/MsgUtils/include/FrameInterface.h =================================================================== diff -u --- lib/MsgUtils/include/FrameInterface.h (revision 0) +++ lib/MsgUtils/include/FrameInterface.h (revision cfc0df719cb5033078d0cac45ce0f6243810f2e7) @@ -0,0 +1,128 @@ +/*! + * + * Copyright (c) 2020-2024 Diality Inc. - All Rights Reserved. + * \copyright + * THIS CODE MAY NOT BE COPIED OR REPRODUCED IN ANY FORM, IN PART OR IN + * WHOLE, WITHOUT THE EXPLICIT PERMISSION OF THE COPYRIGHT OWNER. + * + * \file FrameInterface.h + * \author (last) Behrouz NematiPour + * \date (last) 21-Jan-2023 + * \author (original) Behrouz NematiPour + * \date (original) 26-Aug-2020 + * + */ +#pragma once + +// Qt +#include +#include + +// Project +#include "CanMessage.h" +#include "main.h" // Doxygen : do not remove +// #include "MessageGlobals.h" + +// Define +#define _FrameInterface Can::FrameInterface::I() + +// namespace +namespace Can { + +/*! + * \brief The FrameInterface class + * \details This class is an interface between QByteArray and QCanBusFrame + * and gets the data as QByteArray and creates a frame + * and sends it to the CanInterface to deal with the CANBus. + * And does it in reverse when receives a frame from CanInterface. + */ +class FrameInterface : public QObject +{ + Q_OBJECT + + // Singleton + SINGLETON(FrameInterface) + + /*! + * \brief The ChannelGroup enum + * \details The enum which represent the categories of the CANBus channel + */ + enum class ChannelGroup { + eChannel_Unknown, ///< An Unknown channels category + eChannel_Ignores, ///< The Channels which will be ignored by UI + eChannel_Listens, ///< The Channels that UI is listening to + eChannel_Outputs, ///< The Channels that are related to UI frames out. + }; + + QThread *_thread = nullptr; + bool _init = false; + + struct Frame { + CanId canId; + QByteArray data ; + + Frame(CanId vCanId, const QByteArray &vData): + canId (vCanId), + data (vData ) { } + }; + + QList _txFrameList; + const quint16 _txFrameList_Max = 4000; // maximum number of frames in the transmit buffer + bool _transmitted = false; + + const quint8 _interval = 7; // keep awake call of the UI board in ms + + QString _timestamp; + +protected: + void timerEvent(QTimerEvent *) override; + +public slots: + bool init(); + bool init(QThread &vThread); + void quit(); + +private: + void initConnections(); + + void initThread(QThread &vThread); + void quitThread(); + + ChannelGroup checkChannel(quint32 vFrameId, bool *vOK = nullptr, bool *vDebugChanngel = nullptr); + void transmitFrame (CanId vCanId, const QByteArray &vData = 0); + void appendHead (CanId vCanId, const QByteArray &vData ); + void trnsmtHead (); + void removeHead (); + +private slots: // Should be private for thread safety and is connected internally. + void onFrameTransmit(CanId vCanId, const QByteArray &vData ); // GUI => CAN + void onFrameReceive ( const QCanBusFrame &vFrame ); // GUI <= CAN + void onFrameWritten (qint64 vCount ); // GUI <= CAN + +signals: + /*! + * \brief didFrameReceive + * \details After CanInterface receives a frame notifies FrameInterface + * by emitting CanInterface::didFrameReceive signal, + * then Message Handler calls its slot FrameInterface::onFrameReceive to handle the message, + * And when the message has been processed vCanId of type CanId which is the Channel ID of the frame + * and vPayload of the frame of type QByteArray has been extracted, + * This signal will be emitted to notify MessageDispatcher to start collecting data + * for this message over this channel. + * \param vCanId - Channel Id of the frame. + * \param vPayload - Payload of the frame. + */ + void didFrameReceive (CanId vCanId, const QByteArray &vPayload); // GUI <= CAN + + /*! + * \brief didFrameTransmit + * \details After MessageDispatcher requests FrameInterface to transmit a message, + * And FrameInterface calls its slot FrameInterface::onFrameTransmit to handle the message, + * And when the message has been processed and a Frame has been created, + * this signal will be emitted to notify CanInterface to send the frame. + * \ref CanInterface::onFrameTransmit + * \param vFrame - The frame which has been created to be transmitted. + */ + void didFrameTransmit( const QCanBusFrame &vFrame ); // GUI => CAN +}; +} Index: lib/MsgUtils/include/MessageBuilder.h =================================================================== diff -u --- lib/MsgUtils/include/MessageBuilder.h (revision 0) +++ lib/MsgUtils/include/MessageBuilder.h (revision cfc0df719cb5033078d0cac45ce0f6243810f2e7) @@ -0,0 +1,95 @@ +/*! + * + * Copyright (c) 2020-2024 Diality Inc. - All Rights Reserved. + * \copyright + * THIS CODE MAY NOT BE COPIED OR REPRODUCED IN ANY FORM, IN PART OR IN + * WHOLE, WITHOUT THE EXPLICIT PERMISSION OF THE COPYRIGHT OWNER. + * + * \file MessageBuilder.h + * \author (last) Behrouz NematiPour + * \date (last) 06-Jun-2021 + * \author (original) Behrouz NematiPour + * \date (original) 26-Aug-2020 + * + */ +#pragma once + +// Qt +#include + +// Project +// #include "GuiGlobals.h" +#include "CanMessage.h" + +namespace Can +{ + +/*! + * + * \page DenaliMessageStructure Denali Message Structure + * + * CAN PAYLOAD STRUCTURE + * + * | #0 | #1 , #2 | #3 , #4 | #5 | #6 , #7 | #8 | .......... | + * |:---:|:-------:|:-------:|:---:|:-------:|:---:|:----------:| + * | A5 | #seq | MSG ID | Len | Data | CRC | ..padding..| + * + \verbatim + Header Frame: + 1 - CRC is last after payload + Ex1 - If Len=0 then CRC is #4 + Ex2 - If Len=1 then CRC is #5 and payload is #4 + 2 - If CRC is not the last by in the frame + then there is padding 0x00 to make the frame 8 byte fix + + Tail Frame: + 3 - Partial frames only have Payload and CRC ( and padded ) + \endverbatim + */ + +/*! + * \brief The MessageBuilder class + * \details This class is handling the can message by building and striping it. + * This class constructs a message by reading array of bytes (QByteArray) in CANBus frame(s) + * and converting them to messages by reading the Message ID, Data Length and data. + * On the other hand, this class converts bytes of data requires to be transmitted to frame(s) and vice versa. + */ +class MessageBuilder : public QObject +{ + Q_OBJECT + + bool _enableConsoleOut = false; + + void addSyncByte ( QByteArray &vPayload); + void addSequence (QByteArray &vPayload, const Sequence vSequence); + bool addMsgId ( QByteArray &vPayload, const MsgId vMsgId) ; + bool addData ( QByteArray &vPayload, const MsgId vMsgId, const QByteArray &vData) ; + void addCRC ( QByteArray &vPayload); + void addPadding ( QByteArray &vPayload); + + quint8 calcCRC (const QByteArray &vData ); + bool checkCRC (const QByteArray &vData , quint8 &vExpected, quint8 &vActual); + bool checkCRC (const Message &vMessage); + + bool hasSyncByte ( QByteArray &vPayload); + Sequence getSequence ( QByteArray &vPayload); + QByteArray getHeader (const QByteArray &vPayload); + quint16 getActionId ( QByteArray &vPayload); + int getLength ( QByteArray &vPayload); + QByteArray getData (const QByteArray &vPayload, const int vLen); + + void printPayload(const QByteArray &vPayload, const bool vIsHeader, CanId vCanId, const bool vUseColor = true); + void consoleOut (const QByteArray &vPayload, const bool vIsHeader, CanId vCanId, const bool vUseColor = true); + +public: + explicit MessageBuilder(QObject *parent = nullptr); + + // build message to be sent frame by frame + bool buildFrames (const MsgId vMsgId, const QByteArray &vData, FrameList &vFrameList, const Sequence vSequence); + // build message from received frames + bool buildMessage(const QByteArray &vPayload, Message &vMessage, const CanId vCanId); + + void enableConsoleOut(const bool vEnabled); +}; + +} // namespace Can Index: lib/MsgUtils/include/crc.h =================================================================== diff -u --- lib/MsgUtils/include/crc.h (revision 0) +++ lib/MsgUtils/include/crc.h (revision cfc0df719cb5033078d0cac45ce0f6243810f2e7) @@ -0,0 +1,19 @@ +/*! + * + * Copyright (c) 2019-2024 Diality Inc. - All Rights Reserved. + * \copyright + * THIS CODE MAY NOT BE COPIED OR REPRODUCED IN ANY FORM, IN PART OR IN + * WHOLE, WITHOUT THE EXPLICIT PERMISSION OF THE COPYRIGHT OWNER. + * + * \file crc.h + * \author (last) Behrouz NematiPour + * \date (last) 09-Jan-2020 + * \author (original) Behrouz NematiPour + * \date (original) 16-Dec-2019 + * + */ +#pragma once + +#include + +quint8 crc8(const QByteArray &vData); Index: lib/MsgUtils/include/format.h =================================================================== diff -u --- lib/MsgUtils/include/format.h (revision 0) +++ lib/MsgUtils/include/format.h (revision cfc0df719cb5033078d0cac45ce0f6243810f2e7) @@ -0,0 +1,37 @@ +/*! + * + * Copyright (c) 2019-2024 Diality Inc. - All Rights Reserved. + * \copyright + * THIS CODE MAY NOT BE COPIED OR REPRODUCED IN ANY FORM, IN PART OR IN + * WHOLE, WITHOUT THE EXPLICIT PERMISSION OF THE COPYRIGHT OWNER. + * + * \file format.h + * \author (last) Behrouz NematiPour + * \date (last) 23-Mar-2022 + * \author (original) Behrouz NematiPour + * \date (original) 16-Dec-2019 + * + */ +#pragma once + +// Qt +#include +#include + +// Project + +#define FSN(vNumber) QString::number(vNumber) + +class Format +{ + Format(); + +public: + static QString toHexString ( quint16 vValue, bool vWith0x = true, quint8 vLen = 4); + static QByteArray toHexByteArray (const QByteArray &vData , char separator = '.'); + static QString toHexString (const QByteArray &vData , char separator = '.'); + static QByteArray fromVariant (const QVariant &vData ); + static QStringList toStringList (const QList vList, bool vRemoveDuplicate = false, QString vPrefix = ""); + static QString fromEpoch ( qint64 vEpoch, QString vFormat = "yyyy/MM/dd HH:mm"); + static QStringList fromVariantList (const QVariantList &vData ); +}; Index: lib/MsgUtils/include/main.h =================================================================== diff -u --- lib/MsgUtils/include/main.h (revision 0) +++ lib/MsgUtils/include/main.h (revision cfc0df719cb5033078d0cac45ce0f6243810f2e7) @@ -0,0 +1,609 @@ +/*! + * + * Copyright (c) 2019-2024 Diality Inc. - All Rights Reserved. + * \copyright + * THIS CODE MAY NOT BE COPIED OR REPRODUCED IN ANY FORM, IN PART OR IN + * WHOLE, WITHOUT THE EXPLICIT PERMISSION OF THE COPYRIGHT OWNER. + * + * \file main.h + * \author (last) Behrouz NematiPour + * \date (last) 04-Apr-2024 + * \author (original) Behrouz NematiPour + * \date (original) 28-Oct-2019 + * + */ +#pragma once + +// Qt +#include +#include +// Project + +// Application +#define PRINT_THREAD_NAME_ENABLE 0 +#if (PRINT_THREAD_NAME_ENABLE) + #include + #define PRINT_THREAD_NAME qDebug() << " ----- " << QThread::currentThread()->objectName() << metaObject()->className() << __func__ ; +#else + #define PRINT_THREAD_NAME +#endif + +// TODO : A singleton parent class needs to be created +// to taking care of the Threading, init, quit, and so + +// TODO : Threading +// - We still need to work on threading on other classes +// - We need to have a singleton parent class +// - Some code has been added to debug can interface (We still have swap frames) +#define SINGLETON(vCLASS) \ +private: \ + explicit vCLASS(QObject *parent = nullptr); \ + virtual ~vCLASS() { } \ + vCLASS(vCLASS const &) = delete; \ + vCLASS & operator = (vCLASS const &) = delete; \ +public: \ + /*! \brief instance accessor + \details A singleton class single instance creator/accessor + \return reference to the class static instance + */\ + static vCLASS &I() { \ + static vCLASS _instance; \ + return _instance; \ + } \ + static bool _disable; \ + /* Intentionally disable made private */ \ + static void disable(); \ +public: \ + static bool isDisable() { \ + return _disable; \ + } \ +private: +//--------------------------------------------------------------------------------// +#include +#define SINGLETON_DISABLE(vCLASS) \ + bool vCLASS::_disable = false; \ + void vCLASS:: disable() { \ + if ( QThread::currentThread() == qApp->thread() ) { \ + LOG_DEBUG(QString(" !!! Failed Disable "#vCLASS" [%1] !!!").arg(QThread::currentThread()->objectName())); \ + return; \ + } \ + LOG_DEBUG(QString(" !!! Disabled "#vCLASS" [%1] !!!").arg(QThread::currentThread()->objectName())); \ + QEventLoopLocker _eventLoopLocker(QThread::currentThread()); \ + _disable = true; \ + } +//--------------------------------------------------------------------------------// +#define SINGLETON_DISABLE_CONNECT(vSIGNAL) \ + connect(&_ApplicationController , \ + &ApplicationController::vSIGNAL , \ + this , \ + [=](bool vPass) { if ( ! vPass ) disable(); }); +//--------------------------------------------------------------------------------// +//--------------------------------------------------------------------------------// +extern int gFakeInterval ; +extern QByteArray gFakeData ; +extern const char*gFakeData_default ; +extern bool gSendEmptyKeepAwake ; +extern bool gFakeSeqAtBegin ; +extern bool gDisableUnhandledReport ; +extern bool gDisableDialinUnhandled ; +extern bool gDisableTimeout ; +extern bool gDisableAlarmNoMinimize ; +extern bool gDisableSDCFailLogStop ; +extern bool gDisableCloudSyncFailStop ; + +extern bool gDisableCheckInLog ; +extern bool gDisableAcknowLog ; + +extern bool gConsoleoutLogs ; +extern bool gConsoleoutFrameInterface ; +extern bool gConsoleoutCanInterface ; + +extern bool gEnableDryDemo ; +extern QString gActiveCANBus ; +extern bool gEnableManufacturing ; +extern bool gEnableUpdating ; +extern bool gUseRootHome ; +extern QString gStandard_tmp ; + +extern bool gLogLongName ; +extern bool gLogUpload ; +extern bool gLogCompress ; +extern bool gLogUnhandledOnly ; + + +//--------------------------------------------------------------------------------// +//--------------------------------------------------------------------------------// +#define SKIPPER_DEF(X) \ + const quint8 skipperMaxTry = X; \ + static quint8 skipperCounter = 0; +#define SKIPPER_TST(WHAT) \ + if ( skipperCounter < skipperMaxTry ) WHAT +#define SKIPPER_TRY \ + skipperCounter++ +#define SKIPPER_RST \ + skipperCounter = 0 +//--------------------------------------------------------------------------------// +#define DEBUG_PROPERTY_CHANGED(vVARIABLE, PREFIX) // qDebug() << "#" << #vVARIABLE << PREFIX##vVARIABLE; +//--------------------------------------------------------------------------------// +#define FROMVARIANT(vVARIABLE, vGROUP, vKEY, vCONVERSION) \ +{ \ + bool ok = false; \ + vVARIABLE( _Settings.value(mCategory, vGROUP, vKEY).to##vCONVERSION(&ok) ); \ + if ( !ok ) LOG_DEBUG("incorrect configuration value for " #vVARIABLE); \ +} +//--------------------------------------------------------------------------------// +#define FROMVARIANT_WITHRETURN(vVARIABLE, vGROUP, vKEY, vCONVERSION, vOVERALL_OK) \ +{ \ + bool ok = false; \ + vVARIABLE( _Settings.value(mCategory, vGROUP, vKEY).to##vCONVERSION(&ok) ); \ + if ( !ok ) LOG_DEBUG("incorrect configuration value for " #vVARIABLE); \ + vOVERALL_OK = vOVERALL_OK && ok; \ +} +//--------------------------------------------------------------------------------// +#define PROPERTY_SLOT( vTYPE , vVARIABLE ) \ + protected : \ + /*! \brief Property setter + \details The property setter which update the private variable \n + - if only the value has been changed \n + - or it's the first time property is set. \n + emits the Property notify (...Changed) signal on update. \n + the notify signal (...Changed) passes the new value as its parameter. \n + \param new value + */\ + void vVARIABLE ( const vTYPE & v##vVARIABLE ) { \ + static bool init = false; \ + _##vVARIABLE##Changed = _##vVARIABLE != v##vVARIABLE; \ + if ( !init || _##vVARIABLE##Changed ) { \ + DEBUG_PROPERTY_CHANGED(vVARIABLE, v) \ + init = true; \ + _##vVARIABLE = v##vVARIABLE; \ + emit vVARIABLE##Changed( _##vVARIABLE ); \ + } \ + } +//--------------------------------------------------------------------------------// +#define TRIGGER_SLOT( vTYPE , vVARIABLE ) \ + protected: \ + /*! \brief Trigger setter + \details The Trigger setter which update the private variable \n + with no condition each time the value the passed. \n + emits the Trigger notify (...Triggered) signal after update. \n + the notify signal (...Triggered) passes the new value as its parameter.\n + \param new value + */\ + void vVARIABLE ( const vTYPE & v##vVARIABLE ) { \ + DEBUG_PROPERTY_CHANGED(vVARIABLE, v) \ + _##vVARIABLE##Changed = true; \ + _##vVARIABLE = v##vVARIABLE; \ + emit vVARIABLE##Triggered( _##vVARIABLE ); \ + } +//--------------------------------------------------------------------------------// +#define STATE_SLOT( vTYPE , vVARIABLE ) \ + protected : \ + /*! \brief Property setter + \details The property setter which update the private variable \n + - if only the value has been changed \n + emits the Property notify (...Entered) signal on update. \n + the notify signal (...Entered) passes the new value as its parameter. \n + \param new value + */\ + void vVARIABLE ( const vTYPE & v##vVARIABLE ) { \ + _##vVARIABLE##Changed = _##vVARIABLE != v##vVARIABLE; \ + if ( _##vVARIABLE##Changed ) { \ + DEBUG_PROPERTY_CHANGED(vVARIABLE, v) \ + _##vVARIABLE = v##vVARIABLE; \ + emit vVARIABLE##Entered( _##vVARIABLE ); \ + } \ + } +//--------------------------------------------------------------------------------// +#define PROPERTY_POST_CONNECTION( vCLASS, vVARIABLE ) \ + connect(this, &vCLASS::vVARIABLE##Changed \ + , &vCLASS::vVARIABLE##_post ); +//--------------------------------------------------------------------------------// +#define PROPERTY_BASE(vTYPE , vVARIABLE , vDEFVALUE, vSIGNAL) \ + /*! \brief Qt Property declaration + \details The Qt Property definition by Q_PROPERTY documentation. + */\ + Q_PROPERTY( vTYPE vVARIABLE \ + READ vVARIABLE \ + WRITE vVARIABLE \ + NOTIFY vVARIABLE##vSIGNAL) \ + Q_SIGNALS: \ + /*! \brief Property notify signal + \details The property notify signal (...Changed) + which will be emitted by property setter + - if only the value has been changed \n + - or it's the first time property is set. \n + \return current value + */\ + void vVARIABLE##vSIGNAL( const vTYPE & v##vVARIABLE ); \ + private: \ + void vVARIABLE##_post ( const vTYPE & v##vVARIABLE ); \ + private: \ + vTYPE _##vVARIABLE = vDEFVALUE; \ + bool _##vVARIABLE##Changed = false; \ + protected: \ + /*! \brief Property getter + \details The property getter which reads the private variable + \return current value + */\ + vTYPE vVARIABLE () const { \ + return _##vVARIABLE ; \ + } +//--------------------------------------------------------------------------------// +#define READONLY_BASE(vTYPE , vVARIABLE , vDEFVALUE, vSIGNAL) \ + /*! \brief Qt Read-Only Property declaration + \details The Qt Property definition by Q_PROPERTY documentation. + */\ + Q_PROPERTY( vTYPE vVARIABLE \ + READ vVARIABLE \ + NOTIFY vVARIABLE##vSIGNAL) \ + Q_SIGNALS: \ + /*! \brief Property notify signal + \details The property notify signal (...Changed) + which will be emitted by property setter + - if only the value has been changed \n + - or it's the first time property is set. \n + \return current value + */\ + void vVARIABLE##vSIGNAL( const vTYPE & v##vVARIABLE ); \ + private: \ + vTYPE _##vVARIABLE = vDEFVALUE; \ + bool _##vVARIABLE##Changed = false; \ + protected: \ + /*! \brief Property getter + \details The property getter which reads the private variable + \return current value + */\ + vTYPE vVARIABLE () const { \ + return _##vVARIABLE ; \ + } +//--------------------------------------------------------------------------------// +#define IDBASED_BASE(vTYPE , vVARIABLE , vDEFVALUE , vLIST , vID) \ + /*! \brief Qt Property declaration + \details The Qt Property definition by Q_PROPERTY documentation. + */\ + Q_PROPERTY( vTYPE vVARIABLE \ + READ vVARIABLE \ + WRITE vVARIABLE \ + NOTIFY vID##Changed) \ + Q_SIGNALS: \ + /*! \brief Property notify signal + \details The property notify signal (...Changed) + which will be emitted by property setter + - if only the value has been changed \n + - or it's the first time property is set. \n + \return current value + */\ + void vVARIABLE##Changed( const vTYPE & v##vVARIABLE ); \ + private: \ + vTYPE _##vVARIABLE = vDEFVALUE; \ + bool _##vVARIABLE##Changed = false; \ + bool _##vVARIABLE##ByID = true ; \ + protected: \ + /*! \brief Property byId setter + \details The property sets the ByID value to be used in the getter + */\ + void vVARIABLE##ByID(bool vByID = true) { \ + _##vVARIABLE##ByID = vByID ; \ + } \ + /*! \brief Property getter + \details The property getter which reads the private variable + \return current value + */\ + vTYPE vVARIABLE () const { \ + if ( ! _##vVARIABLE##ByID ) return _##vVARIABLE; \ + QString value = _##vLIST [ _##vID ].vVARIABLE; \ + if ( ! value.isEmpty() ) return value; \ + return vDEFVALUE; \ + } +//--------------------------------------------------------------------------------// +#define READONLY( vTYPE , vVARIABLE , vDEFVALUE ) \ + READONLY_BASE( vTYPE , vVARIABLE , vDEFVALUE , Changed ) \ + PROPERTY_SLOT( vTYPE , vVARIABLE ) +//--------------------------------------------------------------------------------// +#define PROPERTY( vTYPE , vVARIABLE , vDEFVALUE ) \ + PROPERTY_BASE( vTYPE , vVARIABLE , vDEFVALUE , Changed ) \ + PROPERTY_SLOT( vTYPE , vVARIABLE ) +//--------------------------------------------------------------------------------// +#define TRIGGER( vTYPE , vVARIABLE , vDEFVALUE ) \ + PROPERTY_BASE( vTYPE , vVARIABLE , vDEFVALUE , Triggered) \ + TRIGGER_SLOT ( vTYPE , vVARIABLE ) +//--------------------------------------------------------------------------------// +#define STATE( vTYPE , vVARIABLE , vDEFVALUE ) \ + PROPERTY_BASE( vTYPE , vVARIABLE , vDEFVALUE , Entered ) \ + STATE_SLOT ( vTYPE , vVARIABLE ) +//--------------------------------------------------------------------------------// +#define IDBASED( vTYPE , vVARIABLE , vDEFVALUE , vLIST , vID ) \ + IDBASED_BASE ( vTYPE , vVARIABLE , vDEFVALUE , vLIST , vID ) \ + PROPERTY_SLOT( vTYPE , vVARIABLE ) +//--------------------------------------------------------------------------------// +#define VALUESET( vTYPE , vVARIABLE , vDEFVALUE ) \ + PROPERTY( vTYPE , vVARIABLE , vDEFVALUE ) \ + PROPERTY( bool , vVARIABLE##Set , false ) +//--------------------------------------------------------------------------------// +#define RANGESET( vTYPE , vVARIABLE , vDEFVALUE ) \ + READONLY( vTYPE , vVARIABLE##Min , vDEFVALUE ) \ + READONLY( vTYPE , vVARIABLE##Max , vDEFVALUE ) \ + READONLY( vTYPE , vVARIABLE##Res , vDEFVALUE ) \ + READONLY( vTYPE , vVARIABLE##Def , vDEFVALUE ) +//--------------------------------------------------------------------------------// +#define RANGEVALUESET( vTYPE , vVARIABLE , vDEFVALUE ) \ + PROPERTY( vTYPE , vVARIABLE##Min , vDEFVALUE ) \ + PROPERTY( vTYPE , vVARIABLE##Max , vDEFVALUE ) \ + PROPERTY( vTYPE , vVARIABLE##Def , vDEFVALUE ) +//--------------------------------------------------------------------------------// +#define MEMBER( vTYPE , vVARIABLE , vDEFVALUE ) \ + private: \ + vTYPE _##vVARIABLE = vDEFVALUE; \ + public: \ + /*! \brief Property setter + \details The property setter which update the private variable \n + \param new value + */\ + void vVARIABLE ( const vTYPE & v##vVARIABLE ) { \ + _##vVARIABLE = v##vVARIABLE; \ + } \ + public: \ + /*! \brief Property getter + \details The property getter which reads the private variable + \return current value + */\ + vTYPE vVARIABLE () const { \ + return _##vVARIABLE ; \ + } + +//--------------------------------------------------------------------------------// +#define CONSTANT( vTYPE , vVARIABLE , vDEFVALUE ) \ + /*! \brief Qt Constant Property declaration + \details The Qt Property definition by Q_PROPERTY documentation. + */\ + Q_PROPERTY( vTYPE vVARIABLE \ + READ vVARIABLE \ + CONSTANT) \ + protected: \ + /*! \brief Property getter + \details The property getter which reads the private variable + \return current value + */\ + vTYPE vVARIABLE () const { \ + return vDEFVALUE ; \ + } +//--------------------------------------------------------------------------------// +#define NOTIFIER( vVARIABLE ) \ + Q_SIGNALS: \ + /*! \brief Property notify signal + \details The property notify signal (...Changed) + which will be emitted by property setter + - if only the value has been changed \n + - or it's the first time property is set. \n + \return current value + */\ + void vVARIABLE##Notified( const bool & v##vVARIABLE ); \ + private: \ + bool _##vVARIABLE = false; \ + protected: \ + /*! \brief Property getter + \details The property getter which reads the private variable + \return current value + */\ + bool vVARIABLE () const { \ + return _##vVARIABLE ; \ + } \ + /*! \brief Notifier setter + \details The notifier setter which update the private variable \n + emits the notifier (is...) signal on update. \n + the notify signal (is...) passes the new value as its parameter. \n + \param new value + */\ + void vVARIABLE ( const bool & v##vVARIABLE ) { \ + DEBUG_PROPERTY_CHANGED(vVARIABLE, v) \ + _##vVARIABLE = v##vVARIABLE; \ + emit vVARIABLE##Notified( _##vVARIABLE ); \ + } +//--------------------------------------------------------------------------------// +#define SETTINGS_BASE( vVARIABLE , vCATEGORY , vGROUP , vKEY) \ + private: \ + /*! \brief Settings identifier + \details identifies the settings by the given information + \param vCategory - The category of the setting which includes on-level folder and the file name (without .conf extension) + \param vGroup - The group setting key/value belongs to + \param vKey - The key of the setting + */\ + bool is##vVARIABLE(const QString &vCategory , \ + const QString &vGroup , \ + const QString &vKey ) { \ + return vCategory == vVARIABLE##Category() && \ + vGroup == vVARIABLE##Group () && \ + vKey == vVARIABLE##Key (); \ + } \ + /*! \brief Settings property category getter + \details returns the category of the setting + */\ + QString vVARIABLE##Category() { \ + return vCATEGORY; \ + } \ + /*! \brief Settings property group getter + \details returns the group of the setting + */\ + QString vVARIABLE##Group() { \ + return vGROUP; \ + } \ + /*! \brief Settings property key getter + \details returns the key of the setting + */\ + QString vVARIABLE##Key() { \ + return vKEY; \ + } +//--------------------------------------------------------------------------------// +#define SETTINGS( vTYPE , vVARIABLE , vDEFVALUE , vCATEGORY , vGROUP , vKEY) \ + PROPERTY_BASE( vTYPE , vVARIABLE , vDEFVALUE , Changed ) \ + PROPERTY_SLOT( vTYPE , vVARIABLE ) \ + SETTINGS_BASE( vVARIABLE , vCATEGORY , vGROUP , vKEY) +//--------------------------------------------------------------------------------// +//--------------------------------------------------------------------------------// +#define ACTION_VIEW_CONNECTION(vTYPE) \ + connect(&_GuiController, SIGNAL(didActionReceive(const vTYPE &)), \ + this, SLOT( onActionReceive(const vTYPE &))); +//--------------------------------------------------------------------------------// +#define ADJUST_VIEW_CONNECTION(vTYPE) \ + connect( this, SIGNAL(didAdjustment (const vTYPE &)), \ + &_GuiController, SLOT( doAdjustment (const vTYPE &))); +//--------------------------------------------------------------------------------// +//--------------------------------------------------------------------------------// +#define ACTION_METHOD_BRIDGE_CONNECTION(vMETHOD, vSOURCE, vTYPE) \ + connect(&vSOURCE, SIGNAL(did##vMETHOD(const vTYPE &)), \ + this , SLOT( on##vMETHOD(const vTYPE &))); +//--------------------------------------------------------------------------------// +#define ACTION_RECEIVE_BRIDGE_CONNECTION(vSOURCE, vTYPE) \ + ACTION_METHOD_BRIDGE_CONNECTION(ActionReceive, vSOURCE, vTYPE) +//--------------------------------------------------------------------------------// +#define ADJUST_TRANSMT_BRIDGE_CONNECTION(vSOURCE, vTYPE) \ + ACTION_METHOD_BRIDGE_CONNECTION(Adjustment , vSOURCE, vTYPE) +//--------------------------------------------------------------------------------// +#define ACTION_RECEIVE_PRIVATE_SLOT(vTYPE) \ +private Q_SLOTS: \ + /*! \brief Bridge slot + \details The bridge slot is for thread safety between classes for received message + and is used to emit its signal to pass the model data to next observer. + \param vData - The model data which has been received. + \note This method is private and the interface is signals only. (starts with 'on') + */\ + void onActionReceive (const vTYPE &vData) { \ + emit didActionReceive(vData); \ + } +#define ACTION_RECEIVE_PRIVATE_SLOT_NOEMIT(vTYPE) \ +private Q_SLOTS: \ + /*! \brief The Received message slot that needs implementation + \details The bridge slot is for thread safety between classes for received message + and is used to emit its signal to pass the model data to next observer. + \param vData - The model data which has been received. + \note This method is private and the interface is signals only. (starts with 'on') + */\ + void onActionReceive (const vTYPE &vData) +//--------------------------------------------------------------------------------// +#define ADJUST_TRANSMT_PRIVATE_SLOT_DEFINITION(vTYPE) \ +private Q_SLOTS: \ + /*! \brief Adjustment slot + \details The bridge slot is for thread safety between classes for adjustment messages + and is used to emit its signal to pass the model data to next observer. + \param vData - The model data to be used for adjustment + \note This method is private and the interface is signals only. (starts with 'on') + This slot has to have its specific implementation and is not a bridge (not a signal emitter). + */\ + void onAdjustment (const vTYPE &vData); +//--------------------------------------------------------------------------------// +#define ADJUST_TRANSMT_PRIVATE_SLOT(vTYPE) \ +private Q_SLOTS: \ + /*! \brief Adjustment bridge slot + \details The bridge slot is for thread safety between classes for adjustment messages + and is used to emit its signal to pass the model data to next observer. + \param vData - The model data to be used for adjustment + \note This method is private and the interface is signals only. (starts with 'on') + */\ + void onAdjustment (const vTYPE &vData) { \ + emit didAdjustment( vData); \ + } +//--------------------------------------------------------------------------------// +#define ADJUST_TRANSMT_PUBLIC_SLOT(vTYPE) \ +public Q_SLOTS: \ + /*! \brief Adjustment invocable/exposed slot + \details This slot is able to be called within QML context + when an object of the class is instantiated in QML. + For thread safety it's calling its designated signal to notify observers. + \note This method is public and is the interface. (starts with 'do') + */\ + void doAdjustment (const vTYPE &vData) { \ + emit didAdjustment( vData); \ + } +//--------------------------------------------------------------------------------// +#define ACTION_RECEIVE_SIGNAL(vTYPE) \ +Q_SIGNALS: \ + /*! \brief Receive bridge signal + \details The bridge signal is for thread safety between classes for received message + and is used to be emitted in its slot to pass the model data to next observer. + \param vData - The model data which has been received. + \note This method is public to be exposed to the observers to be able to connect to it as the interface. (starts with 'did') + */\ + void didActionReceive (const vTYPE &vData); +//--------------------------------------------------------------------------------// +#define ADJUST_TRANSMT_SIGNAL(vTYPE) \ +Q_SIGNALS: \ + /*! \brief Adjustment bridge signal + \details The bridge signal is for thread safety between classes for received message + and is used to be emitted in its slot to pass the model data to next observer. + \param vData - The model data which has been received. + \note This method is public to be exposed to the observers to be able to connect to it as the interface. (starts with 'did') + */\ + void didAdjustment (const vTYPE &vData); +//--------------------------------------------------------------------------------// +#define ACTION_RECEIVE_BRIDGE_DEFINITION(vTYPE) \ + \ + ACTION_RECEIVE_PRIVATE_SLOT (vTYPE) \ + ACTION_RECEIVE_SIGNAL (vTYPE) \ +//--------------------------------------------------------------------------------// +#define ADJUST_TRANSMT_BRIDGE_DEFINITION_NOEMIT(vTYPE) \ + \ + ADJUST_TRANSMT_PRIVATE_SLOT_DEFINITION (vTYPE) \ + ADJUST_TRANSMT_SIGNAL (vTYPE) \ +//--------------------------------------------------------------------------------// +#define ADJUST_TRANSMT_BRIDGE_DEFINITION(vTYPE) \ + \ + ADJUST_TRANSMT_PRIVATE_SLOT (vTYPE) \ + ADJUST_TRANSMT_SIGNAL (vTYPE) \ +//--------------------------------------------------------------------------------// +#define ADJUST_TRANSMT_BRIDGE_DEFINITION_PUBLIC(vTYPE) \ + \ + ADJUST_TRANSMT_PUBLIC_SLOT (vTYPE) \ + ADJUST_TRANSMT_SIGNAL (vTYPE) \ +//--------------------------------------------------------------------------------// +#define SAFE_CALL( vMETHOD) \ +public Q_SLOTS : void vMETHOD() { \ + static bool init = false; \ + if ( ! init ) { \ + connect(this, SIGNAL( did##vMETHOD()), \ + this, SLOT( on##vMETHOD())); \ + init = true; \ + } \ + emit did##vMETHOD(); \ + } \ +Q_SIGNALS : void did##vMETHOD(); \ +private Q_SLOTS : void on##vMETHOD(); +//--------------------------------------------------------------------------------// +#define SAFE_CALL_EX( vMETHOD,vTYPE) \ +public Q_SLOTS : void vMETHOD(vTYPE vData) { \ + static bool init = false; \ + if ( ! init ) { \ + connect(this, SIGNAL( did##vMETHOD(vTYPE)), \ + this, SLOT( on##vMETHOD(vTYPE))); \ + init = true; \ + } \ + emit did##vMETHOD( vData); \ + } \ + Q_SIGNALS : void did##vMETHOD(vTYPE); \ + private Q_SLOTS : void on##vMETHOD(vTYPE); +//--------------------------------------------------------------------------------// +#define SAFE_CALL_EX2( vMETHOD,vTYPE1 ,vTYPE2 ) \ +public Q_SLOTS : void vMETHOD(vTYPE1 vDATA1 ,vTYPE2 vDATA2 ) { \ + static bool init = false; \ + if ( ! init ) { \ + connect(this, SIGNAL( did##vMETHOD(vTYPE1 ,vTYPE2 )), \ + this, SLOT( on##vMETHOD(vTYPE1 ,vTYPE2 )));\ + init = true; \ + } \ + emit did##vMETHOD( vDATA1 , vDATA2 ); \ + } \ + Q_SIGNALS : void did##vMETHOD(vTYPE1 ,vTYPE2 ); \ + private Q_SLOTS : void on##vMETHOD(vTYPE1 ,vTYPE2 ); +//--------------------------------------------------------------------------------// +#define TIME_CALL( vMETHOD,vCOUNT ) \ +{ \ + static quint8 counter = 0; \ + if ( ! counter-- ) { counter = vCOUNT; vMETHOD; } \ +} \ +//--------------------------------------------------------------------------------// +#define REGISTER_METATYPE(vTYPE) \ + qRegisterMetaType < vTYPE > (#vTYPE); +//--------------------------------------------------------------------------------// +#define REGISTER_TYPE(vTYPE) \ + qmlRegisterType < vTYPE > (#vTYPE, 0, 1, #vTYPE); +//--------------------------------------------------------------------------------// Index: lib/MsgUtils/include/types.h =================================================================== diff -u --- lib/MsgUtils/include/types.h (revision 0) +++ lib/MsgUtils/include/types.h (revision cfc0df719cb5033078d0cac45ce0f6243810f2e7) @@ -0,0 +1,184 @@ +/*! * + * Copyright (c) 2019-2024 Diality Inc. - All Rights Reserved. + * \copyright + * THIS CODE MAY NOT BE COPIED OR REPRODUCED IN ANY FORM, IN PART OR IN + * WHOLE, WITHOUT THE EXPLICIT PERMISSION OF THE COPYRIGHT OWNER. + * + * \file types.h + * \author (last) Dara Navaei + * \date (last) 05-Feb-2024 + * \author (original) Behrouz NematiPour + * \date (original) 16-Dec-2019 + * + */ +#pragma once + +// Qt +#include +#include +#include + +// stl +#include + +// Project +#include "format.h" +// #include "Logger.h" + +// defines +// #define GetValue(vData, vIndex, vValue ) Types::getValue<>(vData, vIndex, vValue, QT_STRINGIFY(vValue)) +// #define GetBits( vData, vIndex, vFlags, vLen) Types::getBits( vData, vIndex, vFlags, vLen ) + +class Types { +public: + class Flags : public QBitArray { + public: + Flags() : QBitArray() { } + QString toString(QString vByteSeparator = " ") const { + QString tmp; + for (int i = 0; i < count(); i++) { + if (i % 4 == 0 && i != 0) + tmp += vByteSeparator; + tmp += at(i) ? '1' : '0'; + } + return tmp; + } + + Flags& operator=(const QBitArray &vData) { + if((QBitArray*)this != &vData){ + QBitArray::operator=(vData); + } + return *this; + } + }; + + /*! + * \brief The F32 union + * \details This is the union which will be used to extract the bytes of a float type value + * 4 bytes + */ + union F32 { // F32 + float value = 0; + quint8 bytes[sizeof(float)]; + }; + + /*! + * \brief The U32 union + * \details This is the union which will be used to extract the bytes of an unsigned int type value + * 4 bytes + */ + union U32 { // U32 + quint32 value = 0; + quint8 bytes[sizeof(quint32)]; + }; + + /*! + * \brief The S32 union + * \details This is the union which will be used to extract the bytes of an signed int type value + * 4 bytes + */ + union S32 { // S32 + qint32 value = 0; + quint8 bytes[sizeof(qint32)]; + }; + + /*! + * \brief The U16 union + * \details This is the union which will be used to extract the bytes of an unsigned char type value + * 2 bytes + */ + union U16 { // U16 + quint16 value = 0; + quint8 bytes[sizeof(quint16)]; + }; + + /*! + * \brief The S32 union + * \details This is the union which will be used to extract the bytes of an signed int type value + * 4 bytes + */ + union S16 { // S16 + qint16 value = 0; + quint8 bytes[sizeof(qint16)]; + }; + + /*! + * \brief The U08 union + * \details This is the union which will be used to extract the byte of an unsigned char type value + * 1 byte + */ + union U08 { // U08 + quint8 value = 0; + quint8 bytes[sizeof(quint8)]; + }; + + /*! + * \brief The S08 union + * \details This is the union which will be used to extract the byte of an signed char type value + * 1 byte + */ + union S08 { // S08 + qint8 value = 0; + qint8 bytes[sizeof(qint8)]; + }; + + + static bool floatCompare(float f1, float f2); + + template < typename T > + static bool getValue(const QByteArray &vData, int &vStartIndex, T &vValue, QString vValueName = ""); + static bool getBits (const QByteArray &vData, int &vStartIndex, QBitArray &vFlags, int vLen); + + template < typename T > + static bool setValue(const T &vValue, QByteArray &vData); + + template < typename T > + static T safeIncrement(T &vValue, quint8 vStep = 1) { + // step cannot be zero + if (!vStep) vStep = 1; + + T mMax = (T)(pow(2, sizeof(T) * 8) - 1); + // DEBUG: qDebug() << mMax; + if ( vValue + vStep <= mMax ) { + vValue += vStep; + } else { + vValue = vValue + vStep - mMax - 1; + } + return vValue; + } +}; + +template < typename T > +bool Types::getValue(const QByteArray &vData, int &vStartIndex, T &vValue, QString vValueName) { + int size = sizeof(T); + int end = vStartIndex + size; + if (vData.length() < end) { + Q_UNUSED(vValueName) + // LOG_DEBUG(QString("Not enough data from position %1 to the length of %2 to get data of type '%3' in buffer %4 %5") + // .arg(vStartIndex) + // .arg(size) + // .arg(typeid(T).name()) + // .arg(Format::toHexString(vData)) + // .arg(vValueName.isEmpty() ? "" : QString("for value %1").arg(vValueName)) + // ); + return false; + } + int i = 0; + while (i < size) { + vValue.bytes[i] = vData[vStartIndex + i]; + i++; + } + vStartIndex += size; + return true; +} + +template < typename T > +bool Types::setValue(const T &vValue, QByteArray &vData) { + int end = sizeof(T); + int i = 0; + while (i < end) { + vData += vValue.bytes[i]; + i++; + } + return true; +} Index: lib/MsgUtils/scripts/GenerateMsgDefsCpp.py =================================================================== diff -u --- lib/MsgUtils/scripts/GenerateMsgDefsCpp.py (revision 0) +++ lib/MsgUtils/scripts/GenerateMsgDefsCpp.py (revision cfc0df719cb5033078d0cac45ce0f6243810f2e7) @@ -0,0 +1,40 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + +import argparse +import os +import sys +from MsgCpp import MsgCpp + +def main(): + parser = argparse.ArgumentParser( + description='Tool for generating C++ header and source files containing messages definitions for the inputted message csv file' + ) + parser.add_argument('csv', nargs='+') + parser.add_argument('device_name', help='name of the device; used for naming the output C++ message definitions header and source files') + parser.add_argument('--header_dir', help='output directory for the generated .h header file', default='.') + parser.add_argument('--source_dir', help='output directory for the generated .cpp source file', default='.') + parser.add_argument('--namespace', dest='namespace', help='C++ namespace scope for generated C++ files', required=False) + parser.add_argument('--proto', action="store_true", help='add protobuf utility functions to generated C++ files', required=False) + + args = parser.parse_args() + if len(sys.argv) < 3: + parser.print_help() + else: + msg_cpp = MsgCpp() + try: + for csv in args.csv: + msg_cpp.load(csv) + if args.header_dir is not None: + os.makedirs(args.header_dir, exist_ok=True) + if args.source_dir is not None: + os.makedirs(args.source_dir, exist_ok=True) + if args.device_name is not None: + msg_cpp.write_msg_defs_header(f"{args.device_name}", args.header_dir, args.namespace, args.proto) + msg_cpp.write_msg_defs_source(f"{args.device_name}", args.source_dir, args.namespace, args.proto) + except Exception as e: + print('Error: %s' % e) + sys.exit(1) + +if __name__ == "__main__": # calling main function + main() Index: lib/MsgUtils/scripts/GenerateProtobuf.py =================================================================== diff -u --- lib/MsgUtils/scripts/GenerateProtobuf.py (revision 0) +++ lib/MsgUtils/scripts/GenerateProtobuf.py (revision cfc0df719cb5033078d0cac45ce0f6243810f2e7) @@ -0,0 +1,35 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + +import argparse +import os +import sys +from MsgProtobuf import MsgProtobuf + +def main(): + parser = argparse.ArgumentParser( + description='Tool for generating the protobuf input file from the inputted message csv file' + ) + parser.add_argument('--output_dir', help='output directory for the generated Protobuf file', default='.') + parser.add_argument('--namespace', help='namespace for protobuf package definition') + parser.add_argument('csv', nargs='+', help='csv input file') + parser.add_argument('protobuf', help='filename of outputted Protobuf file') + + args = parser.parse_args() + if len(sys.argv) < 2: + parser.print_help() + else: + msg_protobuf = MsgProtobuf() + try: + for csv in args.csv: + msg_protobuf.load(csv) + if args.output_dir is not None: + os.makedirs(args.output_dir, exist_ok=True) + msg_protobuf.write_proto(args.protobuf, args.namespace, args.output_dir) + except Exception as e: + print('Error: %s' % e) + sys.exit(1) + # msg_data.dump() + +if __name__ == "__main__": # calling main function + main() Index: lib/MsgUtils/scripts/MsgCpp.py =================================================================== diff -u --- lib/MsgUtils/scripts/MsgCpp.py (revision 0) +++ lib/MsgUtils/scripts/MsgCpp.py (revision cfc0df719cb5033078d0cac45ce0f6243810f2e7) @@ -0,0 +1,166 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- +from jinja2 import Environment, FileSystemLoader +from MsgData import MsgData +from pathlib import Path + +# \brief Class that usees the loaded MsgData and outputs converts to Protobuf +class MsgCpp(MsgData): + # \brief Initializer + def __init__(self): + super().__init__() + + + # \brief Load the csv files and cache the data. + # \note This will clear any previously cached csv data. + # \param[in] filename Filename of the csv to load. + # \param[in] clear If true, clear out any already loaded data before processing new data, + # otherwise new data will be added to the existing data + # \return none + def load(self, filename, clear=True): + super().load(filename, clear) + for (msg_id_value, msg) in self.data.items(): + msg['cpp_struct'] = self.__struct_data(msg) + + + # \brief Write the loaded MsgData to a C++ header file + # \param[in] device_name name of the device used to name the output message definitions header file + # \param[in] output_dir directory where the output header file will be written + # \param[in] namespace namespace to use in the header file, if namespace is blank then no namespace will be added + # \param[in] proto flag to add protobuf utility function to generated header file + def write_msg_defs_header(self, device_name, output_dir, namespace=None, proto=False): + env = Environment(loader = FileSystemLoader(f'{Path(__file__).parent.absolute()}/templates')) + template = env.get_template('MsgDefs_h.jinja') + render = template.render({ + 'msg_cpp': self, + 'cpp_namespace': namespace, + 'proto': proto, + 'device_name': device_name + }) + header_path = Path(output_dir).joinpath(f"{device_name}MsgDefs.h") + with open(header_path, mode='w', encoding='utf-8', newline='\n') as out_file: + out_file.write(render) + out_file.close() + print(f"Wrote C++ message definitions header file {header_path}") + + + # \brief Write the loaded MsgData to a source file + # \param[in] device_name name of the device used to name the output message definitions source file + # \param[in] output_dir directory where the output header file will be written + # \param[in] namespace namespace to use in the header file, if namespace is blank then no namespace will be added + # \param[in] proto flag to add protobuf utility function to generated header file + def write_msg_defs_source(self, device_name, output_dir, namespace=None, proto=False): + env = Environment(loader = FileSystemLoader(f'{Path(__file__).parent.absolute()}/templates')) + template = env.get_template('MsgDefs_cpp.jinja') + render = template.render({ + 'msg_cpp': self, + 'msg_defs_header': f"{device_name}MsgDefs.h", + 'cpp_namespace': namespace, + 'proto': proto, + }) + srcPath = Path(output_dir).joinpath(f"{device_name}MsgDefs.cpp") + with open(srcPath, mode='w', encoding='utf-8', newline='\n') as out_file: + out_file.write(render) + out_file.close() + print(f"Wrote C++ message definitions source file {srcPath}") + + + def write_log_parser(self, basename, header_dir, source_dir, namespace, msg_defs_header): + with open((header_dir.rstrip('/') + "/" if header_dir else "") + basename + '.h', mode='w', encoding='utf-8', newline='\n') as out_file: + includes = set() + includes.add(f"\"{msg_defs_header}\"") + if len(includes): + for key, value in self.__includeGroups(includes): + out_file.write("\n".join(f"#include {include}" for include in value) + "\n\n") + if namespace: + out_file.write(f"namespace {namespace}\n{{\n\n") + if namespace: + out_file.write(f"\n}} // namespace {namespace}\n") + out_file.close() + print(f'Wrote message source file {(output_dir.rstrip("/") + "/" if output_dir else "") + basename}.cpp') + + + def write_source(self, basename, output_dir, namespace): + with open((output_dir.rstrip('/') + "/" if output_dir else "") + basename + '.cpp', mode='w', encoding='utf-8', newline='\n') as out_file: + includes = set() + includes.add(f"\"{Path(basename + '.h').name}\"") + if len(includes): + for key, value in self.__includeGroups(includes): + out_file.write("\n".join(f"#include {include}" for include in value) + "\n\n") + if namespace: + out_file.write(f"namespace {namespace}\n{{\n\n") + if namespace: + out_file.write(f"\n}} // namespace {namespace}\n") + out_file.close() + print(f'Wrote message source file {(output_dir.rstrip("/") + "/" if output_dir else "") + basename}.cpp') + + + def __includeGroups(self, includes): + # sort the includes + groups = { } + # create groups for the includes based on their search order (i.e. , "include") + for include in includes: + if include[0] not in groups: + groups[include[0]] = [ ] + groups[include[0]].append(include) + # sort the groups, system includes () first and local includes ("include") second + groups = sorted(groups.items(), key=lambda x: x[0], reverse=True) + # sort each group of includes based on alphabetic order of the filename + for key, value in groups: + value.sort() + return groups + + + def __struct_data(self, msg_def): + def field_type_to_cpp_type(field_type): + if field_type == 'BOOL': + return "Types::U32" + elif field_type == 'U08': + return "Types::U08" + elif field_type == 'U16': + return "Types::U16" + elif field_type == 'U32': + return "Types::U32" + elif field_type == 'S08': + return "Types::S08" + elif field_type == 'S16': + return "Types::S16" + elif field_type == 'S32': + return "Types::S32" + elif field_type == 'F32': + return "Types::F32" + elif field_type == 'union': + return "ParamUnion" + else: + return None + def field_type_size(field_type): + if field_type == 'BOOL': + return 4 + elif field_type == 'U08': + return 1 + elif field_type == 'U16': + return 2 + elif field_type == 'U32': + return 4 + elif field_type == 'S08': + return 1 + elif field_type == 'S16': + return 2 + elif field_type == 'S32': + return 4 + elif field_type == 'F32': + return 4 + elif field_type == 'union': + return 4 + else: + return 0 + struct_info = { + 'payload': [ ], + 'payload_size': 0, + } + for field in msg_def['payload']: + struct_info['payload_size'] += field_type_size(field['type']) + struct_info['payload'].append({ 'name': field['name'], 'type': field['type'], 'cpp_type': field_type_to_cpp_type(field['type']) }) + if struct_info['payload'][-1]['type'] is None: + print(f"WARNING: unhandled type \"{field['type']}\" for field {field['name']} in message {msg_def['msg_id']} ({msg_def['msg_id_hex_string']})") + return struct_info Index: lib/MsgUtils/scripts/MsgData.py =================================================================== diff -u --- lib/MsgUtils/scripts/MsgData.py (revision 0) +++ lib/MsgUtils/scripts/MsgData.py (revision cfc0df719cb5033078d0cac45ce0f6243810f2e7) @@ -0,0 +1,150 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + +from collections import OrderedDict +import csv +import os +from pathlib import Path +import sys + +# \brief Class that reads and stores the message data. +class MsgData(object): + # \brief Initializer + def __init__(self): + self.data = { } + self._msg_col = [ 'hex_msg_id', 'dec_msg_id', 'msg_id', 'source', 'module', 'type', 'can_channel', 'hex_can_channel', 'ack_status', 'payload' ] + + + @property + def field_types(self): + types = set() + for (msg_id_value, msg) in self.data.items(): + types.update(field['type'].upper() for field in msg['payload']) + return list(sorted(types)) + + + def field_list(self, msg_id_value, empty_string=True): + fields = [ ] + for field in self.data[msg_id_value]['payload']: + fields.append(f"{field['type']}-{field['name']}") + if len(fields) == 0: + fields.append("") + return fields + + + # \brief Converts a value to a hex string + # \return hex string representation of the inputted value + @staticmethod + def value_to_hex_string(value): + return f"0x{f'{value:x}'.upper()}" + + + # \brief Print all loaded data to the console + # \return none + def dump(self): + for row in self.data: + print(f"{row}:") + print(f" msg_name: {self.data[row]['msg_name']}") + print(f" msg_id: {self.data[row]['msg_id_hex_string']} ({self.data[row]['msg_id_value']})") + print(f" can_channel: {self.data[row]['can_channel']} ({MsgData.value_to_hex_string(self.data[row]['can_channel_value'])})") + print(f" ack_status: {self.data[row]['ack_status']}") + if (len(self.data[row]['payload'])): + print(f" payload: [") + for field in self.data[row]['payload']: + print(f" {field['name']} : {field['type']}") + print(f" ]") + else: + print(f" payload: [ ]") + print(f" raw: {self.data[row]['raw']}") + print(f"") + + + # \brief Load the csv files and cache the data. + # \note This will clear any previously cached csv data. + # \param[in] filename Filename of the csv to load. + # \param[in] clear If true, clear out any already loaded data before processing new data, + # otherwise new data will be added to the existing data + # \return none + def load(self, filename, clear=True): + filename = Path(filename).resolve() + def get_field_from_entry(entry, column): + if column in self._msg_col and len(entry) > self._msg_col.index(column): + return entry[self._msg_col.index(column)].strip() + return '' + def get_int_field_from_entry(entry, column): + value = get_field_from_entry(entry, column) + return int(value) if value != '' else 0 + def get_hex_field_from_entry(entry, column): + value = get_field_from_entry(entry, column) + return int(value, 16) if value != '' else 0 + # clear the previously cached csv data, if requested + if clear: + self.data = { } + with open(filename, mode='r', encoding='utf-8') as in_file: + # skip the first row, it is the header + header = in_file.readline() + entry_count = 0 + # loop thru each entry/row in the file and read in the data + for entry in list(csv.reader(in_file, delimiter=',', quotechar='"', quoting=csv.QUOTE_MINIMAL)): + entry_count += 1 + msg_id_value = get_int_field_from_entry(entry, 'dec_msg_id') + row_data = { + 'msg_id': entry[self._msg_col.index('msg_id')].strip(), + 'msg_id_value': get_int_field_from_entry(entry, 'dec_msg_id'), + 'msg_id_hex_string': MsgData.value_to_hex_string(get_int_field_from_entry(entry, 'dec_msg_id')), + 'can_channel': get_field_from_entry(entry, 'can_channel'), + 'can_channel_value': get_hex_field_from_entry(entry, 'hex_can_channel'), + 'ack_status': bool(get_field_from_entry(entry, 'ack_status') == 'ACK_REQUIRED'), + 'msg_name': self.__message_name(entry[self._msg_col.index('msg_id')].strip()), + 'payload': [ ], + 'raw': entry, + } + for i in range(len(self._msg_col), len(entry)): + if '-' in entry[i]: + result = entry[i].split('-', 1) + if '.' in result[1]: + print(f"WARNING: {row_data['msg_id']} ({row_data['msg_id_hex_string']}) field \"{entry[i]}\" contains illegal character \'.\'") + result[1] = result[1].replace(' ', '').split('.')[-1] + field_name = result[1] + count = 0 + while any(field['name'] == field_name for field in row_data['payload']): + if count == 0: + print(f"WARNING: {row_data['msg_id']} ({row_data['msg_id_hex_string']}) contains duplicate field \"{field_name}\"") + count += 1 + field_name = f"{result[1]}{count}" + row_data['payload'].append({ + 'raw': entry[i], + 'name': field_name, + 'type': result[0], + }) + if msg_id_value in self.data: + print(f"WARNING: found MesgIDs with same value, {self.data[msg_id_value]['msg_id']} will be replaced by {row_data['msg_id']} for value {row_data['msg_id_hex_string']}") + self.data[msg_id_value] = row_data + in_file.close() + # sort the collected entries + self.data = OrderedDict(sorted(self.data.items(), + key=lambda x: (int(x[1]['msg_id_value'] if x[1]['msg_id_value'] is not None else 99999), x[0]))) + print(f'Loaded {filename} with {entry_count} entries') + + + # \brief Sort the cached csv data. + # \return none + def sort(self): + try: + self.data = OrderedDict(sorted(self.data.items(), + key=lambda x: (int(x[1]['msg_id_value'] if x[1]['msg_id_value'] is not None else 99999), x[0]))) + except Exception as e: + pass + + + def __message_name(self, msg_id): + # if python version >= 3.9.x, just use str.removeprefix built-in string function + if sys.version_info >= (3,9,0): + return ''.join(word.capitalize() for word in msg_id.removeprefix('MSG_ID_').split('_')) + # otherwise need to define and use remove_prefix custom function + else: + def remove_prefix(text, prefix): + if text.startswith(prefix): + return text[len(prefix):] + return text + return ''.join(word.capitalize() for word in remove_prefix(msg_id, 'MSG_ID_').split('_')) Index: lib/MsgUtils/scripts/MsgProtobuf.py =================================================================== diff -u --- lib/MsgUtils/scripts/MsgProtobuf.py (revision 0) +++ lib/MsgUtils/scripts/MsgProtobuf.py (revision cfc0df719cb5033078d0cac45ce0f6243810f2e7) @@ -0,0 +1,44 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- +from jinja2 import Environment, FileSystemLoader +from MsgData import MsgData +import os +from pathlib import Path + +# \brief Class that usees the loaded MsgData and outputs converts to Protobuf +class MsgProtobuf(MsgData): + # \brief Initializer + def __init__(self): + super().__init__() + + + # \brief Write the loaded MsgData to a C++ header message to protobuf utility class + # \param[in] device_name name of device (e.g. Denali, Leahi) + # \param[in] output_dir directory where the output header file will be written + # \param[in] namespace namespace to use in the header file, if namespace is blank then no namespace will be added + def write_msg_proto_header(self, device_name, output_dir=None, namespace=None): + env = Environment(loader = FileSystemLoader(f'{Path(__file__).parent.absolute()}/templates')) + template = env.get_template('MsgDefs_h.jinja') + render = template.render(msg_data = self.data, device_name = device_name, cpp_namespace = namespace) + header_path = Path(output_dir).joinpath(f"{device_name}MsgProto.h") + with open({header_path}, mode='w', encoding='utf-8', newline='\n') as out_file: + out_file.write(render) + out_file.close() + print(f"Wrote C++ Protobuf utiltity header file {header_path}") + + + # \brief Write the loaded MsgData to a protobuf file + # \param[in] filename name of the outputted protobuf file + # \param[in] output_dir directory where the outputted file will be written + def write_proto(self, filename, namespace=None, output_dir=None): + env = Environment(loader = FileSystemLoader(f'{Path(__file__).parent.absolute()}/templates')) + template = env.get_template('MsgDefs_proto.jinja') + render = template.render({ + 'msg_proto': self, + 'proto_namespace': namespace + }) + proto_path = Path(output_dir).joinpath(filename) + with open(proto_path, mode='w', encoding='utf-8', newline='\n') as out_file: + out_file.write(render) + out_file.close() + print(f"Wrote Protobuf message defintion file {proto_path}") Index: lib/MsgUtils/scripts/templates/MsgDefs_cpp.jinja =================================================================== diff -u --- lib/MsgUtils/scripts/templates/MsgDefs_cpp.jinja (revision 0) +++ lib/MsgUtils/scripts/templates/MsgDefs_cpp.jinja (revision cfc0df719cb5033078d0cac45ce0f6243810f2e7) @@ -0,0 +1,147 @@ +{%- if msg_defs_header | length -%} +#include +#include "{{ msg_defs_header }}" +{%- endif %} +{%- if cpp_namespace is defined and cpp_namespace is not none %} + +namespace {{ cpp_namespace }} +{ +{%- endif %} +{%- for (msg_id_value, msg) in msg_cpp.data.items() %} +{%- if proto is defined %} + +// {{ msg['msg_id'] }} ({{ msg['msg_id_hex_string'] }}) +// payload: {{ msg_cpp.field_list(msg_id_value) | join(", ") }} +void {{ msg['msg_name'] }}Payload::fromProtobuf([[maybe_unused]] const messages::{{ msg['msg_name'] }} &src) +{ +{%- for field in msg['payload'] %} +{%- if field['type'] != "union" %} + // {{ field['type'] ~ "-" ~ field['name'] }} + {{ field['name'] }}.value = src.{{ field['name'].lower() }}(); +{%- else %} + // TODO: {{ field['type'] ~ "-" ~ field['name'] }} +{%- endif %} +{%- endfor %} +} +{%- endif %} + +// {{ msg['msg_id'] }} ({{ msg['msg_id_hex_string'] }}) +// payload: {{ msg_cpp.field_list(msg_id_value) | join(", ") }} +bool {{ msg['msg_name'] }}Payload::fromQByteArray([[maybe_unused]] const QByteArray &src) +{ +{%- if msg['payload'] | length %} + int offset = 0; + return ( +{%- set fields = namespace(count = 0) %} +{%- for field in msg['payload'] %} +{%- if field['type'] == "union" %} + // TODO: {{ field['type'] ~ "-" ~ field['name'] }} +{%- if fields.count < msg['payload'] | length - 1 %} + true && +{%- else %} + true +{%- endif %} +{%- else %} + // {{ field['type'] ~ "-" ~ field['name'] }} +{%- if fields.count < msg['payload'] | length - 1 %} + Types::getValue<>(src, offset, {{ field['name'] }}, QT_STRINGIFY({{ field['name'] }})) && +{%- else %} + Types::getValue<>(src, offset, {{ field['name'] }}, QT_STRINGIFY({{ field['name'] }})) +{%- endif %} +{%- endif %} +{%- set fields.count = fields.count + 1 %} +{%- endfor %} + ); +{%- else %} + return true; +{%- endif %} +} +{%- if proto is defined %} + +// {{ msg['msg_id'] }} ({{ msg['msg_id_hex_string'] }}) +// payload: {{ msg_cpp.field_list(msg_id_value) | join(", ") }} +messages::{{ msg['msg_name'] }} {{ msg['msg_name'] }}Payload::toProtobuf() const +{ + messages::{{ msg['msg_name'] }} protoMsg; +{%- for field in msg['payload'] %} +{%- if field['type'] != "union" %} + // {{ field['type'] ~ "-" ~ field['name'] }} + protoMsg.set_{{ field['name'].lower() }}({{ field['name'] }}.value); +{%- else %} + // TODO: {{ field['type'] ~ "-" ~ field['name'] }} +{%- endif %} +{%- endfor %} + return protoMsg; +} +{%- endif %} + +// {{ msg['msg_id'] }} ({{ msg['msg_id_hex_string'] }}) +// payload: {{ msg_cpp.field_list(msg_id_value) | join(", ") }} +void {{ msg['msg_name'] }}Payload::toQByteArray([[maybe_unused]] QByteArray &dst) const +{ +{%- if msg['payload'] | length %} +{%- for field in msg['payload'] %} +{%- if field['type'] == "union" %} + // TODO: {{ field['type'] ~ "-" ~ field['name'] }} +{%- else %} + // {{ field['type'] ~ "-" ~ field['name'] }} + Types::setValue<>({{ field['name'] }}, dst); +{%- endif %} +{%- endfor %} +{%- endif %} +} + +void {{ msg['msg_name'] }}Payload::dump() const +{ + QStringList params; +{%- for field in msg['payload'] %} +{%- if field['type'] != "union" %} + params << QString("{{ field['name'] }}=%1").arg({{ field['name'] }}.value); +{%- endif %} +{%- endfor %} + qDebug().noquote() << QString("{{ msg['msg_name'] }}Payload: %1").arg(params.count() ? params.join(", ") : ""); +} +{%- endfor %} + +QByteArray canMessageToProtobufByteArray(const QDateTime ×tamp, const QString &deviceSerialNum, const Can::Message &msg) +{ + static const auto updateHeader = [&](messages::Header *header) { + if (header) { + const auto msecs = timestamp.toMSecsSinceEpoch(); + header->set_deviceserialnum(deviceSerialNum.toStdString()); + auto proto_timestamp = header->mutable_timestamp(); + if (proto_timestamp) { + proto_timestamp->set_seconds(msecs / 1000); + proto_timestamp->set_nanos((msecs % 1000) * 1000000); + } + header->set_msgid(msg.msgId); + header->set_sequence(msg.sequence); + } + }; + + switch (msg.msgId) { +{%- for (msg_id_value, msg) in msg_cpp.data.items() %} + case {{ msg['msg_id'] }}: { + {{ msg['msg_name'] }}Payload payload; + if (payload.fromQByteArray(msg.data) == false) { + qDebug().noquote() << "ERROR: could not convert CAN message with MsgId={{ msg['msg_name'] }} to struct"; + } + payload.dump(); + auto proto = payload.toProtobuf(); + updateHeader(proto.mutable_header()); + std::ostringstream out; + proto.SerializeToOstream(&out); + return out.str().c_str(); + break; + } +{%- endfor %} + default: + qDebug().noquote() << QString("WARNING: MsgId=0x%1 not handled").arg(msg.msgId, 4, 16, QChar('0')); + break; + } + return QByteArray(); +} +{%- if cpp_namespace is defined and cpp_namespace is not none %} + +} // namespace {{ cpp_namespace }} +{%- endif %} Index: lib/MsgUtils/scripts/templates/MsgDefs_h.jinja =================================================================== diff -u --- lib/MsgUtils/scripts/templates/MsgDefs_h.jinja (revision 0) +++ lib/MsgUtils/scripts/templates/MsgDefs_h.jinja (revision cfc0df719cb5033078d0cac45ce0f6243810f2e7) @@ -0,0 +1,96 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +#include "CanMessage.h" +#include "types.h" +{%- if proto is defined %} +#include "{{ device_name }}MsgDefs.pb.h" +{%- endif %} + +{%- if cpp_namespace is defined and cpp_namespace is not none %} + +namespace {{ cpp_namespace }} +{ +{%- endif %} + +enum MsgId : quint16 { +{%- for (msg_id_value, msg) in msg_cpp.data.items() %} + {{ msg['msg_id'] }} = {{ msg['msg_id_hex_string'] }}, +{%- endfor %} +}; + +enum ParamType { +{%- for type in msg_cpp.paramTypes %} + {{ type }}, +{%- endfor %} +}; + +// typedef union { +// Types::U08 uint8Value; +// Types::U16 uint16Value; +// Types::U32 uint32Value; +// Types::S08 int8Value; +// Types::S16 int16Value; +// Types::S32 int32Value; +// Types::F32 floatValue; +// } ParamUnion; +union ParamUnion { + uint8_t uint8Value; + uint16_t uint16Value; + uint32_t uint32Value; + int8_t int8Value; + int16_t int16Value; + int32_t int32Value; + float floatValue; + uint8_t bytes[std::max(sizeof(float), sizeof(uint32_t))]; +}; +{%- if msg_cpp.data.items() | length %} + +const QMap payloadSize = { +{%- for (msg_id_value, msg) in msg_cpp.data.items() %} + { {{ msg['msg_id'] }}, {{ msg['cpp_struct']['payload_size'] }} }, +{%- endfor %} +}; +{%- endif %} +{%- for (msg_id_value, msg) in msg_cpp.data.items() %} + +// {{ msg['msg_id'] }} ({{ msg['msg_id_hex_string'] }}) +// payload: {{ msg_cpp.field_list(msg_id_value) | join(", ") }} +struct {{ msg['msg_name'] }}Payload { +{%- for field in msg['cpp_struct']['payload'] %} + {{ field['cpp_type'] }} {{ field['name'] }}; +{%- endfor %} +{%- if msg['cpp_struct']['payload'] | length %} +{% endif %} +{%- if proto is defined %} +{%- if cpp_namespace is defined and cpp_namespace is not none %} + void fromProtobuf(const {{ cpp_namespace }}::messages::{{ msg['msg_name'] }} &src); +{%- else %} + void fromProtobuf(const messages::{{ msg['msg_name'] }} &src); +{%- endif %} +{%- endif %} + bool fromQByteArray(const QByteArray &src); +{%- if proto is defined %} +{%- if cpp_namespace is defined and cpp_namespace is not none %} + {{ cpp_namespace }}::messages::{{ msg['msg_name'] }} toProtobuf() const; +{%- else %} + messages::{{ msg['msg_name'] }} toProtobuf() const; +{%- endif %} +{%- endif %} + void toQByteArray(QByteArray &dst) const; + void dump() const; +}; +{%- endfor %} +{%- if cpp_namespace is defined and cpp_namespace is not none %} + +QByteArray canMessageToProtobufByteArray(const QDateTime ×tamp, const QString &deviceSerialNumber, const Can::Message &msg); + +} // namespace {{ cpp_namespace }} +{%- endif %} Index: lib/MsgUtils/scripts/templates/MsgDefs_proto.jinja =================================================================== diff -u --- lib/MsgUtils/scripts/templates/MsgDefs_proto.jinja (revision 0) +++ lib/MsgUtils/scripts/templates/MsgDefs_proto.jinja (revision cfc0df719cb5033078d0cac45ce0f6243810f2e7) @@ -0,0 +1,60 @@ +syntax = "proto3"; +{% if proto_namespace is defined and proto_namespace is not none %} +package {{ proto_namespace }}.messages; +{%- else %} +package messages; +{%- endif %} + +import "google/protobuf/timestamp.proto"; + +message Header { + string deviceSerialNum = 1; + google.protobuf.Timestamp timestamp = 2; + uint32 msgId = 3; + uint32 sequence = 4; +} +{%- for (msg_id, msg) in msg_proto.data.items() %} + +// {{ msg['msg_id'] }} ({{ msg['msg_id_hex_string'] }}) +{%- if msg['payload'] | length %} +{%- set fields = namespace(payloadList = []) %} +{%- for field in msg['payload'] %} +{%- set fields.payloadList = fields.payloadList + [ field['type'] ~ "-" ~ field['name'] ] %} +{%- endfor %} +{%- else %} +{%- set fields = namespace(payloadList = [""]) %} +{%- endif %} +// payload: {{ fields.payloadList | join(", ") }} +message {{ msg['msg_name'] }} { + Header header = 1; +{%- set fields = namespace(id = 2) %} +{%- for field in msg['payload'] %} +{%- if field['type'] == 'BOOL' %} + bool {{ field['name'] }} = {{ fields.id }}; +{%- set fields.id = fields.id + 1 %} +{%- elif field['type'] == 'U08' or field['type'] == 'U16' or field['type'] == 'U32' %} + uint32 {{ field['name'] }} = {{ fields.id }}; +{%- set fields.id = fields.id + 1 %} +{%- elif field['type'] == 'S16' or field['type'] == 'S32' %} + int32 {{field['name']}} = {{fields.id}}; +{%- set fields.id = fields.id + 1 %} +{%- elif field['type'] == 'F32' %} + float {{field['name']}} = {{fields.id}}; +{%- set fields.id = fields.id + 1 %} +{%- elif field['type'] == 'union' %} + oneof {{field['name']}} { + bool {{field['name']}}_bool = {{fields.id}}; +{%- set fields.id = fields.id + 1 %} + uint32 {{field['name']}}_uint32 = {{fields.id}}; +{%- set fields.id = fields.id + 1 %} + int32 {{field['name']}}_int32 = {{fields.id}}; +{%- set fields.id = fields.id + 1 %} + float {{field['name']}}_float = {{fields.id}}; +{%- set fields.id = fields.id + 1 %} + } +{%- else %} + // {{field['type']}} {{field['name']}} +{%- endif %} +{%- endfor %} +} +{%- endfor %} Index: lib/MsgUtils/src/FrameInterface.cpp =================================================================== diff -u --- lib/MsgUtils/src/FrameInterface.cpp (revision 0) +++ lib/MsgUtils/src/FrameInterface.cpp (revision cfc0df719cb5033078d0cac45ce0f6243810f2e7) @@ -0,0 +1,361 @@ +/*! + * + * Copyright (c) 2020-2024 Diality Inc. - All Rights Reserved. + * \copyright + * THIS CODE MAY NOT BE COPIED OR REPRODUCED IN ANY FORM, IN PART OR IN + * WHOLE, WITHOUT THE EXPLICIT PERMISSION OF THE COPYRIGHT OWNER. + * + * \file FrameInterface.cpp + * \author (last) Behrouz NematiPour + * \date (last) 30-Jul-2023 + * \author (original) Behrouz NematiPour + * \date (original) 26-Aug-2020 + * + */ +#include "FrameInterface.h" + +// Qt +#include +#include +#include + +// Project +// #include "Logger.h" +// #include "MessageDispatcher.h" +// #include "CanInterface.h" + +// namespace +using namespace Can; + +/*! + * \brief FrameInterface::FrameInterface + * \details Constructor + * \param parent - QObject parent owner object. + * Qt handles the children destruction by their parent objects life-cycle. + */ +FrameInterface::FrameInterface(QObject *parent) : + QObject(parent) +{ +} + +/*! + * \brief Message Handler initializer + */ +bool FrameInterface::init() +{ + if (_init) { + return false; + } + _init = true; + + initConnections(); + + startTimer(1, Qt::PreciseTimer); + + // LOG_DEBUG(QString("%1 Initialized").arg(metaObject()->className())); + + return true; +} + +/*! + * \brief FrameInterface::init + * \details Initialized the Class by calling the init() method first + * And initializes the thread vThread by calling initThread + * on success init(). + * \param vThread - the thread + * \return returns the return value of the init() method + */ +bool FrameInterface::init(QThread &vThread) +{ + if (init() == false) { + return false; + } + initThread(vThread); + return true; +} + +/*! + * \brief FrameInterface::quit + * \details quits the class + * Calls quitThread + */ +void FrameInterface::quit() +{ + quitThread(); // validated +} + +/*! + * \brief FrameInterface connections definition + * \details Initializes the required signal/slot connection between this class and other objects + * to be able to communicate. + */ +void FrameInterface::initConnections() +{ + // From GUI + // connect(&_MessageDispatcher, &MessageDispatcher::didFrameTransmit, this, &FrameInterface::onFrameTransmit); + // From CAN + // connect(&_CanInterface, &CanInterface::didFrameReceive, this, &FrameInterface::onFrameReceive); + // connect(&_CanInterface, &CanInterface::didFrameWritten, this, &FrameInteface::onFrameWritten); +} + +/*! + * \brief ApplicationController::initThread + * \details Moves this object into the thread vThread. + * And checks that this method is called from main thread. + * Also connects quitThread to application aboutToQuit. + * \param vThread - the thread + */ +void FrameInterface::initThread(QThread &vThread) +{ + // runs in main thread + Q_ASSERT_X(QThread::currentThread() == qApp->thread() , __func__, "The Class initialization must be done in Main Thread" ); + _thread = &vThread; + _thread->setObjectName(QString("%1_Thread").arg(metaObject()->className())); + connect(qApp, SIGNAL(aboutToQuit()), this, SLOT(quit())); + moveToThread(_thread); + _thread->start(); +} + +/*! + * \brief FrameInterface::quitThread + * \details Moves this object to main thread to be handled by QApplication and to be destroyed there. + */ +void FrameInterface::quitThread() +{ + if (_thread) { + moveToThread(qApp->thread()); // validated + } +} + +/*! + * \brief FrameInterface::transmitFrame + * \details Prepares a frame to be transmitted + * and emit signal didFrameTransmit with the frame as its argument + * \param vFrameId - Channel id of the CANBus frame + * \param vData - The Data this frame is going to carry + * \note This frame is created by MessageBuilder + * and it can be one of the frames of a message + * which has been chopped into frames. + */ +void FrameInterface::transmitFrame(CanId vCanId, const QByteArray &vData) +{ + if (vData.length() > Can::eLenCanFrame) { + // LOG_DEBUG(QString("Payload cannot be larger than %1 bytes").arg(Can::eLenCanFrame)); + return; + } + QCanBusFrame mFrame; + mFrame.setFrameId(vCanId); + mFrame.setPayload(vData); + emit didFrameTransmit(mFrame); +} + +/*! + * \brief FrameInterface::checkChannel + * \details Checks for the channel id of the received frame + * which needs to be handled or ignored. + * \param vFrameId - Channel id of the frame + * \param vOK - will be set to true if the channel + * is valid and a variable has been passed to + * \return The Category if the channels from the UI + * perspective FrameInterface::ChannelGroup + */ +FrameInterface::ChannelGroup FrameInterface::checkChannel(quint32 vFrameId, bool *vOK, bool *vDebugChanngel) +{ + bool ok = true; + bool debugChannel = false; + + FrameInterface::ChannelGroup channelGroup = ChannelGroup::eChannel_Unknown; + switch (vFrameId) { + case eDialin_TD : + case eTD_Dialin : + case eDialin_DD : + case eDD_Dialin : + case eDialin_UI : + case eUI_Dialin : + // TODO if ( gDisableDialinUnhandled ) { + // TODO channelGroup = ChannelGroup::eChannel_Ignores; + // TODO } else { + // TODO channelGroup = ChannelGroup::eChannel_Listens; + // TODO } + // TODO debugChannel = gDisableDialinUnhandled; // if debug channel is true, the raw can message in logged in the service log. + // TODO ok = ! gDisableDialinUnhandled; // if ok is true then it will be interpreted as unhandled messages. + channelGroup = ChannelGroup::eChannel_Listens; + debugChannel = true; // TODO + ok = true; // TODO + break; + + // these channels will be used for testing for now. + case eChlid_TD_DD : + case eChlid_DD_TD : + case eChlid_DD_FP : + case eChlid_FP_DD : + //channelGroup = ChannelGroup::eChannel_Ignores; + //break; + + case eChlid_TD_UI : + case eChlid_TD_Alarm : + case eChlid_TD_Sync : + //case eChlid_DD_UI : // has duplicate value as eChlid_DD_Sync + case eChlid_DD_Alarm : + case eChlid_DD_Sync : + //case eChlid_FP_UI : // has duplicate value as eChlid_FP_Sync, Only in α, will be removed in ꞵ + case eChlid_FP_Alarm : // Only in α, will be removed in ꞵ + case eChlid_FP_Sync : // Only in α, will be removed in ꞵ + channelGroup = ChannelGroup::eChannel_Listens; + break; + + case eChlid_UI_Alarm : + case eChlid_UI_Sync : + case eChlid_UI_TD : + //case eChlid_UI_DD : // has duplicate value as eChlid_UI_Sync + channelGroup = ChannelGroup::eChannel_Outputs; + break; + + default: + ok = false; + break; + + } + if (vOK) *vOK = ok; + if (vDebugChanngel) *vDebugChanngel = debugChannel; + + return channelGroup; +} + +/*! + * \brief FrameInterface::onFrameReceive + * \details This the slot connected to the CanInterface didFrameReceive signal. + * When a frame received over the CANBus, + * this slot will be called to check the channel if should be listened to. + * and will emit didFrameReceive if should be handled and ignored otherwise. + * \param vFrame - The frame has to be sent + */ +void FrameInterface::onFrameReceive(const QCanBusFrame &vFrame) +{ + bool ok = false; + bool debugChannel = false; + + quint32 mFrameId = vFrame.frameId(); + + ChannelGroup channelGroup = checkChannel(mFrameId, &ok, &debugChannel); + + QString logMessage; + if ( debugChannel ) { + // logMessage = "Debug Channel\r\n" + + // Format::toHexString(mFrameId, false, eLenChannelDigits) + " -- " + vFrame.payload().toHex(' '); + // LOG_DEBUG(logMessage); + } + if ( ! ok ) { + // logMessage = "Unexpected Channel\r\n" + + // Format::toHexString(mFrameId, false, eLenChannelDigits) + " -- " + vFrame.payload().toHex(' '); + // LOG_DEBUG(logMessage); + return; + } + + if ( channelGroup != ChannelGroup::eChannel_Listens) { + return; + } + + CanId mCanId = static_cast(mFrameId); + emit didFrameReceive(mCanId, vFrame.payload()); +} + +/*! + * \brief FrameInterface::onFrameTransmit + * \details This the slot connected to the MessageDispatcher didFrameTransmit signal. + * When a frame needs to be send to CANBus, + * this slot will call transmitFrame method to do the job. + * \param vCanId - CANBus Can Id target of the frame. + * \param vData - The data which this frame will carry. + */ +void FrameInterface::onFrameTransmit(CanId vCanId, const QByteArray &vData) +{ + appendHead(vCanId, vData); + // DEBUG: qDebug() << _timestamp << "apnd #" << _txFrameList.count(); +} + +/*! + * \brief FrameInterface::onFrameWritten + * \param vCount + */ +void FrameInterface::onFrameWritten(qint64 /*vCount*/) +{ + _transmitted = true; + removeHead(); + // DEBUG: qDebug() << _timestamp << "Sent #" << _txFrameList.count() << vCount; +} + +/*! + * \brief FrameInterface::timerEvent + * \details This event handler is re-implemented in this subclass to receive timer events for this object. + */ +void FrameInterface::timerEvent(QTimerEvent *) +{ + static quint8 count = 0; + // TEST : _timestamp = QTime::currentTime().toString("HH:mm:ss.zzz"); + + if (++count != _interval) return; + // DEBUG: qDebug() << _timestamp; + count = 0; + _transmitted = false; + trnsmtHead(); +} + +/*! + * \brief FrameInterface::trnsmtHead + * \details Transmits the head of the transmit buffer + * Sends an empty frame with lowest priority if the transmit buffer is empty + * to keep the UI board Can-driver awake. + */ +void FrameInterface::trnsmtHead() +{ + if ( _txFrameList.isEmpty() ) { + // TODO if ( gSendEmptyKeepAwake ) { + transmitFrame(eChlid_LOWEST,QByteArray()); // Keep the CANBus awake. + return; + // TODO } + } + else { + Frame frame = _txFrameList.first(); + transmitFrame(frame.canId, frame.data); + // DEBUG: qDebug() << _timestamp << "Tsmt #" << _txFrameList.count(); + } +} + +/*! + * \brief FrameInterface::removeHead + * \details Removes the frame from the head of the transmit buffer + * in case transmission has been confirmed by the CANBus driver + * in the FrameInterface::onFrameWritten slot + * which is connected to CanInterface::didFrameWritten. + */ +void FrameInterface::removeHead() +{ + if ( _txFrameList.isEmpty() ) { + return; + } + _txFrameList.removeFirst(); +} + +/*! + * \brief FrameInterface::appendHead + * \details Appends the frame to the transmit buffer to be sent later + * \param vCanId - the CANBus id + * \param vData - the data to be sent + */ +void FrameInterface::appendHead(CanId vCanId, const QByteArray &vData) +{ + //DEBUG qDebug() << "F " << _txFrameList.count(); + if (_txFrameList.count() >= _txFrameList_Max) { + static quint32 i = 0; + // if ( i % 60 == 0 ) { // log only for the first time and each minute. + // LOG_DEBUG(QString("Transmit buffer overflow of %1").arg(_txFrameList_Max)); + // } + if ( i < UINT32_MAX - 1 ) i++ ; + else i = 0; + return; + } + + // Frame frame = Frame(vCanId, vData); + _txFrameList.append({ vCanId, vData }); +} Index: lib/MsgUtils/src/MessageBuilder.cpp =================================================================== diff -u --- lib/MsgUtils/src/MessageBuilder.cpp (revision 0) +++ lib/MsgUtils/src/MessageBuilder.cpp (revision cfc0df719cb5033078d0cac45ce0f6243810f2e7) @@ -0,0 +1,472 @@ +/*! + * + * Copyright (c) 2020-2024 Diality Inc. - All Rights Reserved. + * \copyright + * THIS CODE MAY NOT BE COPIED OR REPRODUCED IN ANY FORM, IN PART OR IN + * WHOLE, WITHOUT THE EXPLICIT PERMISSION OF THE COPYRIGHT OWNER. + * + * \file MessageBuilder.cpp + * \author (last) Behrouz NematiPour + * \date (last) 18-Apr-2022 + * \author (original) Behrouz NematiPour + * \date (original) 26-Aug-2020 + * + */ +#include "MessageBuilder.h" + +// Project +#include "crc.h" +#include "format.h" + +namespace Can +{ + +/*! + * \brief MessageBuilder::MessageBuilder + * \details Constructor + * \param parent - QObject parent owner object. + * Qt handles the children destruction by their parent objects life-cycle. + */ +MessageBuilder::MessageBuilder(QObject *parent) : + QObject(parent) +{ +} + +/*! + * \brief MessageBuilder::buildFrames + * \details This method builds list of frames out of the vMsgIds of type GuiActionType + * and vData of type QByteArray which has been requested to be sent by UI. + * The message will be chopped into 8 bytes frames to be able to be send + * by fixed length CANBus protocol. + * \param vMsgId - The ActionID of the requested message. + * \param vData - The payload of the message. + * \param vFrameList - The list of frames which has been created by vMsgId and vData to be sent. + * \return true on successful to build a frame + */ +bool MessageBuilder::buildFrames(const MsgId vMsgId, const QByteArray &vData, FrameList &vFrameList, const Sequence vSequence) +{ + QByteArray mPayload; + addSyncByte(mPayload); + addSequence(mPayload, vSequence); + if (addMsgId(mPayload, vMsgId) == false) { + return false; + } + if (addData(mPayload, vMsgId, vData) == false) { + return false; + } + addCRC(mPayload); + + const quint16 len = mPayload.length(); + if (len > eLenCanFrame) { + quint8 frameCount = len / eLenCanFrame; + if (len % eLenCanFrame) { + ++frameCount; + } + for (quint8 i = 0; i < frameCount; i++) { + vFrameList += mPayload.mid(i * eLenCanFrame, eLenCanFrame); + } + } + else { + vFrameList += mPayload; + } + + addPadding(vFrameList.last()); + return true; +} + +/*! + * \brief MessageBuilder::addSyncByte + * \details Adds the sync/start byte at the end of the Payload vPayload of type QByteArray + * \param vPayload - payload which is going to be constructed by appending byte + */ +void MessageBuilder::addSyncByte(QByteArray &vPayload) +{ + vPayload.append(ePayload_Sync); // Sync byte +} + +/*! + * \brief MessageBuilder::addSequence + * \details Adds the sequence number to the Denali message. + * \param vPayload - The message payload to be used for adding the sequence number to. + * \param vSequence - The sequence number + */ +void MessageBuilder::addSequence(QByteArray &vPayload, const Sequence vSequence) +{ + Sequence_Bytes mSequence; + mSequence.value = vSequence; + for (quint8 index = 0; index < sizeof(mSequence); index++) { + vPayload += mSequence.bytes[index]; + } +} + +/*! + * \brief MessageBuilder::addMsgId + * \details Adds the sync/start byte at the end of the Payload vPayload of type QByteArray + * \param vPayload - payload which is going to be constructed by appending byte + * \param vMsgId - The ActionID of the message which needs to be appended + * to the Payload vPayload + */ +bool MessageBuilder::addMsgId(QByteArray &vPayload, const MsgId vMsgId) +{ + quint16 mAction = static_cast(vMsgId); + vPayload += (mAction >> 8) & 0xFF; // high byte + vPayload += mAction & 0xFF; // low byte + return true; +} + +/*! + * \brief MessageBuilder::addData + * \details Regarding ActionID appends correct bytes amount of vData to the vPayload + * \param vPayload - payload which is going to be constructed by appending byte + * \param vMsgId - The ActionID of the message which needs to be appended + * \param vData - The data which is going to be message payload. + * \return false if the vData of type QByteArray is not sufficient regarding vMsgId + */ +bool MessageBuilder::addData(QByteArray &vPayload, MsgId const vMsgId, const QByteArray &vData) +{ + // quint8 len = payloadLen[vMsgId]; + // // if len has been set to max(255) + // // it means it has no limit and can be as long as 255 bytes + // if (len == eLenMaxData) { + // len = (vData.length() > eLenMaxData) ? eLenMaxData : vData.length(); + // } + // if (vData.length() < len) { + // // QString mHexMIdString = Format::toHexString(vMsgId, false, eLenMessageIDDigits); + // // QString mHexDatString = vData.toHex('.').toUpper(); + // // LOG_DEBUG(QString("Not enough data has been provided for the Message ID '%1'\r\n%2") + // // .arg(mHexMIdString) + // // .arg(mHexDatString) + // // ); + // return false; + // } + Q_UNUSED(vMsgId) + const quint8 len = (vData.length() > eLenMaxData) ? eLenMaxData : vData.length(); + vPayload += len; + vPayload += vData.mid(0, len); // Adding required Data + return true; +} + +/*! + * \brief MessageBuilder::addCRC + * \details Appends calculated crc8 (1 byte) of the vPayload at the end of the vPayload + * \param vPayload - The Payload which is going to be used for crc8 calculation + * \note The first byte will be excluded and is not part of the crc8 calculation. + * Since the first byte is the Sync and has always constant value of A5. + * SW/FW agreement. + */ +void MessageBuilder::addCRC(QByteArray &vPayload) +{ + // sync byte should not be Used for crc calculation + vPayload += calcCRC(vPayload.mid(1)); +} + +/*! + * \brief MessageBuilder::addPadding + * \details This method is appending bytes containing 0x00 + * to keep the length of the frame 8 bytes. + * \param vPayload - The payload of the CANBus message + */ +void MessageBuilder::addPadding(QByteArray &vPayload) +{ + vPayload = vPayload.leftJustified(eLenCanFrame, '\0'); +} + +/*! + * \brief MessageBuilder::calcCRC + * \details Calculates the crc8 + * \param vData - The data of type QByteArray to be used for crc8 calculation. + * \return returns a byte contains crc8 + */ +quint8 MessageBuilder::calcCRC(const QByteArray &vData) +// TODO : This section better to be in the MessageModel +{ + return crc8(vData); +} + +// CRC is always next byte after Data +/*! + * \brief MessageBuilder::checkCRC + * \details This method checks the crc8 of the vData of type QByteArray + * by using the last byte as the crc8 of the rest of the data + * \param vData - The data of type QByteArray to be used for crc8 calculation. + * \param vExpected - The expected CRC value + * \param vActual - The actual value which has been read + * \return returns false if the crc8 is not correct or the data is empty. + */ +bool MessageBuilder::checkCRC(const QByteArray &vData, quint8 &vExpected, quint8 &vActual) +// TODO : This section better to be in the MessageModel +{ +#ifndef DISABLE_CRC + const int len = vData.length(); + if (! len) { + return false; + } + vActual = vData.back(); + vExpected = calcCRC(vData.mid(0, len - 1)); + return (vExpected == vActual); +#else + return true; +#endif +} + +/*! + * \brief MessageBuilder::checkCRC + * \details Overloaded CheckCRC which checks the CRC and Log the error if there is. + * \param vMessage - The message to check for the CRC + * \return false if has error + */ +bool MessageBuilder::checkCRC(const Message &vMessage) +{ + consoleOut("", false, CanId::eChlid_NONE); + QByteArray crcData = vMessage.head + vMessage.data; + quint8 mExpected = 0; + quint8 mBeenRead = 0; + return checkCRC(crcData, mExpected, mBeenRead); +} + +/*! + * \brief MessageBuilder::buildMessage + * \details Builds Message out of vPayload of type QByteArray + * by adding Sync byte, ActionID(MessageID), data length, data, CANBus channel id for header + * and keeps collecting data from payload up until the specified length. + * \param vPayload - The payload of the CANBus message + * \param vMessage - The Message variable which is going to be built + * \param vCanId - The CANBus frame channel id + * \return false if the payload does not contain sync byte (Payload_Data::ePayload_Sync) + */ +bool MessageBuilder::buildMessage(const QByteArray &vPayload, Message &vMessage, const CanId vCanId) +{ + QByteArray mPayload = vPayload; + if (vMessage.data.isEmpty()) { // message is empty so expected a header + if (hasSyncByte(mPayload)) { // Got header + consoleOut(vPayload, true, vCanId); + vMessage.canId = vCanId; + vMessage.head = getHeader(mPayload); // keep header before taking it out of the payload. does not affect payload + vMessage.sequence = getSequence(mPayload); + vMessage.msgId = getActionId(mPayload); + vMessage.length = getLength(mPayload); + vMessage.data = getData(mPayload, vMessage.length); + vMessage.initialized = true; + } + else { // Expected Header but got pure data + // LOG_DEBUG(QString("Expected Header, got frame without Sync byte")); + qDebug().noquote() << QString("Expected Header, got frame without Sync byte"); + printPayload(vPayload, false ,vCanId); + return false; + } + } + else { + consoleOut(vPayload, false ,vCanId); + vMessage.data += vPayload.mid(0, vMessage.length - vMessage.data.length()); + } + + // TODO : This section better to be in the MessageModel + // and when Message model identifies the message is complete + // will SIGNAL builder to check for crc. + if (vMessage.isComplete() && checkCRC(vMessage) == false) { + return false; + } + + return true; +} + +/*! + * \brief MessageBuilder::enableConsoleOut + * \details Enables or Disables the console output and logs the status + * \param vEnabled + */ +void MessageBuilder::enableConsoleOut(const bool vEnabled) { + if (_enableConsoleOut != vEnabled) { + _enableConsoleOut = vEnabled; + } + // if (_enableConsoleOut) { + // LOG_DEBUG("Console out MessageBuilder enabled"); + // } else { + // LOG_DEBUG("Console out MessageBuilder disabled"); + // } +} + +/*! + * \brief MessageBuilder::hasSyncByte + * \details Checks for Sync byte and take it out of vPayload + * \param vPayload - The payload of type QByteArray + * \return true if the first byte of the vPayload is sync byte + * (Payload_Data::ePayload_Sync) + * \note Removes the first 1 byte of sync byte from vPayload + * It starts from the first byte. + */ +bool MessageBuilder::hasSyncByte(QByteArray &vPayload) +{ + const quint8 mSyncByte = vPayload[0]; + if (mSyncByte == ePayload_Sync) { + vPayload = vPayload.mid(eLenSyncByte); + return true; + } + return false; +} + +/*! + * \brief MessageBuilder::getSequence + * \details Extract the 2 bytes of the sequence + * out of the vPayload of type QByteArray + * \param vPayload - The payload of the CANBus message + * \return Returns ActionId of type GuiActionType + * \note Removes the 2 bytes of ActionID from vPayload + * It starts from the first byte so those 2 bytes should be the first 2 bytes. + */ +Sequence MessageBuilder::getSequence(QByteArray &vPayload) +{ + Sequence_Bytes mSequence; + int index = 0; + Types::getValue<>(vPayload, index, mSequence); + vPayload = vPayload.mid(eLenSequence); + return mSequence.value; +} + +/*! + * \brief MessageBuilder::getHeader + * \details Collect the 3 bytes (Frame_Data::eLenHeaderInfo) + * as header portion of the payload + * which is the MessageID (2 bytes) , data length (1 byte) + * It does not contain sync byte (Payload_Data::ePayload_Sync) + * as this value will be used for crc8 calculation + * and that does not include sync byte (Payload_Data::ePayload_Sync) + * \param vPayload - The payload of the CANBus message + * \return Returns 3 byte of header data of type QByteArray + * \note As it's obvious from the function parameter it's not changing the vPayload + * Just has been mentioned to be consistent with the other methods of buildMessage. + * It starts from the first byte so the vPayload should not have the sync byte. + */ +QByteArray MessageBuilder::getHeader(const QByteArray &vPayload) +{ + QByteArray headInfo; + if (vPayload.length() < eLenHeaderInfo) { + // LOG_DEBUG("Incorrect Message Header"); + return headInfo; + } + for (int i = 0; i < eLenHeaderInfo; i++) { + headInfo += vPayload[i]; + } + return headInfo; +} + +/*! + * \brief MessageBuilder::getActionId + * \details Extracts the 2 bytes ActionID (Frame_Data::eLenActionId) + * out of the vPayload of type QByteArray + * \param vPayload - The payload of the CANBus message + * \return Returns ActionId of type GuiActionType + * \note Removes the 2 bytes of ActionID from vPayload + * It starts from the first byte so those 2 bytes should be the first 2 bytes. + */ +quint16 MessageBuilder::getActionId(QByteArray &vPayload) +{ + MsgId_Bytes mMsgId; + int index = 0; + Types::getValue<>(vPayload, index, mMsgId); + vPayload = vPayload.mid(eLenActionId); + return mMsgId.value; +} + +/*! + * \brief MessageBuilder::getLength + * \details Extracts the 1 byte data length out of the vPayload + * and removes the first 1 byte + * \param vPayload - The payload of the CANBus message + * \return returns the length of the data which is added by 1 to include 1 byte crc + * \note the length of the data should not be more than 255 which requires 1 byte only + * so technically it does not need to return a value of type more than 1 byte + * But some room reserved for later huge buffer passing + * And also as 1 byte crc8 is included as part of data + * it make it 256 which is going to be more than a byte. + * \note Removes the 1 byte of data length from vPayload + * It starts from the first byte so that byte should be the first 1 byte. + */ +int MessageBuilder::getLength(QByteArray &vPayload) +{ + // on the line bellow it has to be cast to unsigned otherwise FF will be converted to -1 and + 1 becomes 0. + const int mlen = static_cast(vPayload[0]) + 1; // Add CRC to the length of data + vPayload = vPayload.mid(eLenLength); + return mlen; +} + +/*! + * \brief MessageBuilder::getData + * \details Extract data from vPayload + * if vLen is less or equal to Frame_Data::eLenMaxHeaderData + * it gets data of first byte up to the len of vLen + * otherwise returns the whole vPayload as the data + * \param vPayload - The payload of the CANBus message + * \param vLen - the length of the data + * \return The data to be returned + */ +QByteArray MessageBuilder::getData(const QByteArray &vPayload, const int vLen) +{ + return (vLen <= eLenMaxHeaderData) ? vPayload.mid(0, vLen) : vPayload; +} + +/*! + * \brief MessageBuilder::printPayload + * \details Sends out formatted message to the console + * for debugging purposes. + * Since this method is only formatting the payload + * and does not extract the content of the payload + * it gets required information from outside through it's parameters. + * \param vPayload - The payload of the CANBus message + * \param vIsHeader - Should be sent as true if this payload contains header data + * \param vCanId - CANBus channel id + * \param vUseColor - Use coloring or just space formatted output + * if vUseColor passed true is uses different color for Sync byte, MessageID & data + */ +void MessageBuilder::printPayload(const QByteArray &vPayload, const bool vIsHeader, const CanId vCanId, bool const vUseColor) +{ + if (vCanId == CanId::eChlid_NONE) { + // qDebug() << " "; + return; + } + QByteArray view; + if (vUseColor) { + QList byteList; + byteList = vPayload.toHex('.').split('.'); + for (int i = 0; i < byteList.length(); i++) { + if (vIsHeader) { + if (i == 0) { + byteList[i] = QByteArray("\033[32m") + byteList[i].constData(); + } + if (i == 1 || i == 2) { + byteList[i] = QByteArray("\033[33m") + byteList[i].constData(); + } + if (i > 2) { + byteList[i] = QByteArray("\033[36m") + byteList[i].constData() + QByteArray("\033[0m"); + } + } + else { + byteList[i] = QByteArray("\033[36m") + byteList[i].constData() + QByteArray("\033[0m"); + } + } + view = Format::toHexString(vCanId, false, eLenChannelDigits).toLatin1() + " " + byteList.join('.'); + // the fprintf is used for the colored output + fprintf(stderr, "%s\n", view.constData()); + } + else { + view = Format::toHexString(vCanId, false, eLenChannelDigits).toLatin1() + " " + vPayload.toHex('.'); + // the fprintf is used for the colored output + fprintf(stderr, "%s\n", view.constData()); + } +} + +/*! + * \brief MessageBuilder::consoleOut + * \details An overloaded method of the MessageBuilder::printPayload + * which is only printPayload if the console output is enabled + * by setting MessageBuilder::enableConsoleOut + * \note please refer to MessageBuilder::printPayload + */ +void MessageBuilder::consoleOut(const QByteArray &vPayload, const bool vIsHeader, const CanId vCanId, const bool vUseColor) +{ + if (_enableConsoleOut) { + printPayload(vPayload, vIsHeader, vCanId, vUseColor); + } +} + +} // namespace Can Index: lib/MsgUtils/src/crc.cpp =================================================================== diff -u --- lib/MsgUtils/src/crc.cpp (revision 0) +++ lib/MsgUtils/src/crc.cpp (revision cfc0df719cb5033078d0cac45ce0f6243810f2e7) @@ -0,0 +1,61 @@ +/*! + * + * Copyright (c) 2019-2024 Diality Inc. - All Rights Reserved. + * \copyright + * THIS CODE MAY NOT BE COPIED OR REPRODUCED IN ANY FORM, IN PART OR IN + * WHOLE, WITHOUT THE EXPLICIT PERMISSION OF THE COPYRIGHT OWNER. + * + * \file crc.cpp + * \author (last) Behrouz NematiPour + * \date (last) 05-Sep-2020 + * \author (original) Behrouz NematiPour + * \date (original) 16-Dec-2019 + * + */ + +#include "crc.h" + +/*! + * \brief The CRC table + * \details This code has been borrowed from Firmware repository + * This should be exactly the same replica of that copy. + * PLEASE DO UPDATE ON ANY MODIFICATION IN FIRMWARE + */ +const unsigned char crc8_table[] = { + 0, 49, 98, 83, 196, 245, 166, 151, 185, 136, 219, 234, 125, 76, 31, 46, + 67, 114, 33, 16, 135, 182, 229, 212, 250, 203, 152, 169, 62, 15, 92, 109, + 134, 183, 228, 213, 66, 115, 32, 17, 63, 14, 93, 108, 251, 202, 153, 168, + 197, 244, 167, 150, 1, 48, 99, 82, 124, 77, 30, 47, 184, 137, 218, 235, + 61, 12, 95, 110, 249, 200, 155, 170, 132, 181, 230, 215, 64, 113, 34, 19, + 126, 79, 28, 45, 186, 139, 216, 233, 199, 246, 165, 148, 3, 50, 97, 80, + 187, 138, 217, 232, 127, 78, 29, 44, 2, 51, 96, 81, 198, 247, 164, 149, + 248, 201, 154, 171, 60, 13, 94, 111, 65, 112, 35, 18, 133, 180, 231, 214, + 122, 75, 24, 41, 190, 143, 220, 237, 195, 242, 161, 144, 7, 54, 101, 84, + 57, 8, 91, 106, 253, 204, 159, 174, 128, 177, 226, 211, 68, 117, 38, 23, + 252, 205, 158, 175, 56, 9, 90, 107, 69, 116, 39, 22, 129, 176, 227, 210, + 191, 142, 221, 236, 123, 74, 25, 40, 6, 55, 100, 85, 194, 243, 160, 145, + 71, 118, 37, 20, 131, 178, 225, 208, 254, 207, 156, 173, 58, 11, 88, 105, + 4, 53, 102, 87, 192, 241, 162, 147, 189, 140, 223, 238, 121, 72, 27, 42, + 193, 240, 163, 146, 5, 52, 103, 86, 120, 73, 26, 43, 188, 141, 222, 239, + 130, 179, 224, 209, 70, 119, 36, 21, 59, 10, 89, 104, 255, 206, 157, 172 +}; + +/*! + * \brief crc8 + * \param vData the data which to be used for crc8 generation + * \return a byte contains the generated crc8. + * \note this algorithm borrowed from Firmware repository + * some modification has been made to be able to work with Qt libraries. + */ +quint8 crc8(const QByteArray &vData) +{ + quint8 crc = 0; + int len = vData.length(); + int i = 0; + while ( len-- > 0 ) + { + crc = crc8_table[ (static_cast(vData[i])) ^ crc ]; + i++; + } + return crc; +} Index: lib/MsgUtils/src/format.cpp =================================================================== diff -u --- lib/MsgUtils/src/format.cpp (revision 0) +++ lib/MsgUtils/src/format.cpp (revision cfc0df719cb5033078d0cac45ce0f6243810f2e7) @@ -0,0 +1,225 @@ +/*! + * + * Copyright (c) 2019-2024 Diality Inc. - All Rights Reserved. + * \copyright + * THIS CODE MAY NOT BE COPIED OR REPRODUCED IN ANY FORM, IN PART OR IN + * WHOLE, WITHOUT THE EXPLICIT PERMISSION OF THE COPYRIGHT OWNER. + * + * \file format.cpp + * \author (last) Behrouz NematiPour + * \date (last) 28-May-2023 + * \author (original) Behrouz NematiPour + * \date (original) 16-Dec-2019 + * + */ +#include "format.h" + +// Qt +#include + +// Project +#include "types.h" + +Format::Format() { } + +/*! + * \brief Format::toHexString + * \details converts the unsigned int 16 bit value vValue to QString. + * \param vValue - value to convert. + * \param vWith0x - adds 0x to the output. + * \param vLen - length of the output string. + * \return QString + */ +QString Format::toHexString(quint16 vValue, bool vWith0x, quint8 vLen) { + if ( vWith0x ) { + return "0x" + QString("%1").arg(vValue,0,16).rightJustified(vLen, '0').toUpper(); + } else { + return QString("%1").arg(vValue,0,16).rightJustified(vLen, '0').toUpper(); + } +} + +/*! + * \brief Format::toHexByteArray + * \details Returns a hex encoded copy of the byte array. + * The hex encoding uses the numbers 0-9 and the letters a-f. + * Also converts to uppercase. + * \param vData - Data to convert + * \param separator - separator to put in string output in the middle of each byte. + * \return + */ +QByteArray Format::toHexByteArray(const QByteArray &vData, char separator) +{ + return vData.toHex(separator).toUpper(); +} + +/*! + * \brief Format::toHexString + * \details Converts the vData of QByteArray to Hex representation of QString + * \param vData - Data to convert + * \param separator - separator to put in string output in the middle of each byte. + * \return QString + */ +QString Format::toHexString(const QByteArray &vData, char separator) +{ + QString string = toHexByteArray(vData, separator); + return string; +} + +/*! + * \brief Format::fromVariant + * \details This static method converts the defined types into QByteArray + * \param vData - The value + * \return The QByteAttay of the value vData if cannot be converted 0x00 will be returned + * \note Regarding the QVariant type conversion, if cannot be converted 0 will be returned + * This rule has been used and also to be consistent followed the same rule. + * \note This method converts both float and double to F32 and returns its QByteArray representation. + */ +QByteArray Format::fromVariant(const QVariant &vData) +{ + QByteArray mData; + + switch (static_cast(vData.type())) { + case QMetaType::QString: // string + { + mData += vData.toByteArray(); + return mData; + } + + case QMetaType::QByteArray: // byte array + { + mData += vData.toByteArray(); + return mData; + } + + case QMetaType::QVariantList: // list + { + QVariantList list = vData.toList(); + for(const auto &item: std::as_const(list)) { + mData += fromVariant(item); + } + return mData; + } + case QMetaType::Bool: // bool + { + mData += vData.toUInt(); + return mData; + } + + case QMetaType::Float: + case QMetaType::Double: // F32 + { + Types::F32 f32; + f32.value = vData.toFloat(); + Types::setValue(f32, mData); + return mData; + } + + case QMetaType::UInt: // U32 + { + Types::U32 u32; + u32.value = vData.toUInt(); + Types::setValue(u32, mData); + return mData; + } + + case QMetaType::Int: // S32 + { + Types::S32 s32; + s32.value = vData.toInt(); + Types::setValue(s32, mData); + return mData; + } + + case QMetaType::Short: // S16 + { + Types::S16 s16; + s16.value = vData.toInt(); + Types::setValue(s16, mData); + return mData; + } + + case QMetaType::UShort: // U16 + { + Types::U16 u16; + u16.value = vData.toInt(); + Types::setValue(u16, mData); + return mData; + } + + case QMetaType::Char: // S08 + { + Types::S08 s08; + s08.value = vData.toInt(); + Types::setValue(s08, mData); + return mData; + } + + case QMetaType::UChar: // U08 + { + Types::U08 u08; + u08.value = vData.toInt(); + Types::setValue(u08, mData); + return mData; + } + + default: + break; + } + + mData += '\0'; + return mData; +} + +/*! + * \brief Format::toStringList + * \details Converts the list of of character base items in List of string items. + * \param vList - list of the character base items + * \param vRemoveDuplicate - remove duplicate items if true + * \param vPrefix - add the prefix vPrefix to each item + * \return The QStringList conversion of the character base items vList. + */ +QStringList Format::toStringList(const QList vList, bool vRemoveDuplicate, QString vPrefix) +{ + QStringList list; + for (const auto &listItem : vList) { + auto item = vPrefix + listItem; + if ( vRemoveDuplicate ) { + if ( ! list.contains(item) ) { + list += item; + } + } + else + list += item; + } + return list; +} + +/*! + * \brief Format::fromEpoch + * \details Converts the epoch (in seconds) to the date string format, formatted by vDateTimeFormat + * \param vEpoch - epoch + * \param vFormat - returned date time format + * \return the date time representation of the epoch in QString by vFormat. + */ +QString Format::fromEpoch(qint64 vEpoch, QString vFormat) +{ + if ( ! vEpoch ) return ""; + QDateTime dateTime = QDateTime::fromSecsSinceEpoch(vEpoch); + return dateTime.toString(vFormat); +} + +/*! + * \brief Format::fromVariantList + * \param vData - QVariantList data to be converted to the QSteingList + * \return the QStringList conversion of the vData + */ +QStringList Format::fromVariantList(const QVariantList &vData) +{ + QStringList data; + + // convert the data to string list + for (auto datum : vData) { + data += datum.toString(); + } + return data; +} Index: lib/MsgUtils/src/types.cpp =================================================================== diff -u --- lib/MsgUtils/src/types.cpp (revision 0) +++ lib/MsgUtils/src/types.cpp (revision cfc0df719cb5033078d0cac45ce0f6243810f2e7) @@ -0,0 +1,53 @@ +/*! + * + * Copyright (c) 2019-2024 Diality Inc. - All Rights Reserved. + * \copyright + * THIS CODE MAY NOT BE COPIED OR REPRODUCED IN ANY FORM, IN PART OR IN + * WHOLE, WITHOUT THE EXPLICIT PERMISSION OF THE COPYRIGHT OWNER. + * + * \file types.cpp + * \author (last) Behrouz NematiPour + * \date (last) 28-Mar-2023 + * \author (original) Behrouz NematiPour + * \date (original) 26-Dec-2019 + * + */ +#include "types.h" + +// Qt + +// Project + +/*! + * \brief Types::floatCompare + * \details compares two floats with a tolerance. + * \param f1 - float value 1 + * \param f2 - float value 2 + * \return true if (almost) equal + */ +bool Types::floatCompare(float f1, float f2) { + static constexpr auto epsilon = 1.0e-05f; + if (qAbs(f1 - f2) <= epsilon) + return true; + return qAbs(f1 - f2) <= epsilon * qMax(qAbs(f1), qAbs(f2)); +} + +/*! + * \brief Types::getBits + * \details converts the array of byte to array of bits + * \param vData - the array of bytes + * \param vStartIndex - start index of the array to be used for conversion + * \param vFlags - the array of bits + * \param vLen - the amounts of bits to be converted + * \return false if there is not enough data available from start index vStartIndex to the length of vLen. + */ +bool Types::getBits(const QByteArray &vData, int &vStartIndex, QBitArray &vFlags, int vLen) { + vFlags.clear(); + QByteArray data = vData.mid(vStartIndex, vLen); + quint8 bytes = vLen / 8; + vStartIndex = vLen % 8 ? bytes + 1 : bytes ; //CEILING DIVISION TO ROUND UP TO LATEST BIT + if ( data.length() * 8 < vLen ) + return false; + vFlags = QBitArray::fromBits(data, vLen); + return true; +} Index: tools/.gitignore =================================================================== diff -u --- tools/.gitignore (revision 0) +++ tools/.gitignore (revision cfc0df719cb5033078d0cac45ce0f6243810f2e7) @@ -0,0 +1 @@ +bin/ Index: tools/CANDumpPlayer/CMakeLists.txt =================================================================== diff -u --- tools/CANDumpPlayer/CMakeLists.txt (revision 0) +++ tools/CANDumpPlayer/CMakeLists.txt (revision cfc0df719cb5033078d0cac45ce0f6243810f2e7) @@ -0,0 +1,43 @@ +cmake_minimum_required(VERSION 3.16) + +project(CANDumpPlayer LANGUAGES CXX) + +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(CMAKE_DISABLE_IN_SOURCE_BUILD ON) + +set(TOOLS_BIN ${CMAKE_CURRENT_SOURCE_DIR}/../bin) +file(MAKE_DIRECTORY ${TOOLS_BIN}) + +find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Core) +find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Core SerialBus) + +set(INCLUDES +) + +set(SRCS + main.cpp +) + +add_executable(${PROJECT_NAME} + ${INCLUDES} ${SRCS} +) + +target_link_libraries(${PROJECT_NAME} PRIVATE + Qt${QT_VERSION_MAJOR}::SerialBus + Qt${QT_VERSION_MAJOR}::Core +) + +set_target_properties(${PROJECT_NAME} PROPERTIES + RUNTIME_OUTPUT_DIRECTORY ${TOOLS_BIN} + ADDITIONAL_CLEAN_FILES ${TOOLS_BIN} +) + +install(TARGETS ${PROJECT_NAME} + BUNDLE DESTINATION . + RUNTIME DESTINATION ${TOOLS_BIN} + # RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + LIBRARY DESTINATION ${CMAKE_CURRENT_SOURCE_DIR}/lib +) Index: tools/CANDumpPlayer/main.cpp =================================================================== diff -u --- tools/CANDumpPlayer/main.cpp (revision 0) +++ tools/CANDumpPlayer/main.cpp (revision cfc0df719cb5033078d0cac45ce0f6243810f2e7) @@ -0,0 +1,96 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +int main(int argc, char *argv[]) +{ + QCoreApplication app(argc, argv); + app.setApplicationName("CANDUmpPlayer"); + app.setApplicationVersion("1.0"); + + QCommandLineParser parser; + parser.addHelpOption(); + parser.addVersionOption(); + parser.addPositionalArgument("can_interface", "CAN device"); + parser.addPositionalArgument("candump_file", "Input CAN dump file"); + parser.process(app); + + const QStringList args = parser.positionalArguments(); + + if (args.length() != 2) { + qCritical().noquote() << Qt::endl + << QString("ERROR: incorrect number of arguments (expected 2, but received %1)").arg(args.length()) << Qt::endl; + parser.showHelp(1); + return 1; + } + + QString error; + QSharedPointer can_device(QCanBus::instance()->createDevice(QStringLiteral("socketcan"), args.at(0), &error)); + if (can_device == nullptr) { + qCritical().noquote() << QString("ERROR: could not open CAN device %1 (error=%2)").arg(args.at(0)).arg(error); + return 1; + } + can_device->setConfigurationParameter(QCanBusDevice::CanFdKey, false); + can_device->setConfigurationParameter(QCanBusDevice::BitRateKey, 250000); + can_device->connectDevice(); + + QFile can_file(args.at(1)); + if (can_file.open(QIODevice::ReadOnly | QIODevice::Text) == false) { + qCritical().noquote() << QString("ERROR: could not open input CAN dump file %1").arg(args.at(1)); + return 1; + } + + QTextStream stream(&can_file); + QString line; + const QRegularExpression regexEntry("^\\s*\\((?.+)\\)\\s+(?\\S+)\\s+(?\\S+)\\s+\\[(?\\S+)\\]\\s+(?.*)$"); + const QRegularExpression regexPayload("(?:\\s*)(\\S+)"); + unsigned int lineCount = 1; + while (stream.readLineInto(&line)) { + auto match = regexEntry.match(line); + if (match.hasMatch()) { + // qDebug().noquote() << " timestamp=" << match.captured(QStringLiteral("timestamp")); + // qDebug().noquote() << " device=" << match.captured(QStringLiteral("device")); + // qDebug().noquote() << " can_id=" << match.captured(QStringLiteral("can_id")); + // qDebug().noquote() << " size=" << match.captured(QStringLiteral("size")); + // qDebug().noquote() << " payload=" << match.captured(QStringLiteral("payload")); + + QByteArray payload; + auto it = regexPayload.globalMatch(match.captured(QStringLiteral("payload"))); + while (it.hasNext()) { + auto payloadMatch = it.next(); + payload.append(payloadMatch.captured(1).toUInt(nullptr, 16)); + } + + const unsigned int can_id = match.captured(QStringLiteral("can_id")).toUInt(nullptr, 16); + QCanBusFrame frame(can_id, payload); + can_device->writeFrame(frame); + } + else { + qWarning().noquote() << QString("WARNING: \"%1\" (line %2) did not match expected format").arg(line).arg(lineCount); + } + lineCount++; + } + + can_file.close(); + can_device->disconnectDevice(); + + // Set up code that uses the Qt event loop here. + // Call a.quit() or a.exit() to quit the application. + // A not very useful example would be including + // #include + // near the top of the file and calling + // QTimer::singleShot(5000, &a, &QCoreApplication::quit); + // which quits the application after 5 seconds. + + // If you do not need a running Qt event loop, remove the call + // to a.exec() or use the Non-Qt Plain C++ Application template. + + return 0; +} Index: tools/CMakeLists.txt =================================================================== diff -u --- tools/CMakeLists.txt (revision 0) +++ tools/CMakeLists.txt (revision cfc0df719cb5033078d0cac45ce0f6243810f2e7) @@ -0,0 +1 @@ +add_subdirectory(CANDumpPlayer)