Index: LeahiRt/LeahiRtController.cpp =================================================================== diff -u -r402926738e7394ee2d3dc7add2e6d755f06a289d -rccc40a7e73e9ee5d2a5fb56f3f2bea4f8294900f --- LeahiRt/LeahiRtController.cpp (.../LeahiRtController.cpp) (revision 402926738e7394ee2d3dc7add2e6d755f06a289d) +++ LeahiRt/LeahiRtController.cpp (.../LeahiRtController.cpp) (revision ccc40a7e73e9ee5d2a5fb56f3f2bea4f8294900f) @@ -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; + } +}