Index: LeahiRt/CMakeLists.txt =================================================================== diff -u -r1690ea0a578a6b8ce86394c403b12f31202f36a1 -ra3336c0c40f8c14c2f649cc24afcbaf877932c53 --- LeahiRt/CMakeLists.txt (.../CMakeLists.txt) (revision 1690ea0a578a6b8ce86394c403b12f31202f36a1) +++ LeahiRt/CMakeLists.txt (.../CMakeLists.txt) (revision a3336c0c40f8c14c2f649cc24afcbaf877932c53) @@ -27,11 +27,11 @@ main.cpp ) -generate_message_ini(LEAHI_MSG_CONF ${CMAKE_CURRENT_SOURCE_DIR}/config/LeahiRtMessage.ini generate_message_ini) +generate_msg_handling_ini(LEAHI_MSG_CONF ${CMAKE_CURRENT_SOURCE_DIR}/config/LeahiRtMsgHandling.ini generate_msg_handling_ini) add_executable(${PROJECT_NAME}) -add_dependencies(${PROJECT_NAME} generate_message_ini) +add_dependencies(${PROJECT_NAME} generate_msg_handling_ini) target_sources(${PROJECT_NAME} PRIVATE ${INCLUDES} ${SRCS}) Index: LeahiRt/LeahiRtController.cpp =================================================================== diff -u -ra52c25b8231e90a9de0421db171cde2336007733 -ra3336c0c40f8c14c2f649cc24afcbaf877932c53 --- LeahiRt/LeahiRtController.cpp (.../LeahiRtController.cpp) (revision a52c25b8231e90a9de0421db171cde2336007733) +++ LeahiRt/LeahiRtController.cpp (.../LeahiRtController.cpp) (revision a3336c0c40f8c14c2f649cc24afcbaf877932c53) @@ -20,19 +20,23 @@ * \brief LeahiRtController::LeahiRtController * \details Constructor. Starts the CAN interface and wires the frame→dispatcher→ * completed-message pipeline. - * \param configPath - path to the settings file - * \param parent - optional QObject parent + * \param configPath - path to the settings file + * \param msgHandlingPath - path to the message handling INI + * \param parent - optional QObject parent */ -LeahiRtController::LeahiRtController(const QString &configPath, QObject *parent) : +LeahiRtController::LeahiRtController(const QString &configPath, const QString &msgHandlingPath, QObject *parent) : QObject(parent), _settings(configPath, QSettings::IniFormat), _canInterface(), _canThread(this), _dispatcher(this) { + loadMsgHandling(msgHandlingPath); + _canInterface.init(_canThread); connect(&_canInterface, &Can::CanInterface::didFrameReceive, this, &LeahiRtController::onFrameReceive); connect(&_dispatcher, &Can::MessageDispatcher::didActionReceive, this, &LeahiRtController::onMessageReceive); + connect(&_agentInterface, &AgentInterface::didDisconnect, this, &LeahiRtController::onAgentDisconnect); } /*! @@ -61,6 +65,64 @@ } /*! + * \brief LeahiRtController::loadMsgHandling + * \details Parses message handling INI and populates _msgHandling. + * \note Unknown msg action values default to Drop; unknown topic strings default to ClinicalData. + * \param msgHandlingPath - path to the message handling INI file + */ +void LeahiRtController::loadMsgHandling(const QString &msgHandlingPath) +{ + static const QHash actionMap = { + { QStringLiteral("send_always"), MsgAction::SendAlways }, + { QStringLiteral("send_delta"), MsgAction::SendDelta }, + { QStringLiteral("drop"), MsgAction::Drop }, + }; + static const QHash topicMap = { + { QStringLiteral("ClinicalData"), AgentMessage::MsgId::ClinicalData }, + { QStringLiteral("Diagnostic"), AgentMessage::MsgId::Diagnostic }, + { QStringLiteral("Ack"), AgentMessage::MsgId::Ack }, + { QStringLiteral("Alarms"), AgentMessage::MsgId::Alarms }, + { QStringLiteral("Audit"), AgentMessage::MsgId::Audit }, + { QStringLiteral("DeviceLogFile"), AgentMessage::MsgId::DeviceLogFile }, + { QStringLiteral("TreatmentLogFile"), AgentMessage::MsgId::TreatmentLogFile }, + { QStringLiteral("CloudSyncLogFile"), AgentMessage::MsgId::CloudSyncLogFile }, + }; + + QSettings msgHandlingIni(msgHandlingPath, QSettings::IniFormat); + if (msgHandlingIni.status() != QSettings::NoError) { + qWarning().noquote() << "LeahiRt: could not read message handling INI" << msgHandlingPath << "— all messages will be dropped"; + return; + } + + int loaded = 0; + for (const QString &group : msgHandlingIni.childGroups()) { + bool ok = false; + const Can::MsgId msgId = static_cast(group.toUInt(&ok, 16)); + if (!ok) { + continue; + } + + msgHandlingIni.beginGroup(group); + const QString actionStr = msgHandlingIni.value(QStringLiteral("action"), QStringLiteral("drop")).toString().trimmed(); + const QString topicStr = msgHandlingIni.value(QStringLiteral("topic")).toString().trimmed(); + msgHandlingIni.endGroup(); + + MsgHandling msgHandling; + msgHandling.action = actionMap.value(actionStr, MsgAction::Drop); + msgHandling.topic = topicMap.value(topicStr, AgentMessage::MsgId::ClinicalData); + + if (!actionMap.contains(actionStr)) { + qWarning().noquote() << QString("LeahiRt: unknown message action \"%1\" for msgId=0x%2 — defaulting to drop") + .arg(actionStr).arg(msgId, 4, 16, QChar('0')); + } + + _msgHandling.insert(msgId, msgHandling); + loaded++; + } + qInfo().noquote() << QString("LeahiRt: loaded message handling %1 (%2 entries)").arg(msgHandlingPath).arg(loaded); +} + +/*! * \brief LeahiRtController::onFrameReceive * \details Unpacks a CAN frame and feeds it to the dispatcher, which reassembles * multi-frame messages per CAN id. @@ -73,25 +135,51 @@ /*! * \brief LeahiRtController::onMessageReceive - * \details Converts a fully reassembled message to protobuf and forwards it to - * the Agent. + * \details Applies the message handling policy from LeahiRtMsgHandling.ini: drops, + * forwards unconditionally, or forwards only on payload change. Uses the + * section's topic to set the AgentMessage frame msg_id. * \param msg - the reassembled message */ void LeahiRtController::onMessageReceive(const Can::Message &msg) { - Can::Message &cachedMsg = _msgCache[msg.msgId]; - qDebug().noquote() << QString("Received message with MsgId=0x%1").arg(QString("%1").arg(msg.msgId, 4, 16, QChar('0')).toUpper()); + const auto it = _msgHandling.constFind(msg.msgId); + if (it == _msgHandling.constEnd()) { + qInfo().noquote() << QString("LeahiRt: no action defined for %1 (0x%2), dropping") + .arg(leahi::msgIdString(leahi::MsgId(msg.msgId))).arg(msg.msgId, 4, 16, QChar('0')); + return; + } - // only send the message if the data is different - // SQ if (cachedMsg.data != msg.data) { - // SQ if (msg.msgId == leahi::MSG_ID_TD_OP_MODE_DATA) { - const QByteArray payload = leahi::canMessageToProtobufByteArray( - QDateTime::currentDateTime(), - QStringLiteral("test_device"), - msg); - _agentInterface.send(AgentMessage::MsgId::ClinicalData, _txSequence++, payload); - // SQ } - // SQ } - cachedMsg = msg; - msg.dump(); + if (it->action == MsgAction::Drop) { + qInfo().noquote() << QString("LeahiRt: dropping message %1 (0x%2)") + .arg(leahi::msgIdString(leahi::MsgId(msg.msgId))).arg(msg.msgId, 4, 16, QChar('0')); + return; + } + + auto &[received, cachedMsg] = _msgCache[msg.msgId]; + if (it->action == MsgAction::SendDelta && received && + cachedMsg.data.chopped(1) == msg.data.chopped(1)) { + qInfo().noquote() << QString("LeahiRt: received message %1 (0x%2) did not changed from previous, dropping") + .arg(leahi::msgIdString(leahi::MsgId(msg.msgId))).arg(msg.msgId, 4, 16, QChar('0')); + return; + } + + const QByteArray payload = leahi::canMessageToProtobufByteArray( + QDateTime::currentDateTime(), + QStringLiteral("test_device"), + msg); + _agentInterface.send(it->topic, _txSequence++, payload); + received = true; + cachedMsg = msg; } + +/*! + * \brief LeahiRtController::onAgentDisconnect + * \details Resets the received flag on all cache entries so that send_delta + * messages are treated as new on the next connection. + */ +void LeahiRtController::onAgentDisconnect() +{ + for (auto &[received, msg] : _msgCache) { + received = false; + } +} Index: LeahiRt/LeahiRtController.h =================================================================== diff -u -r808df723cf9d1b0097796815e5229abfa18f2d8f -ra3336c0c40f8c14c2f649cc24afcbaf877932c53 --- LeahiRt/LeahiRtController.h (.../LeahiRtController.h) (revision 808df723cf9d1b0097796815e5229abfa18f2d8f) +++ LeahiRt/LeahiRtController.h (.../LeahiRtController.h) (revision a3336c0c40f8c14c2f649cc24afcbaf877932c53) @@ -14,11 +14,13 @@ #include #include +#include #include #include #include #include #include +#include #include "AgentInterface.h" #include "AgentMessage.h" @@ -40,7 +42,7 @@ Q_OBJECT public: - explicit LeahiRtController(const QString &configPath, QObject *parent = nullptr); + explicit LeahiRtController(const QString &configPath, const QString &msgHandlingPath, QObject *parent = nullptr); ~LeahiRtController(); /*! @@ -51,11 +53,31 @@ void connectToAgent(); private: + /*! + * \brief Send message handling policy for a CAN message. + */ + enum class MsgAction { + Drop, ///< Discard the message; do not forward to the Agent. + SendAlways, ///< Forward unconditionally. + SendDelta, ///< Forward only when the payload has changed since the last send. + }; + + /*! + * \brief Message handling entry loaded from message handling INI. + */ + struct MsgHandling { + MsgAction action = MsgAction::Drop; + AgentMessage::MsgId topic = AgentMessage::MsgId::ClinicalData; + }; + + void loadMsgHandling(const QString &msgHandlingPath); + QSettings _settings; Can::CanInterface _canInterface; QThread _canThread; Can::MessageDispatcher _dispatcher; - QMap _msgCache; + QMap> _msgCache; + QHash _msgHandling; AgentInterface _agentInterface; QThread _agentThread; quint16 _txSequence = 0; @@ -79,8 +101,16 @@ /*! * \brief onMessageReceive - * \details Converts a completed message to protobuf and forwards it to the Agent. + * \details Applies the message handling policy: drops, forwards unconditionally, + * or forwards only on payload change, using the INI-configured topic. * \param msg - the reassembled message */ void onMessageReceive(const Can::Message &msg); + + /*! + * \brief onAgentDisconnect + * \details Resets the received flag on all cache entries so that send_delta + * messages are treated as new on the next connection. + */ + void onAgentDisconnect(); }; Index: LeahiRt/main.cpp =================================================================== diff -u -ra52c25b8231e90a9de0421db171cde2336007733 -ra3336c0c40f8c14c2f649cc24afcbaf877932c53 --- LeahiRt/main.cpp (.../main.cpp) (revision a52c25b8231e90a9de0421db171cde2336007733) +++ LeahiRt/main.cpp (.../main.cpp) (revision a3336c0c40f8c14c2f649cc24afcbaf877932c53) @@ -54,10 +54,16 @@ {"c", "config"}, "Path to the configuration INI file.", "config", QDir(app.applicationDirPath()).filePath("config/LeahiRt.ini") ); + QCommandLineOption msgHandlingOption( + {"m", "msg_handling"}, "Path to the message handling INI file.", "msg_handling", + QDir(app.applicationDirPath()).filePath("config/LeahiRtMsgHandling.ini") + ); parser.addOption(configOption); + parser.addOption(msgHandlingOption); parser.process(app); - LeahiRtController rtController(parser.value(configOption)); + LeahiRtController rtController(parser.value(configOption), + parser.value(msgHandlingOption)); rtController.connectToAgent(); return app.exec(); Index: lib/Comms/src/AgentInterface.cpp =================================================================== diff -u -ra52c25b8231e90a9de0421db171cde2336007733 -ra3336c0c40f8c14c2f649cc24afcbaf877932c53 --- lib/Comms/src/AgentInterface.cpp (.../AgentInterface.cpp) (revision a52c25b8231e90a9de0421db171cde2336007733) +++ lib/Comms/src/AgentInterface.cpp (.../AgentInterface.cpp) (revision a3336c0c40f8c14c2f649cc24afcbaf877932c53) @@ -77,7 +77,7 @@ /*! * \brief AgentInterface::send * \details Builds an AgentMessage frame and writes it to the socket. - * \param msgId - message identifier for Agent MQTT routing + * \param msgId - message identifier for Agent MQTT topic * \param sequence - caller-managed sequence number * \param payload - message payload; pass empty for zero-length frames * \return true if written to the socket, false if not connected Index: lib/MsgUtils/cmake/MsgUtils.cmake =================================================================== diff -u -r1690ea0a578a6b8ce86394c403b12f31202f36a1 -ra3336c0c40f8c14c2f649cc24afcbaf877932c53 --- lib/MsgUtils/cmake/MsgUtils.cmake (.../MsgUtils.cmake) (revision 1690ea0a578a6b8ce86394c403b12f31202f36a1) +++ lib/MsgUtils/cmake/MsgUtils.cmake (.../MsgUtils.cmake) (revision a3336c0c40f8c14c2f649cc24afcbaf877932c53) @@ -61,7 +61,7 @@ # \param[in] _input_confs list of message conf files to use to generate the INI # \param[in] _output_ini path to the INI file to create or update # \param[in] _target_name name of the custom target to add -function(generate_message_ini _input_confs _output_ini _target_name) +function(generate_msg_handling_ini _input_confs _output_ini _target_name) find_package(Python3 COMPONENTS Interpreter Development REQUIRED) # cmake >= 3.20: cmake_path(ABSOLUTE_PATH _output_ini) @@ -78,14 +78,14 @@ add_custom_command( DEPENDS ${MSGUTILS_SCRIPTS_DIR}/msgutils/MsgData.py - ${MSGUTILS_SCRIPTS_DIR}/msgutils/MsgIni.py - ${MSGUTILS_SCRIPTS_DIR}/GenerateMsgIni.py - ${MSGUTILS_SCRIPTS_DIR}/msgutils/templates/MsgIni.jinja + ${MSGUTILS_SCRIPTS_DIR}/msgutils/MsgHandlingIni.py + ${MSGUTILS_SCRIPTS_DIR}/GenerateMsgHandlingIni.py + ${MSGUTILS_SCRIPTS_DIR}/msgutils/templates/MsgHandlingIni.jinja ${${_input_confs}} OUTPUT ${_output_ini} COMMAND - ${PROJECT_PYTHON} ${MSGUTILS_SCRIPTS_DIR}/GenerateMsgIni.py + ${PROJECT_PYTHON} ${MSGUTILS_SCRIPTS_DIR}/GenerateMsgHandlingIni.py ${${_input_confs}} ${_output_ini} COMMENT "Generating/updating message INI ${_output_ini} from input ${_file_list}" Index: lib/MsgUtils/include/AgentMessage.h =================================================================== diff -u -r808df723cf9d1b0097796815e5229abfa18f2d8f -ra3336c0c40f8c14c2f649cc24afcbaf877932c53 --- lib/MsgUtils/include/AgentMessage.h (.../AgentMessage.h) (revision 808df723cf9d1b0097796815e5229abfa18f2d8f) +++ lib/MsgUtils/include/AgentMessage.h (.../AgentMessage.h) (revision a3336c0c40f8c14c2f649cc24afcbaf877932c53) @@ -41,7 +41,7 @@ { public: /*! - * \brief MQTT routing identifier carried in every frame header + * \brief MQTT topic identifier carried in every frame header * \details The Connectivity Agent uses this value to determine the MQTT topic. */ enum class MsgId : quint16 { Index: lib/MsgUtils/src/AgentMessage.cpp =================================================================== diff -u -r808df723cf9d1b0097796815e5229abfa18f2d8f -ra3336c0c40f8c14c2f649cc24afcbaf877932c53 --- lib/MsgUtils/src/AgentMessage.cpp (.../AgentMessage.cpp) (revision 808df723cf9d1b0097796815e5229abfa18f2d8f) +++ lib/MsgUtils/src/AgentMessage.cpp (.../AgentMessage.cpp) (revision a3336c0c40f8c14c2f649cc24afcbaf877932c53) @@ -21,7 +21,7 @@ /*! * \brief AgentMessage::build * \details Builds a complete wire-ready frame with header and optional payload CRCs. - * \param msgId - message identifier for Agent MQTT routing + * \param msgId - message identifier for Agent MQTT topic * \param sequence - caller-managed sequence number * \param payload - optional payload; pass empty for zero-length frames (e.g. Ack) * \return complete frame ready to write to the transport Fisheye: Tag a3336c0c40f8c14c2f649cc24afcbaf877932c53 refers to a dead (removed) revision in file `scripts/MsgUtils/GenerateMsgIni.py'. Fisheye: No comparison available. Pass `N' to diff? Fisheye: Tag a3336c0c40f8c14c2f649cc24afcbaf877932c53 refers to a dead (removed) revision in file `scripts/MsgUtils/msgutils/MsgIni.py'. Fisheye: No comparison available. Pass `N' to diff? Index: scripts/MsgUtils/msgutils/__init__.py =================================================================== diff -u -rb3c378d491270fcffbd8aba0effe2a950ced1a22 -ra3336c0c40f8c14c2f649cc24afcbaf877932c53 --- scripts/MsgUtils/msgutils/__init__.py (.../__init__.py) (revision b3c378d491270fcffbd8aba0effe2a950ced1a22) +++ scripts/MsgUtils/msgutils/__init__.py (.../__init__.py) (revision a3336c0c40f8c14c2f649cc24afcbaf877932c53) @@ -1,4 +1,4 @@ from .MsgData import MsgData from .MsgCpp import MsgCpp from .MsgProtobuf import MsgProtobuf -from .MsgIni import MsgIni +from .MsgHandlingIni import MsgHandlingIni Index: scripts/MsgUtils/msgutils/templates/MsgDefs_cpp.jinja =================================================================== diff -u -ra52c25b8231e90a9de0421db171cde2336007733 -ra3336c0c40f8c14c2f649cc24afcbaf877932c53 --- scripts/MsgUtils/msgutils/templates/MsgDefs_cpp.jinja (.../MsgDefs_cpp.jinja) (revision a52c25b8231e90a9de0421db171cde2336007733) +++ scripts/MsgUtils/msgutils/templates/MsgDefs_cpp.jinja (.../MsgDefs_cpp.jinja) (revision a3336c0c40f8c14c2f649cc24afcbaf877932c53) @@ -1,12 +1,24 @@ {%- if msg_defs_header | length -%} #include "{{ msg_defs_header }}" {%- endif %} + +#include {%- if cpp_namespace is defined and cpp_namespace is not none %} namespace {{ cpp_namespace }} { {%- endif %} + +QString msgIdString(const MsgId msgId) +{ + switch (msgId) { {%- for (msg_id_value, msg) in msg_cpp.data.items() %} + case {{ msg['msg_id'] }}: return QStringLiteral("{{ msg['msg_id'] }}"); +{%- endfor %} + default: return QStringLiteral("0x%1").arg(static_cast(msgId), 4, 16, QChar('0')); + } +} +{%- 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(", ") }} Index: scripts/MsgUtils/msgutils/templates/MsgDefs_h.jinja =================================================================== diff -u -ra52c25b8231e90a9de0421db171cde2336007733 -ra3336c0c40f8c14c2f649cc24afcbaf877932c53 --- scripts/MsgUtils/msgutils/templates/MsgDefs_h.jinja (.../MsgDefs_h.jinja) (revision a52c25b8231e90a9de0421db171cde2336007733) +++ scripts/MsgUtils/msgutils/templates/MsgDefs_h.jinja (.../MsgDefs_h.jinja) (revision a3336c0c40f8c14c2f649cc24afcbaf877932c53) @@ -23,6 +23,8 @@ {%- endfor %} }; +QString msgIdString(const MsgId msgId); + enum ParamType { {%- for type in msg_cpp.paramTypes %} {{ type }}, Fisheye: Tag a3336c0c40f8c14c2f649cc24afcbaf877932c53 refers to a dead (removed) revision in file `scripts/MsgUtils/msgutils/templates/MsgIni.jinja'. Fisheye: No comparison available. Pass `N' to diff? Index: scripts/MsgUtils/pyproject.toml =================================================================== diff -u -rb3c378d491270fcffbd8aba0effe2a950ced1a22 -ra3336c0c40f8c14c2f649cc24afcbaf877932c53 --- scripts/MsgUtils/pyproject.toml (.../pyproject.toml) (revision b3c378d491270fcffbd8aba0effe2a950ced1a22) +++ scripts/MsgUtils/pyproject.toml (.../pyproject.toml) (revision a3336c0c40f8c14c2f649cc24afcbaf877932c53) @@ -12,7 +12,7 @@ [project.scripts] GenerateMsgDefsCpp = "GenerateMsgDefsCpp:main" GenerateProtobuf = "GenerateProtobuf:main" -GenerateMsgIni = "GenerateMsgIni:main" +GenerateMsgHandlingIni = "GenerateMsgHandlingIni:main" [tool.setuptools.packages.find] exclude = ["test*"]