Index: LeahiRt/LeahiRtController.cpp =================================================================== diff -u -r808df723cf9d1b0097796815e5229abfa18f2d8f -ra52c25b8231e90a9de0421db171cde2336007733 --- LeahiRt/LeahiRtController.cpp (.../LeahiRtController.cpp) (revision 808df723cf9d1b0097796815e5229abfa18f2d8f) +++ LeahiRt/LeahiRtController.cpp (.../LeahiRtController.cpp) (revision a52c25b8231e90a9de0421db171cde2336007733) @@ -14,7 +14,7 @@ #include #include "LeahiRtController.h" -#include "LeahiMsgDefs.h" +#include "LeahiMsgProtoUtils.h" /*! * \brief LeahiRtController::LeahiRtController Index: LeahiRt/main.cpp =================================================================== diff -u -r864d025811eb43fd2592bde174e5cb41fa10d783 -ra52c25b8231e90a9de0421db171cde2336007733 --- LeahiRt/main.cpp (.../main.cpp) (revision 864d025811eb43fd2592bde174e5cb41fa10d783) +++ LeahiRt/main.cpp (.../main.cpp) (revision a52c25b8231e90a9de0421db171cde2336007733) @@ -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 -ra52c25b8231e90a9de0421db171cde2336007733 Binary files differ Index: lib/Comms/CMakeLists.txt =================================================================== diff -u -rdfe875a4159529de072df08b6c500707e9312d96 -ra52c25b8231e90a9de0421db171cde2336007733 --- lib/Comms/CMakeLists.txt (.../CMakeLists.txt) (revision dfe875a4159529de072df08b6c500707e9312d96) +++ lib/Comms/CMakeLists.txt (.../CMakeLists.txt) (revision a52c25b8231e90a9de0421db171cde2336007733) @@ -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 a52c25b8231e90a9de0421db171cde2336007733 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 -r808df723cf9d1b0097796815e5229abfa18f2d8f -ra52c25b8231e90a9de0421db171cde2336007733 --- lib/Comms/src/AgentInterface.cpp (.../AgentInterface.cpp) (revision 808df723cf9d1b0097796815e5229abfa18f2d8f) +++ lib/Comms/src/AgentInterface.cpp (.../AgentInterface.cpp) (revision a52c25b8231e90a9de0421db171cde2336007733) @@ -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 a52c25b8231e90a9de0421db171cde2336007733 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 -r808df723cf9d1b0097796815e5229abfa18f2d8f -ra52c25b8231e90a9de0421db171cde2336007733 --- lib/MsgUtils/CMakeLists.txt (.../CMakeLists.txt) (revision 808df723cf9d1b0097796815e5229abfa18f2d8f) +++ lib/MsgUtils/CMakeLists.txt (.../CMakeLists.txt) (revision a52c25b8231e90a9de0421db171cde2336007733) @@ -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 -r5d7e5fb0ae59ff93b2c5756ade0093eb2d5fe41f -ra52c25b8231e90a9de0421db171cde2336007733 --- lib/MsgUtils/cmake/MsgUtils.cmake (.../MsgUtils.cmake) (revision 5d7e5fb0ae59ff93b2c5756ade0093eb2d5fe41f) +++ lib/MsgUtils/cmake/MsgUtils.cmake (.../MsgUtils.cmake) (revision a52c25b8231e90a9de0421db171cde2336007733) @@ -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: scripts/MsgUtils/GenerateMsgDefsCpp.py =================================================================== diff -u -rfd9c43b00259567ff49a3d9b31b98df75b47bc52 -ra52c25b8231e90a9de0421db171cde2336007733 --- scripts/MsgUtils/GenerateMsgDefsCpp.py (.../GenerateMsgDefsCpp.py) (revision fd9c43b00259567ff49a3d9b31b98df75b47bc52) +++ scripts/MsgUtils/GenerateMsgDefsCpp.py (.../GenerateMsgDefsCpp.py) (revision a52c25b8231e90a9de0421db171cde2336007733) @@ -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 -rfd9c43b00259567ff49a3d9b31b98df75b47bc52 -ra52c25b8231e90a9de0421db171cde2336007733 --- scripts/MsgUtils/msgutils/MsgCpp.py (.../MsgCpp.py) (revision fd9c43b00259567ff49a3d9b31b98df75b47bc52) +++ scripts/MsgUtils/msgutils/MsgCpp.py (.../MsgCpp.py) (revision a52c25b8231e90a9de0421db171cde2336007733) @@ -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 -r808df723cf9d1b0097796815e5229abfa18f2d8f -ra52c25b8231e90a9de0421db171cde2336007733 --- scripts/MsgUtils/msgutils/templates/MsgDefs_cpp.jinja (.../MsgDefs_cpp.jinja) (revision 808df723cf9d1b0097796815e5229abfa18f2d8f) +++ scripts/MsgUtils/msgutils/templates/MsgDefs_cpp.jinja (.../MsgDefs_cpp.jinja) (revision a52c25b8231e90a9de0421db171cde2336007733) @@ -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 -r808df723cf9d1b0097796815e5229abfa18f2d8f -ra52c25b8231e90a9de0421db171cde2336007733 --- scripts/MsgUtils/msgutils/templates/MsgDefs_h.jinja (.../MsgDefs_h.jinja) (revision 808df723cf9d1b0097796815e5229abfa18f2d8f) +++ scripts/MsgUtils/msgutils/templates/MsgDefs_h.jinja (.../MsgDefs_h.jinja) (revision a52c25b8231e90a9de0421db171cde2336007733) @@ -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: tools/AgentSim/AgentSimController.cpp =================================================================== diff -u -r22efa5e0d67f4046f9f91c5c3cab9a0a95d9a17f -ra52c25b8231e90a9de0421db171cde2336007733 --- tools/AgentSim/AgentSimController.cpp (.../AgentSimController.cpp) (revision 22efa5e0d67f4046f9f91c5c3cab9a0a95d9a17f) +++ tools/AgentSim/AgentSimController.cpp (.../AgentSimController.cpp) (revision a52c25b8231e90a9de0421db171cde2336007733) @@ -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 -r22efa5e0d67f4046f9f91c5c3cab9a0a95d9a17f -ra52c25b8231e90a9de0421db171cde2336007733 --- tools/AgentSim/AgentSimController.h (.../AgentSimController.h) (revision 22efa5e0d67f4046f9f91c5c3cab9a0a95d9a17f) +++ tools/AgentSim/AgentSimController.h (.../AgentSimController.h) (revision a52c25b8231e90a9de0421db171cde2336007733) @@ -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 -rfd9c43b00259567ff49a3d9b31b98df75b47bc52 -ra52c25b8231e90a9de0421db171cde2336007733 --- tools/CMakeLists.txt (.../CMakeLists.txt) (revision fd9c43b00259567ff49a3d9b31b98df75b47bc52) +++ tools/CMakeLists.txt (.../CMakeLists.txt) (revision a52c25b8231e90a9de0421db171cde2336007733) @@ -1,3 +1,4 @@ +add_subdirectory(AgentSim) if(UNIX AND NOT APPLE) add_subdirectory(CANDumpPlayer) endif()