Index: LeahiRt/LeahiRtController.cpp =================================================================== diff -u -r088513e6ea7bad08b4fb7862127c726eabad18fd -r402926738e7394ee2d3dc7add2e6d755f06a289d --- LeahiRt/LeahiRtController.cpp (.../LeahiRtController.cpp) (revision 088513e6ea7bad08b4fb7862127c726eabad18fd) +++ LeahiRt/LeahiRtController.cpp (.../LeahiRtController.cpp) (revision 402926738e7394ee2d3dc7add2e6d755f06a289d) @@ -14,7 +14,7 @@ #include #include "LeahiRtController.h" -#include "LeahiMsgDefs.h" +#include "LeahiMsgProtoUtils.h" /*! * \brief LeahiRtController::LeahiRtController Index: LeahiRt/main.cpp =================================================================== diff -u -r2c00c6e743844c9a71fa03ce5a5c436ef3836484 -r402926738e7394ee2d3dc7add2e6d755f06a289d --- LeahiRt/main.cpp (.../main.cpp) (revision 2c00c6e743844c9a71fa03ce5a5c436ef3836484) +++ LeahiRt/main.cpp (.../main.cpp) (revision 402926738e7394ee2d3dc7add2e6d755f06a289d) @@ -1,49 +1,57 @@ +#include +#include + #include #include #include #include #include -#include +#include #include "LeahiRtController.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); + // Block SIGINT and SIGTERM from normal delivery; redirect them to a fd. + sigset_t mask; + sigemptyset(&mask); + sigaddset(&mask, SIGINT); + sigaddset(&mask, SIGTERM); + sigprocmask(SIG_BLOCK, &mask, nullptr); + const int sfd = signalfd(-1, &mask, SFD_CLOEXEC); + if (sfd == -1) { + qCritical("Failed to create signalfd"); + return 1; + } + QCoreApplication app(argc, argv); app.setApplicationName("LeahiRt"); app.setApplicationVersion("1.0"); - + + // Notifier runs on the main thread inside the event loop — safe to call quit(). + QSocketNotifier notifier(sfd, QSocketNotifier::Read); + QObject::connect(¬ifier, &QSocketNotifier::activated, [&](int fd) { + struct signalfd_siginfo info{}; + (void)read(fd, &info, sizeof(info)); + if (info.ssi_signo == SIGINT) { + log << "Application terminated by SIGINT"; + } + else if (info.ssi_signo == SIGTERM) { + log << "Application terminated by SIGTERM"; + } + app.quit(); + }); + QCommandLineParser parser; parser.setApplicationDescription("Leahi Real-time Cloud Data Transmission daemon."); parser.addHelpOption(); parser.addVersionOption(); QCommandLineOption configOption( - {"c", "config"}, - "Path to the configuration INI file.", - "config", + {"c", "config"}, "Path to the configuration INI file.", "config", QDir(app.applicationDirPath()).filePath("config/LeahiRt.ini") ); parser.addOption(configOption); Index: SDDs/CloudSyncRt.png =================================================================== diff -u -rcfc0df719cb5033078d0cac45ce0f6243810f2e7 -r402926738e7394ee2d3dc7add2e6d755f06a289d Binary files differ Index: lib/Comms/CMakeLists.txt =================================================================== diff -u -rac303b902c681a25ff0d910dd56ab309669e381f -r402926738e7394ee2d3dc7add2e6d755f06a289d --- lib/Comms/CMakeLists.txt (.../CMakeLists.txt) (revision ac303b902c681a25ff0d910dd56ab309669e381f) +++ lib/Comms/CMakeLists.txt (.../CMakeLists.txt) (revision 402926738e7394ee2d3dc7add2e6d755f06a289d) @@ -12,13 +12,11 @@ set(INCLUDES include/AgentInterface.h include/CanInterface.h - include/ProtoInterface.h ) set(SRCS src/AgentInterface.cpp src/CanInterface.cpp - src/ProtoInterface.cpp ) add_library(${PROJECT_NAME} SHARED) Fisheye: Tag 402926738e7394ee2d3dc7add2e6d755f06a289d refers to a dead (removed) revision in file `lib/Comms/include/ProtoInterface.h'. Fisheye: No comparison available. Pass `N' to diff? Index: lib/Comms/src/AgentInterface.cpp =================================================================== diff -u -r088513e6ea7bad08b4fb7862127c726eabad18fd -r402926738e7394ee2d3dc7add2e6d755f06a289d --- lib/Comms/src/AgentInterface.cpp (.../AgentInterface.cpp) (revision 088513e6ea7bad08b4fb7862127c726eabad18fd) +++ lib/Comms/src/AgentInterface.cpp (.../AgentInterface.cpp) (revision 402926738e7394ee2d3dc7add2e6d755f06a289d) @@ -88,11 +88,6 @@ return false; } const QByteArray frame = AgentMessage::build(msgId, sequence, payload); - QString output; - for (auto i = 0; i < frame.size(); i++) { - output += QStringLiteral(" 0x") + QString("%1").arg(uint8_t(frame.at(i)), 2, 16, QChar('0')).toUpper(); - } - qDebug().noquote() << QString("frame =%1").arg(output); _socket.write(frame); _socket.flush(); return true; Fisheye: Tag 402926738e7394ee2d3dc7add2e6d755f06a289d refers to a dead (removed) revision in file `lib/Comms/src/ProtoInterface.cpp'. Fisheye: No comparison available. Pass `N' to diff? Index: lib/MsgUtils/CMakeLists.txt =================================================================== diff -u -r088513e6ea7bad08b4fb7862127c726eabad18fd -r402926738e7394ee2d3dc7add2e6d755f06a289d --- lib/MsgUtils/CMakeLists.txt (.../CMakeLists.txt) (revision 088513e6ea7bad08b4fb7862127c726eabad18fd) +++ lib/MsgUtils/CMakeLists.txt (.../CMakeLists.txt) (revision 402926738e7394ee2d3dc7add2e6d755f06a289d) @@ -60,15 +60,19 @@ set(GENERATED_INCLUDES # include/DenaliMsgDefs.h # include/DenaliMsgDefs.pb.h + # include/DenaliMsgProtoUtils.h include/LeahiMsgDefs.h include/LeahiMsgDefs.pb.h + include/LeahiMsgProtoUtils.h ) set(GENERATED_SRCS # src/DenaliMsgDefs.cpp # src/DenaliMsgDefs.pb.cc + # src/DenaliMsgProtoUtils.cpp src/LeahiMsgDefs.cpp src/LeahiMsgDefs.pb.cc + src/LeahiMsgProtoUtils.cpp ) # generate_msg_defs_cpp(DENALI_MSG_CSV Denali ${CMAKE_CURRENT_SOURCE_DIR}/include ${CMAKE_CURRENT_SOURCE_DIR}/src denali) Index: lib/MsgUtils/cmake/MsgUtils.cmake =================================================================== diff -u -r6cdf791210cfa0d96514094d33510e639f9bc0b6 -r402926738e7394ee2d3dc7add2e6d755f06a289d --- lib/MsgUtils/cmake/MsgUtils.cmake (.../MsgUtils.cmake) (revision 6cdf791210cfa0d96514094d33510e639f9bc0b6) +++ lib/MsgUtils/cmake/MsgUtils.cmake (.../MsgUtils.cmake) (revision 402926738e7394ee2d3dc7add2e6d755f06a289d) @@ -17,6 +17,10 @@ # cmake >= 3.20: cmake_path(ABSOLUTE_PATH _source_path) string(JOIN "/" _source_path ${_source_dir} ${_device_name}MsgDefs.cpp) + # protobuf utility files (struct <-> protobuf conversions) generated alongside the structs + string(JOIN "/" _proto_utils_header_path ${_header_dir} ${_device_name}MsgProtoUtils.h) + string(JOIN "/" _proto_utils_source_path ${_source_dir} ${_device_name}MsgProtoUtils.cpp) + # generate a pretty string containing a list of the relative paths of all csv files foreach(_path ${${_input_confs}}) # cmake >= 3.20: cmake_path(ABSOLUTE_PATH _path) @@ -32,15 +36,20 @@ ${MSGUTILS_SCRIPTS_DIR}/GenerateMsgDefsCpp.py ${MSGUTILS_SCRIPTS_DIR}/msgutils/templates/MsgDefs_h.jinja ${MSGUTILS_SCRIPTS_DIR}/msgutils/templates/MsgDefs_cpp.jinja + ${MSGUTILS_SCRIPTS_DIR}/msgutils/templates/MsgProtoUtils_h.jinja + ${MSGUTILS_SCRIPTS_DIR}/msgutils/templates/MsgProtoUtils_cpp.jinja ${${_input_confs}} OUTPUT ${_header_path} ${_source_path} + ${_proto_utils_header_path} + ${_proto_utils_source_path} COMMAND ${PROJECT_PYTHON} ${MSGUTILS_SCRIPTS_DIR}/GenerateMsgDefsCpp.py --namespace ${_namespace} --header_dir ${_header_dir} --source_dir ${_source_dir} + --proto ${${_input_confs}} ${_device_name} COMMENT "Generating MsgDefs C++ header ${_header_path} and source ${_source_path} from input ${_file_list}" Index: lib/MsgUtils/src/MessageDispatcher.cpp =================================================================== diff -u --- lib/MsgUtils/src/MessageDispatcher.cpp (revision 0) +++ lib/MsgUtils/src/MessageDispatcher.cpp (revision 402926738e7394ee2d3dc7add2e6d755f06a289d) @@ -0,0 +1,244 @@ +/*! + * + * Copyright (c) 2020-2026 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 MessageDispatcher.cpp + * \author (last) Stephen Quong + * \date (last) 12-Jun-2026 + * \author (original) Behrouz NematiPour + * \date (original) 26-Aug-2020 + * + */ +// SQ Ported from luis application/sources/canbus/MessageDispatcher.cpp. +// SQ Only the inbound per-CAN-id frame reassembly path is kept active; the +// SQ GUI/action/acknowledge machinery is commented out (not deleted). +#include "MessageDispatcher.h" + +// Qt +#include // SQ added: replaces luis Logger.h macros with qDebug() +// #include // SQ commented out: thread mgmt removed +// #include // SQ commented out: no QApplication here + +// Project +// #include "Logger.h" // SQ commented out: luis logging infra +// #include "ApplicationController.h" // SQ commented out: GUI app controller +// #include "FrameInterface.h" // SQ commented out: luis frame interface +// #include "MessageAcknowModel.h" // SQ commented out: acknowledge model + +//#define DEBUG_ACKBACK_HD_TO_UI +//#define DEBUG_OUT_OF_SYNC + +using namespace Can; + +/*! + * \brief MessageDispatcher::MessageDispatcher + * \details Constructor + * \param parent - QObject parent owner object. + * Qt handles the children destruction by their parent objects life-cycle. + */ +MessageDispatcher::MessageDispatcher(QObject *parent) : QObject(parent) { } + +// SQ commented out: init()/quit() thread lifecycle -- LeahiRtController owns threading. +#if 0 +/*! + * \brief Message Handler initializer + */ +bool MessageDispatcher::init() +{ + if ( _init ) return false; + _init = true; + + // runs in the thread + initConnections(); + + LOG_DEBUG(QString("%1 Initialized").arg(metaObject()->className())); + + return true; +} + +/*! + * \brief MessageDispatcher::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 MessageDispatcher::init(QThread &vThread) +{ + if ( ! init() ) return false; + initThread(vThread); + return true; +} + +/*! + * \brief MessageDispatcher::quit + * \details quits the class + * Calls quitThread + */ +void MessageDispatcher::quit() +{ + quitThread(); // validated +} + +/*! + * \brief Message Handler connections definition + * \details Initializes the required signal/slot connection between this class and other objects + * to be able to communicate. + */ +void MessageDispatcher::initConnections() +{ + // From GUI + connect(&_ApplicationController, SIGNAL(didActionTransmit(GuiActionType , const QVariantList &)), + this , SLOT( onActionTransmit(GuiActionType , const QVariantList &))); + + // From HD + connect(&_FrameInterface , SIGNAL(didFrameReceive (Can_Id , const QByteArray &)), + this , SLOT( onFrameReceive (Can_Id , const QByteArray &))); + + // From Message Acknow Model timer timeout. + connect(&_MessageAcknowModel , SIGNAL(didFramesTransmit(Can_Id, Sequence, const FrameList &)), + this , SLOT( onFramesTransmit(Can_Id, Sequence, const FrameList &))); + + connect(&_MessageAcknowModel , SIGNAL(didFailedTransmit( Sequence )), + this , SLOT( onFailedTransmit( Sequence ))); + + // Application Settings are ready + connect(&_ApplicationController, SIGNAL(didSettingsDone ()), + this , SLOT( onSettingsDone ())); + + // ---- Signal/Slots + ADJUST_TRANSMT_MODEL_BRIDGE_CONNECTIONS(_ApplicationController) + ACTION_RECEIVE_MODEL_BRIDGE_CONNECTIONS(_interpreter ) +} + +/*! + * \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 MessageDispatcher::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 MessageDispatcher::quitThread + * \details Moves this object to main thread to be handled by QApplication + * And to be destroyed there. + */ +void MessageDispatcher::quitThread() +{ + if ( ! _thread ) return; + + // runs in thread + moveToThread(qApp->thread()); // validated +} +#endif + +/*! + * \brief MessageDispatcher::onFrameReceive + * \details Upon message has been received over CANBus this slot will be called + * by FrameInterface::didFrameReceive signal to process the frame + * Upon completion of collected all the required frames + * on successful interpretation of the message, emits didActionReceived signal. + * The message will be removed from list of the channel vCanId messages. + * \param vCanId - CANBus channel of the frame // SQ Can_Id -> CanId + * \param vPayload - Payload of the frame + */ +void MessageDispatcher::onFrameReceive(CanId vCanId, const QByteArray &vPayload) // SQ Can_Id -> CanId +{ + // Append a message to the list + // because if the list is empty there is no last() item + if (_messageList[vCanId].isEmpty() || _messageList[vCanId].last().isComplete()) { + _messageList[vCanId].append(Message()); + } + + // build the message and check. + if (! buildMessage(vCanId, vPayload)) { + return; + } + Message mMessage = _messageList[vCanId].last(); + + // TODO : must be moved to a MessageModel class + if (mMessage.isComplete()) { + rxCount(); + #ifdef DEBUG_OUT_OF_SYNC + if (_rxSequence != mMessage.sequence) { + qDebug() << tr("Out of Sync : %1 , %2").arg(_rxSequence).arg(mMessage.sequence); + } + #endif + // interpretMessage(mMessage); // SQ commented out: GUI interpretation + emit didActionReceive(mMessage); // SQ emit completed message for the controller to forward + _messageList[vCanId].removeLast(); // SQ done with this message (luis did this inside interpretMessage) + } +} + +// SQ commented out: outbound transmit, action, settings, adjustment, acknowledge, and +// SQ interpret paths -- all GUI/action specific and rely on Message::actionId / GuiActionType +// SQ / ApplicationController / MessageInterpreter, none of which exist in this project. +#if 0 +/*! + * \brief MessageDispatcher::onFramesTransmit + * \details this slots calls the framesTransmit to emit the didFrameTransmit signal + * to queue the frame(s) to be sent + * \param vSequence - sequence of the message which is going to be resent. (not used) + * \param vFrameList - frame(s) to be sent + */ +void MessageDispatcher::onFramesTransmit(Can_Id vCan_Id, Sequence vSequence, const FrameList &vFrameList) +{ + Q_UNUSED(vSequence) + framesTransmit(vCan_Id, vFrameList); +} + +// ... (luis onFailedTransmit / onActionTransmit / onSettingsDone / ~60 onAdjustment overloads, +// actionTransmit / framesTransmit / interpretMessage / checkAcknowReceived / +// checkAcknowTransmit / needsAcknow / txCount preserved here in the luis original) ... +#endif + +/*! + * \brief MessageDispatcher::buildMessage + * \details Calls the messageBuilder buildMessage method. + * \param vCanId - CANBus channel of the frame // SQ Can_Id -> CanId + * \param vPayload - Payload of the frame + * \return false on error + */ +bool MessageDispatcher::buildMessage(CanId vCanId, const QByteArray &vPayload) // SQ Can_Id -> CanId +{ + if (vPayload.length() < eLenCanFrame) { + // Each frame has to have exactly 8 (eLenCanFrame) bytes of data and unused bytes should be passed as 00. + qDebug() << QString("Incorrect frame length. Exp:%1,got:%2").arg(eLenCanFrame).arg(vPayload.length()); // SQ LOG_DEBUG -> qDebug + return false; + } + if (! _builder.buildMessage(vPayload, _messageList[vCanId].last(), vCanId)) { + _messageList[vCanId].removeLast(); + return false; + } + return true; +} + +/*! + * \brief MessageDispatcher::rxCount + * \details count received messages up the size of the Sequence type size + * \return message count + */ +Sequence MessageDispatcher::rxCount() +{ + if ( _rxSequence < SEQUENCE_MAX ) { + ++_rxSequence; + } else { + _rxSequence = 1; + } + return _rxSequence; +} Index: scripts/MsgUtils/GenerateMsgDefsCpp.py =================================================================== diff -u -rbde1243eb2ff6af2868f6c6ad0cb4f5760aaf68b -r402926738e7394ee2d3dc7add2e6d755f06a289d --- scripts/MsgUtils/GenerateMsgDefsCpp.py (.../GenerateMsgDefsCpp.py) (revision bde1243eb2ff6af2868f6c6ad0cb4f5760aaf68b) +++ scripts/MsgUtils/GenerateMsgDefsCpp.py (.../GenerateMsgDefsCpp.py) (revision 402926738e7394ee2d3dc7add2e6d755f06a289d) @@ -30,8 +30,11 @@ 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) + msg_cpp.write_msg_defs_header(f"{args.device_name}", args.header_dir, args.namespace) + msg_cpp.write_msg_defs_source(f"{args.device_name}", args.source_dir, args.namespace) + if args.proto: + msg_cpp.write_msg_proto_utils_header(f"{args.device_name}", args.header_dir, args.namespace) + msg_cpp.write_msg_proto_utils_source(f"{args.device_name}", args.source_dir, args.namespace) except Exception as e: print('Error: %s' % e) sys.exit(1) Index: scripts/MsgUtils/msgutils/MsgCpp.py =================================================================== diff -u -rbde1243eb2ff6af2868f6c6ad0cb4f5760aaf68b -r402926738e7394ee2d3dc7add2e6d755f06a289d --- scripts/MsgUtils/msgutils/MsgCpp.py (.../MsgCpp.py) (revision bde1243eb2ff6af2868f6c6ad0cb4f5760aaf68b) +++ scripts/MsgUtils/msgutils/MsgCpp.py (.../MsgCpp.py) (revision 402926738e7394ee2d3dc7add2e6d755f06a289d) @@ -37,14 +37,12 @@ # \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): + def write_msg_defs_header(self, device_name, output_dir, namespace=None): 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") @@ -58,15 +56,13 @@ # \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): + def write_msg_defs_source(self, device_name, output_dir, namespace=None): 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: @@ -75,6 +71,44 @@ print(f"Wrote C++ message definitions source file {srcPath}") + # \brief Write the protobuf utility C++ header file (struct <-> protobuf conversions) + # \param[in] device_name name of the device used to name the output files + # \param[in] output_dir directory where the output header file will be written + # \param[in] namespace namespace to use in the header file, if blank then no namespace + def write_msg_proto_utils_header(self, device_name, output_dir, namespace=None): + env = Environment(loader = FileSystemLoader(f'{Path(__file__).parent.absolute()}/templates')) + template = env.get_template('MsgProtoUtils_h.jinja') + render = template.render({ + 'msg_cpp': self, + 'cpp_namespace': namespace, + 'device_name': device_name + }) + header_path = Path(output_dir).joinpath(f"{device_name}MsgProtoUtils.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 utilities header file {header_path}") + + + # \brief Write the protobuf utility C++ source file (struct <-> protobuf conversions) + # \param[in] device_name name of the device used to name the output files + # \param[in] output_dir directory where the output source file will be written + # \param[in] namespace namespace to use in the source file, if blank then no namespace + def write_msg_proto_utils_source(self, device_name, output_dir, namespace=None): + env = Environment(loader = FileSystemLoader(f'{Path(__file__).parent.absolute()}/templates')) + template = env.get_template('MsgProtoUtils_cpp.jinja') + render = template.render({ + 'msg_cpp': self, + 'msg_proto_utils_header': f"{device_name}MsgProtoUtils.h", + 'cpp_namespace': namespace, + }) + srcPath = Path(output_dir).joinpath(f"{device_name}MsgProtoUtils.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++ protobuf utilities 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() Index: scripts/MsgUtils/msgutils/templates/MsgDefs_cpp.jinja =================================================================== diff -u -r088513e6ea7bad08b4fb7862127c726eabad18fd -r402926738e7394ee2d3dc7add2e6d755f06a289d --- scripts/MsgUtils/msgutils/templates/MsgDefs_cpp.jinja (.../MsgDefs_cpp.jinja) (revision 088513e6ea7bad08b4fb7862127c726eabad18fd) +++ scripts/MsgUtils/msgutils/templates/MsgDefs_cpp.jinja (.../MsgDefs_cpp.jinja) (revision 402926738e7394ee2d3dc7add2e6d755f06a289d) @@ -1,5 +1,4 @@ {%- if msg_defs_header | length -%} -#include #include "{{ msg_defs_header }}" {%- endif %} {%- if cpp_namespace is defined and cpp_namespace is not none %} @@ -8,25 +7,9 @@ { {%- 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 %} @@ -56,27 +39,9 @@ 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 %} @@ -102,47 +67,6 @@ qDebug().noquote() << QString("{{ msg['msg_name'] }}Payload: %1").arg(paramList.count() ? paramList.join(", ") : ""); } {%- endfor %} -{%- if proto is defined %} - -QByteArray canMessageToProtobufByteArray(const QDateTime ×tamp, const QString &deviceSerialNum, const Can::Message &msg) -{ - 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::string out; - (void)proto.SerializeToString(&out); - return QByteArray(out.data(), static_cast(out.size())); - break; - } -{%- endfor %} - default: - qDebug().noquote() << QString("WARNING: MsgId=0x%1 not handled").arg(msg.msgId, 4, 16, QChar('0')); - break; - } - return QByteArray(); -} -{%- endif %} {%- if cpp_namespace is defined and cpp_namespace is not none %} } // namespace {{ cpp_namespace }} Index: scripts/MsgUtils/msgutils/templates/MsgDefs_h.jinja =================================================================== diff -u -r088513e6ea7bad08b4fb7862127c726eabad18fd -r402926738e7394ee2d3dc7add2e6d755f06a289d --- scripts/MsgUtils/msgutils/templates/MsgDefs_h.jinja (.../MsgDefs_h.jinja) (revision 088513e6ea7bad08b4fb7862127c726eabad18fd) +++ scripts/MsgUtils/msgutils/templates/MsgDefs_h.jinja (.../MsgDefs_h.jinja) (revision 402926738e7394ee2d3dc7add2e6d755f06a289d) @@ -10,9 +10,6 @@ #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 %} @@ -73,29 +70,11 @@ {%- 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 proto is defined %} - -QByteArray canMessageToProtobufByteArray(const QDateTime ×tamp, const QString &deviceSerialNumber, const Can::Message &msg); -{%- endif %} {%- if cpp_namespace is defined and cpp_namespace is not none %} } // namespace {{ cpp_namespace }} Index: scripts/MsgUtils/msgutils/templates/MsgProtoUtils_cpp.jinja =================================================================== diff -u --- scripts/MsgUtils/msgutils/templates/MsgProtoUtils_cpp.jinja (revision 0) +++ scripts/MsgUtils/msgutils/templates/MsgProtoUtils_cpp.jinja (revision 402926738e7394ee2d3dc7add2e6d755f06a289d) @@ -0,0 +1,119 @@ +{%- if msg_proto_utils_header | length -%} +#include "{{ msg_proto_utils_header }}" +{%- endif %} + +#include + +#include +{%- if cpp_namespace is defined and cpp_namespace is not none %} + +namespace {{ cpp_namespace }} +{ +{%- endif %} + +// Populate the protobuf Header (field 1) shared by every typed message. +static void updateHeader(messages::Header *header, const QDateTime ×tamp, const QString &deviceSerialNum, quint16 msgId, qint16 sequence) +{ + 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(msgId); + header->set_sequence(sequence); + } +} +{%- for (msg_id_value, msg) in msg_cpp.data.items() %} +{%- set has_union = msg['payload'] | selectattr('type', 'equalto', 'union') | list | length > 0 %} + +// {{ msg['msg_id'] }} ({{ msg['msg_id_hex_string'] }}) +// payload: {{ msg_cpp.field_list(msg_id_value) | join(", ") }} +// serializeProto: msg struct -> QByteArray of serialized protobuf data (header populated from params) +QByteArray serializeProto([[maybe_unused]] const {{ msg['msg_name'] }}Payload &src, const QDateTime ×tamp, const QString &deviceSerialNum, quint16 msgId, qint16 sequence) +{ +{%- if has_union %} + qDebug().noquote() << "WARNING: MsgId={{ msg['msg_name'] }} contains union/oneof field(s); protobuf serialization is partial"; +{%- endif %} + messages::{{ msg['msg_name'] }} proto; +{%- for field in msg['payload'] %} +{%- if field['type'] != "union" %} + // {{ field['type'] ~ "-" ~ field['name'] }} + proto.set_{{ field['name'].lower() }}(src.{{ field['name'] }}.value); +{%- else %} + // TODO: {{ field['type'] ~ "-" ~ field['name'] }} +{%- endif %} +{%- endfor %} + updateHeader(proto.mutable_header(), timestamp, deviceSerialNum, msgId, sequence); + std::string out; + (void)proto.SerializeToString(&out); + return QByteArray(out.data(), static_cast(out.size())); +} + +// {{ msg['msg_id'] }} ({{ msg['msg_id_hex_string'] }}) +// payload: {{ msg_cpp.field_list(msg_id_value) | join(", ") }} +// deserializeProto: QByteArray of serialized protobuf data -> msg struct (false on parse failure) +bool deserializeProto(const QByteArray &bytes, [[maybe_unused]] {{ msg['msg_name'] }}Payload &dst) +{ +{%- if has_union %} + qDebug().noquote() << "WARNING: MsgId={{ msg['msg_name'] }} contains union/oneof field(s); protobuf deserialization is partial"; +{%- endif %} + messages::{{ msg['msg_name'] }} proto; + if (proto.ParseFromArray(bytes.constData(), bytes.size()) == false) { + qDebug().noquote() << "ERROR: could not parse protobuf for MsgId={{ msg['msg_name'] }}"; + return false; + } +{%- for field in msg['payload'] %} +{%- if field['type'] != "union" %} + // {{ field['type'] ~ "-" ~ field['name'] }} + dst.{{ field['name'] }}.value = proto.{{ field['name'].lower() }}(); +{%- else %} + // TODO: {{ field['type'] ~ "-" ~ field['name'] }} +{%- endif %} +{%- endfor %} + return true; +} +{%- endfor %} + +QByteArray canMessageToProtobufByteArray(const QDateTime ×tamp, const QString &deviceSerialNum, const Can::Message &msg) +{ + 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(); + return serializeProto(payload, timestamp, deviceSerialNum, msg.msgId, msg.sequence); + } +{%- endfor %} + default: + qDebug().noquote() << QString("WARNING: MsgId=0x%1 not handled").arg(msg.msgId, 4, 16, QChar('0')); + break; + } + return QByteArray(); +} + +// Maps a msgId to its fully-qualified protobuf message name for descriptor-pool lookup. +const std::string &msgIdToProtoName(quint16 msgId) +{ + static const std::unordered_map names = { +{%- for (msg_id_value, msg) in msg_cpp.data.items() %} +{%- if cpp_namespace is defined and cpp_namespace is not none %} + { {{ msg['msg_id'] }}, "{{ cpp_namespace }}.messages.{{ msg['msg_name'] }}" }, +{%- else %} + { {{ msg['msg_id'] }}, "messages.{{ msg['msg_name'] }}" }, +{%- endif %} +{%- endfor %} + }; + static const std::string empty; + const auto it = names.find(msgId); + return it == names.end() ? empty : it->second; +} +{%- if cpp_namespace is defined and cpp_namespace is not none %} + +} // namespace {{ cpp_namespace }} +{%- endif %} Index: scripts/MsgUtils/msgutils/templates/MsgProtoUtils_h.jinja =================================================================== diff -u --- scripts/MsgUtils/msgutils/templates/MsgProtoUtils_h.jinja (revision 0) +++ scripts/MsgUtils/msgutils/templates/MsgProtoUtils_h.jinja (revision 402926738e7394ee2d3dc7add2e6d755f06a289d) @@ -0,0 +1,37 @@ +#pragma once + +#include + +#include +#include +#include + +#include "CanMessage.h" +#include "{{ device_name }}MsgDefs.h" +#include "{{ device_name }}MsgDefs.pb.h" + +{%- 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() %} + +// {{ msg['msg_id'] }} ({{ msg['msg_id_hex_string'] }}) +// payload: {{ msg_cpp.field_list(msg_id_value) | join(", ") }} +// serializeProto: msg struct -> QByteArray of serialized protobuf data (header populated from params) +// deserializeProto: QByteArray of serialized protobuf data -> msg struct (false on parse failure) +QByteArray serializeProto(const {{ msg['msg_name'] }}Payload &src, const QDateTime ×tamp, const QString &deviceSerialNum, quint16 msgId, qint16 sequence); +bool deserializeProto(const QByteArray &bytes, {{ msg['msg_name'] }}Payload &dst); +{%- endfor %} + +QByteArray canMessageToProtobufByteArray(const QDateTime ×tamp, const QString &deviceSerialNumber, const Can::Message &msg); + +// Maps a msgId to its fully-qualified protobuf message name (e.g. "{{ cpp_namespace if cpp_namespace else 'messages' }}.messages.AlarmStatusData"). +// Returns an empty string if the msgId is unknown. Intended for descriptor-pool lookup +// (DescriptorPool::FindMessageTypeByName) to decode/inspect a message generically. +const std::string &msgIdToProtoName(quint16 msgId); +{%- if cpp_namespace is defined and cpp_namespace is not none %} + +} // namespace {{ cpp_namespace }} +{%- endif %} Index: tools/AgentSim/AgentSimController.cpp =================================================================== diff -u -rb1bab9e8d105f31bca82799af43ed354a4ab2df0 -r402926738e7394ee2d3dc7add2e6d755f06a289d --- tools/AgentSim/AgentSimController.cpp (.../AgentSimController.cpp) (revision b1bab9e8d105f31bca82799af43ed354a4ab2df0) +++ tools/AgentSim/AgentSimController.cpp (.../AgentSimController.cpp) (revision 402926738e7394ee2d3dc7add2e6d755f06a289d) @@ -11,7 +11,12 @@ * */ #include "AgentSimController.h" +#include "LeahiMsgProtoUtils.h" +#include +#include +#include + #include /*! @@ -91,8 +96,7 @@ result = _rxMsg.feed(_rxBuf); switch (result) { case AgentMessage::FeedResult::Complete: - logMessage(_rxMsg.msgId(), _rxMsg.sequence(), _rxMsg.payload()); - sendAck(_rxMsg.sequence()); + handleMessage(_rxMsg); _rxMsg.reset(); break; case AgentMessage::FeedResult::HeaderError: @@ -108,6 +112,61 @@ } /*! + * \brief AgentSimController::handleMessage + * \details Decodes a received frame and prints its typed body as JSON, generically + * via the protobuf descriptor pool (msgId -> message name -> dynamic parse). + * \param msg The complete AgentMessage frame. + */ +void AgentSimController::handleMessage(const AgentMessage &msg) +{ + const QByteArray payload = msg.payload(); + logMessage(msg.msgId(), msg.sequence(), payload); + + // Read the Header (field 1, shared by every typed message) to learn the msgId. + leahi::messages::Envelope envelope; + if (!envelope.ParseFromArray(payload.constData(), payload.size())) { + qWarning().noquote() << "AgentSim: could not parse Envelope header — frame dropped"; + return; + } + const leahi::messages::Header &header = envelope.header(); + + // Identify the concrete message type from the msgId and decode it generically. + const std::string &typeName = leahi::msgIdToProtoName(static_cast(header.msgid())); + if (typeName.empty()) { + qWarning().noquote() << QString("AgentSim: unknown msgId=0x%1 — cannot decode typed body") + .arg(header.msgid(), 4, 16, QChar('0')); + return; + } + + const google::protobuf::Descriptor *desc = + google::protobuf::DescriptorPool::generated_pool()->FindMessageTypeByName(typeName); + if (desc == nullptr) { + qWarning().noquote() << "AgentSim: no descriptor for" << QString::fromStdString(typeName); + return; + } + + google::protobuf::DynamicMessageFactory factory; + std::unique_ptr body(factory.GetPrototype(desc)->New()); + if (!body->ParseFromArray(payload.constData(), payload.size())) { + qWarning().noquote() << "AgentSim: could not parse" << QString::fromStdString(typeName); + return; + } + + std::string json; + google::protobuf::util::JsonPrintOptions opts; + opts.add_whitespace = true; + opts.always_print_primitive_fields = true; + google::protobuf::util::MessageToJsonString(*body, &json, opts); + + // protobuf renders msgId as a decimal integer; rewrite it as 0x + 4-digit hex. + QString jsonStr = QString::fromStdString(json); + jsonStr.replace(QString("\"msgId\": %1").arg(header.msgid()), + QString("\"msgId\": \"0x%1\"").arg(header.msgid(), 4, 16, QChar('0'))); + qDebug().noquote() << QString::fromStdString(typeName) << ":" << Qt::endl + << jsonStr; +} + +/*! * \brief AgentSimController::logMessage * \details Logs the message identifier, sequence number, and payload length. * \param msgId Message identifier from the parsed frame. @@ -121,19 +180,3 @@ .arg(sequence) .arg(payload.size()); } - -/*! - * \brief AgentSimController::sendAck - * \details Builds an Ack frame carrying \p sequence and writes it to the client socket. - * \param sequence Sequence number to echo back in the Ack frame. - */ -void AgentSimController::sendAck(quint16 sequence) -{ - if (!_client || _client->state() != QLocalSocket::ConnectedState) { - return; - } - const QByteArray frame = AgentMessage::build(AgentMessage::MsgId::Ack, sequence); - _client->write(frame); - _client->flush(); - qInfo().noquote() << QString("AgentSim: tx MsgId=Ack seq=%1").arg(sequence); -} Index: tools/AgentSim/AgentSimController.h =================================================================== diff -u -rb1bab9e8d105f31bca82799af43ed354a4ab2df0 -r402926738e7394ee2d3dc7add2e6d755f06a289d --- tools/AgentSim/AgentSimController.h (.../AgentSimController.h) (revision b1bab9e8d105f31bca82799af43ed354a4ab2df0) +++ tools/AgentSim/AgentSimController.h (.../AgentSimController.h) (revision 402926738e7394ee2d3dc7add2e6d755f06a289d) @@ -52,8 +52,8 @@ void onReadyRead(); private: + void handleMessage(const AgentMessage &msg); void logMessage(AgentMessage::MsgId msgId, quint16 sequence, const QByteArray &payload); - void sendAck(quint16 sequence); QSettings _settings; QLocalServer _server; Index: tools/CMakeLists.txt =================================================================== diff -u -rbde1243eb2ff6af2868f6c6ad0cb4f5760aaf68b -r402926738e7394ee2d3dc7add2e6d755f06a289d --- tools/CMakeLists.txt (.../CMakeLists.txt) (revision bde1243eb2ff6af2868f6c6ad0cb4f5760aaf68b) +++ tools/CMakeLists.txt (.../CMakeLists.txt) (revision 402926738e7394ee2d3dc7add2e6d755f06a289d) @@ -1,3 +1,4 @@ +add_subdirectory(AgentSim) if(UNIX AND NOT APPLE) add_subdirectory(CANDumpPlayer) endif()