Index: .qmake.conf =================================================================== diff -u --- .qmake.conf (revision 0) +++ .qmake.conf (revision a5781739bcbe58c754aff8861561495624bc5b67) @@ -0,0 +1,12 @@ +# Project-wide qmake settings +# Loaded automatically for every .pro in this tree, before the .pro is parsed. + +# BUILD_ROOT: the top-level shadow-build directory. +# $$shadowed($$PWD) maps this file's source dir (the repo root) to its build-tree +# counterpart, so all component resolves the same value regardless of depth in the tree. +# Used by codegen.pri to place ONE shared Python venv instead of one per component. +BUILD_ROOT = $$shadowed($$PWD) + +# individual .pro files can also set these options for standalone builds +CONFIG += c++20 +QMAKE_CXXFLAGS += -Wall -Werror -Wextra Index: LeahiRt/LeahiRt.pro =================================================================== diff -u --- LeahiRt/LeahiRt.pro (revision 0) +++ LeahiRt/LeahiRt.pro (revision a5781739bcbe58c754aff8861561495624bc5b67) @@ -0,0 +1,41 @@ +TEMPLATE = app +TARGET = LeahiRt +CONFIG += c++20 moc +QT += core network +QT -= gui + +QMAKE_CXXFLAGS += -Wall -Werror -Wextra + +DESTDIR = $$PWD/../bin + +include($$PWD/../lib/MsgUtils/codegen.pri) +include($$PWD/../lib/MsgUtils/MsgUtils.pri) + +LEAHI_MSG_CONF = $$PWD/../data/LeahiUnhandled.conf + +# Generate/update the Message-handling INI +MSG_HANDLING_INI = $$PWD/config/LeahiRtMsgHandling.ini + +gen_ini.target = $$MSG_HANDLING_INI +gen_ini.depends = \ + $$LEAHI_MSG_CONF \ + $$MSGUTILS_SCRIPTS_DIR/msgutils/MsgData.py \ + $$MSGUTILS_SCRIPTS_DIR/msgutils/MsgHandlingIni.py \ + $$MSGUTILS_SCRIPTS_DIR/GenerateMsgHandlingIni.py \ + $$MSGUTILS_SCRIPTS_DIR/msgutils/templates/MsgHandlingIni.jinja +gen_ini.commands = \ + $$shell_quote($$PROJECT_PYTHON) \ + $$MSGUTILS_SCRIPTS_DIR/GenerateMsgHandlingIni.py \ + $$LEAHI_MSG_CONF \ + $$MSG_HANDLING_INI +QMAKE_EXTRA_TARGETS += gen_ini +PRE_TARGETDEPS += $$MSG_HANDLING_INI + +HEADERS = \ + LeahiRtController.h + +SOURCES = \ + LeahiRtController.cpp \ + main.cpp + +INCLUDEPATH += $$PWD Index: LeahiRt/LeahiRtController.cpp =================================================================== diff -u -r64243101dff61b5c1a40b96ef33080236999acf6 -ra5781739bcbe58c754aff8861561495624bc5b67 --- LeahiRt/LeahiRtController.cpp (.../LeahiRtController.cpp) (revision 64243101dff61b5c1a40b96ef33080236999acf6) +++ LeahiRt/LeahiRtController.cpp (.../LeahiRtController.cpp) (revision a5781739bcbe58c754aff8861561495624bc5b67) @@ -29,14 +29,15 @@ _settings(configPath, QSettings::IniFormat), _canInterface(), _canThread(this), - _dispatcher(this) + _dispatcher(this), + _appServer(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); + connect(&_agentInterface, &RtInterface::didDisconnect, this, &LeahiRtController::onAgentDisconnect); } /*! @@ -54,7 +55,7 @@ /*! * \brief LeahiRtController::connectToAgent - * \details Initialises the AgentInterface using the socket path and reconnect + * \details Initialises the RtInterface using the socket path and reconnect * interval from the settings file. */ void LeahiRtController::connectToAgent() @@ -65,6 +66,18 @@ } /*! + * \brief LeahiRtController::listenForApp + * \details Starts the local-socket server that the Luis application connects to, + * using the app socket path from the settings file. + * \return true if the server bound successfully, false otherwise. + */ +bool LeahiRtController::listenForApp() +{ + const QString appSocketPath = _settings.value("Socket/AppSocketName", "/tmp/leahi_app.sock").toString(); + return _appServer.listen(appSocketPath); +} + +/*! * \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. @@ -77,15 +90,15 @@ { 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 }, + static const QHash topicMap = { + { QStringLiteral("ClinicalData"), RtMessage::MsgId::ClinicalData }, + { QStringLiteral("Diagnostic"), RtMessage::MsgId::Diagnostic }, + { QStringLiteral("Ack"), RtMessage::MsgId::Ack }, + { QStringLiteral("Alarms"), RtMessage::MsgId::Alarms }, + { QStringLiteral("Audit"), RtMessage::MsgId::Audit }, + { QStringLiteral("DeviceLogFile"), RtMessage::MsgId::DeviceLogFile }, + { QStringLiteral("TreatmentLogFile"), RtMessage::MsgId::TreatmentLogFile }, + { QStringLiteral("CloudSyncLogFile"), RtMessage::MsgId::CloudSyncLogFile }, }; QSettings msgHandlingIni(msgHandlingPath, QSettings::IniFormat); @@ -109,7 +122,7 @@ MsgHandling msgHandling; msgHandling.action = actionMap.value(actionStr, MsgAction::Drop); - msgHandling.topic = topicMap.value(topicStr, AgentMessage::MsgId::ClinicalData); + msgHandling.topic = topicMap.value(topicStr, RtMessage::MsgId::ClinicalData); if (!actionMap.contains(actionStr)) { qWarning().noquote() << QString("LeahiRt: unknown message action \"%1\" for msgId=0x%2 — defaulting to drop") @@ -137,7 +150,7 @@ * \brief LeahiRtController::onMessageReceive * \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. + * section's topic to set the RtMessage frame msg_id. * \param msg - the reassembled message */ void LeahiRtController::onMessageReceive(const Can::Message &msg) @@ -167,7 +180,9 @@ QDateTime::currentDateTime(), QStringLiteral("test_device"), msg); - _agentInterface.send(it->topic, _txSequence++, payload); + const quint16 sequence = _txSequence++; + _agentInterface.send(it->topic, sequence, payload); + _appServer.send(it->topic, sequence, payload); received = true; cachedMsg = msg; } Index: LeahiRt/LeahiRtController.h =================================================================== diff -u -r64243101dff61b5c1a40b96ef33080236999acf6 -ra5781739bcbe58c754aff8861561495624bc5b67 --- LeahiRt/LeahiRtController.h (.../LeahiRtController.h) (revision 64243101dff61b5c1a40b96ef33080236999acf6) +++ LeahiRt/LeahiRtController.h (.../LeahiRtController.h) (revision a5781739bcbe58c754aff8861561495624bc5b67) @@ -22,20 +22,17 @@ #include #include -#include "AgentInterface.h" -#include "AgentMessage.h" +#include "RtInterface.h" +#include "RtMessage.h" #include "CanInterface.h" #include "CanMessage.h" +#include "RtServer.h" #include "MessageDispatcher.h" using namespace Can; /*! - * \brief Real-time CAN to cloud controller - * \details Owns the CAN→cloud pipeline. CanInterface receives frames, the - * MessageDispatcher reassembles them per CAN id, and completed messages - * are converted to protobuf and forwarded to the Connectivity Agent - * over the AgentInterface. + * \brief Real-time Cloud Communications controller */ class LeahiRtController : public QObject { @@ -45,12 +42,17 @@ explicit LeahiRtController(const QString &configPath, const QString &msgHandlingPath, QObject *parent = nullptr); ~LeahiRtController(); + void connectToAgent(); + bool listenForApp(); + +Q_SIGNALS: /*! - * \brief connectToAgent - * \details Initialises the AgentInterface using the socket path and reconnect - * interval from the settings file. + * \brief didCanMessageReceive + * \details Emitted when a complete CAN message has been reassembled. + * \param timestamp - time the message was completed + * \param msg - the completed message */ - void connectToAgent(); + void didCanMessageReceive(const QDateTime timestamp, const Can::Message msg); private: /*! @@ -67,7 +69,7 @@ */ struct MsgHandling { MsgAction action = MsgAction::Drop; - AgentMessage::MsgId topic = AgentMessage::MsgId::ClinicalData; + RtMessage::MsgId topic = RtMessage::MsgId::ClinicalData; }; void loadMsgHandling(const QString &msgHandlingPath); @@ -78,39 +80,13 @@ Can::MessageDispatcher _dispatcher; QMap> _msgCache; QHash _msgHandling; - AgentInterface _agentInterface; + RtInterface _agentInterface; QThread _agentThread; + RtServer _appServer; ///< The Luis application connects here. quint16 _txSequence = 0; -Q_SIGNALS: - /*! - * \brief didCanMessageReceive - * \details Emitted when a complete CAN message has been reassembled. - * \param timestamp - time the message was completed - * \param msg - the completed message - */ - void didCanMessageReceive(const QDateTime timestamp, const Can::Message msg); - private Q_SLOTS: - /*! - * \brief onFrameReceive - * \details Unpacks a CAN frame and feeds it to the dispatcher for reassembly. - * \param frame - the received CAN frame - */ void onFrameReceive(const QCanBusFrame frame); - - /*! - * \brief onMessageReceive - * \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/config/LeahiRt.ini =================================================================== diff -u -r401d8ad0e5c070f9c6ecb41ed3ba25ba3d1c69a0 -ra5781739bcbe58c754aff8861561495624bc5b67 --- LeahiRt/config/LeahiRt.ini (.../LeahiRt.ini) (revision 401d8ad0e5c070f9c6ecb41ed3ba25ba3d1c69a0) +++ LeahiRt/config/LeahiRt.ini (.../LeahiRt.ini) (revision a5781739bcbe58c754aff8861561495624bc5b67) @@ -1,3 +1,4 @@ [Socket] LocalSocketName=/tmp/leahi_rt.sock +AppSocketName=/tmp/leahi_app.sock ReconnectIntervalMs=5000 Index: LeahiRt/config/LeahiRtMsgHandling.ini =================================================================== diff -u --- LeahiRt/config/LeahiRtMsgHandling.ini (revision 0) +++ LeahiRt/config/LeahiRtMsgHandling.ini (revision a5781739bcbe58c754aff8861561495624bc5b67) @@ -0,0 +1,2370 @@ +[0x0000] +msg_id = MSG_ID_UNUSED +action = drop +topic = + +[0x0080] +msg_id = MSG_ID_TESTER_LOGIN_REQUEST +action = drop +topic = + +[0x00A0] +msg_id = MSG_ID_DD_TESTER_LOGIN_REQUEST +action = drop +topic = + +[0x00B0] +msg_id = MSG_ID_FP_TESTER_LOGIN_REQUEST +action = drop +topic = + +[0x0100] +msg_id = MSG_ID_ALARM_STATUS_DATA +action = drop +topic = + +[0x0180] +msg_id = MSG_ID_TD_SOFTWARE_RESET_REQUEST +action = drop +topic = + +[0x01A0] +msg_id = MSG_ID_DD_SOFTWARE_RESET_REQUEST +action = drop +topic = + +[0x01B0] +msg_id = MSG_ID_FP_SOFTWARE_RESET_REQUEST +action = drop +topic = + +[0x0200] +msg_id = MSG_ID_ALARM_TRIGGERED +action = drop +topic = + +[0x0280] +msg_id = MSG_ID_TD_SEND_TEST_CONFIGURATION +action = drop +topic = + +[0x02A0] +msg_id = MSG_ID_DD_SEND_TEST_CONFIGURATION +action = drop +topic = + +[0x02B0] +msg_id = MSG_ID_FP_SEND_TEST_CONFIGURATION +action = drop +topic = + +[0x0300] +msg_id = MSG_ID_ALARM_CLEARED +action = drop +topic = + +[0x0380] +msg_id = MSG_ID_TD_BUBBLE_OVERRIDE_REQUEST +action = drop +topic = + +[0x03A0] +msg_id = MSG_ID_DD_VALVE_PUBLISH_INTERVAL_OVERRIDE_REQUEST +action = drop +topic = + +[0x03B0] +msg_id = MSG_ID_FP_VALVE_PUBLISH_INTERVAL_OVERRIDE_REQUEST +action = drop +topic = + +[0x0400] +msg_id = MSG_ID_ALARM_CONDITION_CLEARED +action = drop +topic = + +[0x0480] +msg_id = MSG_ID_TD_VOLTAGE_PUBLISH_INTERVAL_OVERRIDE_REQUEST +action = drop +topic = + +[0x04A0] +msg_id = MSG_ID_DD_VALVE_STATE_OVERRIDE_REQUEST +action = drop +topic = + +[0x04B0] +msg_id = MSG_ID_FP_VALVE_CMD_STATE_OVERRIDE_REQUEST +action = drop +topic = + +[0x0500] +msg_id = MSG_ID_USER_ALARM_SILENCE_REQUEST +action = drop +topic = + +[0x0580] +msg_id = MSG_ID_TD_VOLTAGE_OVERRIDE_REQUEST +action = drop +topic = + +[0x05A0] +msg_id = MSG_ID_DD_VALVE_SENSED_STATE_OVERRIDE_REQUEST +action = drop +topic = + +[0x05B0] +msg_id = MSG_ID_FP_VALVE_SENSED_STATE_OVERRIDE_REQUEST +action = drop +topic = + +[0x0600] +msg_id = MSG_ID_UI_ALARM_USER_ACTION_REQUEST +action = drop +topic = + +[0x0680] +msg_id = MSG_ID_TD_BUBBLE_PUBLISH_INTERVAL_OVERRIDE_REQUEST +action = drop +topic = + +[0x06A0] +msg_id = MSG_ID_DD_PRESSURE_SENSOR_READINGS_OVERRIDE_REQUEST +action = drop +topic = + +[0x06B0] +msg_id = MSG_ID_FP_FLUID_PUMP_SET_PWM_REQUEST +action = drop +topic = + +[0x0700] +msg_id = MSG_ID_TD_ALARM_INFORMATION_DATA +action = drop +topic = + +[0x0780] +msg_id = MSG_ID_TD_PRESSURE_OVERRIDE_REQUEST +action = drop +topic = + +[0x07A0] +msg_id = MSG_ID_DD_PRESSURE_SENSOR_TEMPERATURE_OVERRIDE_REQUEST +action = drop +topic = + +[0x07B0] +msg_id = MSG_ID_FP_FLUID_PUMP_READ_PWM_OVERRIDE_REQUEST +action = drop +topic = + +[0x0800] +msg_id = MSG_ID_DD_ALARM_INFO_DATA +action = drop +topic = + +[0x0880] +msg_id = MSG_ID_TD_AIR_PUMP_SET_STATE_REQUEST +action = drop +topic = + +[0x08A0] +msg_id = MSG_ID_DD_PRESSURE_SENSOR_READ_COUNTER_OVERRIDE_REQUEST +action = drop +topic = + +[0x08B0] +msg_id = MSG_ID_FP_FLUID_PUMP_SPEED_OVERRIDE_REQUEST +action = drop +topic = + +[0x0900] +msg_id = MSG_ID_UI_ACTIVE_ALARMS_LIST_REQUEST +action = drop +topic = + +[0x0980] +msg_id = MSG_ID_TD_AIR_PUMP_PUBLISH_INTERVAL_OVERRIDE_REQUEST +action = drop +topic = + +[0x09A0] +msg_id = MSG_ID_DD_PRESSURE_SENSOR_ERROR_COUNTER_OVERRIDE_REQUEST +action = drop +topic = + +[0x09B0] +msg_id = MSG_ID_FP_RO_PUMP_PUBLISH_INTERVAL_OVERRIDE_REQUEST +action = drop +topic = + +[0x0A00] +msg_id = MSG_ID_TD_ACTIVE_ALARMS_LIST_REQUEST_RESPONSE +action = drop +topic = + +[0x0A80] +msg_id = MSG_ID_TD_SWITCHES_PUBLISH_INTERVAL_OVERRIDE_REQUEST +action = drop +topic = + +[0x0AA0] +msg_id = MSG_ID_DD_PRESSURE_SENSOR_PUBLISH_INTERVAL_OVERRIDE_REQUEST +action = drop +topic = + +[0x0AB0] +msg_id = MSG_ID_FP_PRESSURE_OVERRIDE_REQUEST +action = drop +topic = + +[0x0B00] +msg_id = MSG_ID_UI_SET_ALARM_AUDIO_VOLUME_LEVEL_CMD_REQUEST +action = drop +topic = + +[0x0B80] +msg_id = MSG_ID_TD_SWITCH_STATE_OVERRIDE_REQUEST +action = drop +topic = + +[0x0BA0] +msg_id = MSG_ID_DD_PRESSURE_SENSOR_FILTER_READINGS_OVERRIDE_REQUEST +action = drop +topic = + +[0x0BB0] +msg_id = MSG_ID_FP_PRESSURE_TEMP_OVERRIDE_REQUEST +action = drop +topic = + +[0x0C00] +msg_id = MSG_ID_TD_ALARM_AUDIO_VOLUME_SET_RESPONSE +action = drop +topic = + +[0x0C80] +msg_id = MSG_ID_TD_OFF_BUTTON_OVERRIDE_REQUEST +action = drop +topic = + +[0x0CA0] +msg_id = MSG_ID_DD_PRESSURE_SENSOR_FILTER_TEMPERATURE_OVERRIDE_REQUEST +action = drop +topic = + +[0x0CB0] +msg_id = MSG_ID_FP_PRESSURE_SENSOR_FILTER_READINGS_OVERRIDE_REQUEST +action = drop +topic = + +[0x0D00] +msg_id = MSG_ID_FW_VERSIONS_REQUEST +action = drop +topic = + +[0x0D80] +msg_id = MSG_ID_TD_STOP_BUTTON_OVERRIDE_REQUEST +action = drop +topic = + +[0x0DA0] +msg_id = MSG_ID_DD_CONDUCTIVITY_SENSOR_READINGS_OVERRIDE_REQUEST +action = drop +topic = + +[0x0DB0] +msg_id = MSG_ID_FP_PRESSURE_SENSOR_FILTER_TEMPERATURE_OVERRIDE_REQUEST +action = drop +topic = + +[0x0E00] +msg_id = MSG_ID_TD_VERSION_RESPONSE +action = drop +topic = + +[0x0E80] +msg_id = MSG_ID_TD_ALARM_LAMP_PATTERN_OVERRIDE_REQUEST +action = drop +topic = + +[0x0EA0] +msg_id = MSG_ID_DD_CONDUCTIVITY_SENSOR_TEMPERATURE_OVERRIDE_REQUEST +action = drop +topic = + +[0x0EB0] +msg_id = MSG_ID_FP_PRESSURE_PUBLISH_INTERVAL_OVERRIDE_REQUEST +action = drop +topic = + +[0x0F00] +msg_id = MSG_ID_DD_VERSION_RESPONSE +action = drop +topic = + +[0x0F80] +msg_id = MSG_ID_TD_ALARM_AUDIO_LEVEL_OVERRIDE_REQUEST +action = drop +topic = + +[0x0FA0] +msg_id = MSG_ID_DD_CONDUCTIVITY_SENSOR_CONDUCTIVITY_READ_COUNTER_OVERRIDE_REQUEST +action = drop +topic = + +[0x0FB0] +msg_id = MSG_ID_FP_LEVEL_PUBLISH_INTERVAL_OVERRIDE_REQUEST +action = drop +topic = + +[0x1000] +msg_id = MSG_ID_UI_CHECK_IN +action = drop +topic = + +[0x1080] +msg_id = MSG_ID_TD_ALARM_AUDIO_CURRENT_HG_OVERRIDE_REQUEST +action = drop +topic = + +[0x10A0] +msg_id = MSG_ID_DD_CONDUCTIVITY_SENSOR_CONDUCTIVITY_ERROR_COUNTER_OVERRIDE_REQUEST +action = drop +topic = + +[0x10B0] +msg_id = MSG_ID_FP_FLOATER_LEVEL_OVERRIDE_REQUEST +action = drop +topic = + +[0x1100] +msg_id = MSG_ID_TD_BLOOD_PUMP_DATA +action = drop +topic = + +[0x1180] +msg_id = MSG_ID_TD_ALARM_AUDIO_CURRENT_LG_OVERRIDE_REQUEST +action = drop +topic = + +[0x11A0] +msg_id = MSG_ID_DD_CONDUCTIVITY_SENSOR_PUBLISH_INTERVAL_OVERRIDE_REQUEST +action = drop +topic = + +[0x11B0] +msg_id = MSG_ID_FP_FLOWS_PUBLISH_INTERVAL_OVERRIDE_REQUEST +action = drop +topic = + +[0x1200] +msg_id = MSG_ID_TD_OP_MODE_DATA +action = drop +topic = + +[0x1280] +msg_id = MSG_ID_TD_BACKUP_ALARM_AUDIO_CURRENT_OVERRIDE_REQUEST +action = drop +topic = + +[0x12A0] +msg_id = MSG_ID_DD_CONCENTRATE_PUMP_PUBLISH_INTERVAL_OVERRIDE_REQUEST +action = drop +topic = + +[0x12B0] +msg_id = MSG_ID_FP_FLOW_RATE_OVERRIDE_REQUEST +action = drop +topic = + +[0x1300] +msg_id = MSG_ID_DD_OP_MODE_DATA +action = drop +topic = + +[0x1380] +msg_id = MSG_ID_TD_PRESSURE_PUBLISH_INTERVAL_OVERRIDE_REQUEST +action = drop +topic = + +[0x13A0] +msg_id = MSG_ID_DD_CONCENTRATE_PUMP_TARGET_SPEED_OVERRIDE_REQUEST +action = drop +topic = + +[0x13B0] +msg_id = MSG_ID_FP_FLOW_TEMP_OVERRIDE_REQUEST +action = drop +topic = + +[0x1400] +msg_id = MSG_ID_DD_COMMAND_RESPONSE +action = drop +topic = + +[0x1480] +msg_id = MSG_ID_TD_AIR_TRAP_LEVEL_OVERRIDE_REQUEST +action = drop +topic = + +[0x14A0] +msg_id = MSG_ID_DD_CONCENTRATE_PUMP_MEASURED_SPEED_OVERRIDE_REQUEST +action = drop +topic = + +[0x14B0] +msg_id = MSG_ID_FP_CONDUCTIVITY_PUBLISH_INTERVAL_OVERRIDE_REQUEST +action = drop +topic = + +[0x1500] +msg_id = MSG_ID_TD_UI_VERSION_INFO_REQUEST +action = drop +topic = + +[0x1580] +msg_id = MSG_ID_TD_AIR_TRAP_LEVEL_RAW_OVERRIDE_REQUEST +action = drop +topic = + +[0x15A0] +msg_id = MSG_ID_DD_CONCENTRATE_PUMP_PARKED_OVERRIDE_REQUEST +action = drop +topic = + +[0x15B0] +msg_id = MSG_ID_FP_CONDUCTIVITY_SENSOR_READINGS_OVERRIDE_REQUEST +action = drop +topic = + +[0x1600] +msg_id = MSG_ID_UI_VERSION_INFO_RESPONSE +action = drop +topic = + +[0x1680] +msg_id = MSG_ID_TD_AIR_TRAP_PUBLISH_INTERVAL_OVERRIDE_REQUEST +action = drop +topic = + +[0x16A0] +msg_id = MSG_ID_DD_CONCENTRATE_PUMP_PARK_FAULT_OVERRIDE_REQUEST +action = drop +topic = + +[0x16B0] +msg_id = MSG_ID_FP_CONDUCTIVITY_SENSOR_TEMPERATURE_OVERRIDE_REQUEST +action = drop +topic = + +[0x1700] +msg_id = MSG_ID_TD_EVENT +action = drop +topic = + +[0x1780] +msg_id = MSG_ID_TD_3_WAY_VALVE_SET_STATE_REQUEST +action = drop +topic = + +[0x17A0] +msg_id = MSG_ID_DD_CONCENTRATE_PUMP_PARK_REQUEST_OVERRIDE_REQUEST +action = drop +topic = + +[0x17B0] +msg_id = MSG_ID_FP_CONDUCTIVITY_SENSOR_CONDUCTIVITY_READ_COUNT_OVERRIDE_REQUEST +action = drop +topic = + +[0x1800] +msg_id = MSG_ID_DD_EVENT +action = drop +topic = + +[0x1880] +msg_id = MSG_ID_TD_ROTARY_PINCH_VALVE_SET_POS_REQUEST +action = drop +topic = + +[0x18A0] +msg_id = MSG_ID_DD_TEMPERATURE_SENSOR_PUBLISH_INTERVAL_OVERRIDE_REQUEST +action = drop +topic = + +[0x18B0] +msg_id = MSG_ID_FP_CONDUCTIVITY_SENSOR_CONDUCTIVITY_ERROR_COUNT_OVERRIDE_REQUEST +action = drop +topic = + +[0x1900] +msg_id = MSG_ID_TD_DD_ALARMS_REQUEST +action = drop +topic = + +[0x1980] +msg_id = MSG_ID_TD_ROTARY_PINCH_VALVE_STATUS_OVERRIDE_REQUEST +action = drop +topic = + +[0x19A0] +msg_id = MSG_ID_DD_TEMPERATURE_SENSOR_MEASURED_TEMPERATURE_OVERRIDE_REQUEST +action = drop +topic = + +[0x19B0] +msg_id = MSG_ID_FP_TEMPERATURE_OVERRIDE_REQUEST +action = drop +topic = + +[0x1A00] +msg_id = MSG_ID_UI_TD_RESET_IN_SERVICE_MODE_REQUEST +action = drop +topic = + +[0x1A80] +msg_id = MSG_ID_TD_ROTARY_PINCH_VALVE_POSITION_OVERRIDE_REQUEST +action = drop +topic = + +[0x1AA0] +msg_id = MSG_ID_DD_TEMPERATURE_SENSOR_READ_COUNTER_OVERRIDE_REQUEST +action = drop +topic = + +[0x1AB0] +msg_id = MSG_ID_FP_FILTERED_FLOW_RATE_OVERRIDE_REQUEST +action = drop +topic = + +[0x1B00] +msg_id = MSG_ID_DD_VALVES_STATES_DATA +action = drop +topic = + +[0x1B80] +msg_id = MSG_ID_TD_VALVES_PUBLISH_INTERVAL_OVERRIDE_REQUEST +action = drop +topic = + +[0x1BA0] +msg_id = MSG_ID_DD_TEMPERATURE_SENSOR_FILTERED_TEMP_OVERRIDE_REQUEST +action = drop +topic = + +[0x1BB0] +msg_id = MSG_ID_FP_FILTERED_FLOW_TEMP_OVERRIDE_REQUEST +action = drop +topic = + +[0x1C00] +msg_id = MSG_ID_DD_PRESSURES_DATA +action = drop +topic = + +[0x1C80] +msg_id = MSG_ID_TD_ALARM_STATUS_PUBLISH_INTERVAL_OVERRIDE_REQUEST +action = drop +topic = + +[0x1CA0] +msg_id = MSG_ID_DD_SET_OPERATION_SUB_MODE_OVERRIDE_REQUEST +action = drop +topic = + +[0x1CB0] +msg_id = MSG_ID_FP_PRE_GEN_PUBLISH_INTERVAL_OVERRIDE_REQUEST +action = drop +topic = + +[0x1D00] +msg_id = MSG_ID_TD_VOLTAGES_DATA +action = drop +topic = + +[0x1D80] +msg_id = MSG_ID_TD_ALARM_INFO_PUBLISH_INTERVAL_OVERRIDE_REQUEST +action = drop +topic = + +[0x1DA0] +msg_id = MSG_ID_DD_DIALYSATE_PUMPS_PUBLISH_INTERVAL_OVERRIDE_REQUEST +action = drop +topic = + +[0x1DB0] +msg_id = MSG_ID_FP_SET_OPERATION_MODE_REQUEST +action = drop +topic = + +[0x1E00] +msg_id = MSG_ID_TD_BUBBLES_DATA +action = drop +topic = + +[0x1E80] +msg_id = MSG_ID_TD_ALARM_START_TIME_OVERRIDE_REQUEST +action = drop +topic = + +[0x1EA0] +msg_id = MSG_ID_DD_DIALYSATE_PUMPS_TARGET_SPEED_OVERRIDE_REQUEST +action = drop +topic = + +[0x1EB0] +msg_id = MSG_ID_FP_OPERATION_MODE_PUBLISH_INTERVAL_OVERRIDE_REQUEST +action = drop +topic = + +[0x1F00] +msg_id = MSG_ID_DD_CONDUCTIVITY_DATA +action = drop +topic = + +[0x1F80] +msg_id = MSG_ID_TD_ALARM_CLEAR_ALL_ALARMS_REQUEST +action = drop +topic = + +[0x1FA0] +msg_id = MSG_ID_DD_DIALYSATE_PUMPS_MEASURED_SPEED_OVERRIDE_REQUEST +action = drop +topic = + +[0x1FB0] +msg_id = MSG_ID_FP_TEMPERATURE_SENSOR_PUBLISH_INTERVAL_OVERRIDE_REQUEST +action = drop +topic = + +[0x2000] +msg_id = MSG_ID_TD_AIR_PUMP_DATA +action = drop +topic = + +[0x2080] +msg_id = MSG_ID_TD_WATCHDOG_OVERRIDE_REQUEST +action = drop +topic = + +[0x20A0] +msg_id = MSG_ID_DD_DIALYSATE_PUMPS_TARGET_PRESSURE_OVERRIDE_REQUEST +action = drop +topic = + +[0x20B0] +msg_id = MSG_ID_FP_RO_PUMP_TARGET_PRESSURE_OVERRIDE_REQUEST +action = drop +topic = + +[0x2100] +msg_id = MSG_ID_TD_SWITCHES_DATA +action = drop +topic = + +[0x2180] +msg_id = MSG_ID_TD_ALARM_STATE_OVERRIDE_REQUEST +action = drop +topic = + +[0x21A0] +msg_id = MSG_ID_DD_DIALYSATE_PUMPS_MEASURED_CURRENT_OVERRIDE_REQUEST +action = drop +topic = + +[0x21B0] +msg_id = MSG_ID_FP_RO_PUMP_TARGET_FLOW_OVERRIDE_REQUEST +action = drop +topic = + +[0x2200] +msg_id = MSG_ID_POWER_OFF_WARNING +action = drop +topic = + +[0x2280] +msg_id = MSG_ID_TD_SAFETY_SHUTDOWN_OVERRIDE_REQUEST +action = drop +topic = + +[0x22A0] +msg_id = MSG_ID_DD_DIALYSATE_PUMPS_MEASURED_DIRECTION_OVERRIDE_REQUEST +action = drop +topic = + +[0x22B0] +msg_id = MSG_ID_FP_RO_PUMP_TARGET_PWM_OVERRIDE_REQUEST +action = drop +topic = + +[0x2300] +msg_id = MSG_ID_OFF_BUTTON_PRESS_REQUEST +action = drop +topic = + +[0x2380] +msg_id = MSG_ID_TD_PINCH_VALVE_SET_POSITION_REQUEST +action = drop +topic = + +[0x23A0] +msg_id = MSG_ID_DD_HEATERS_PUBLISH_INTERVAL_OVERRIDE_REQUEST +action = drop +topic = + +[0x23B0] +msg_id = MSG_ID_FP_BOOST_PUMP_PUBLISH_INTERVAL_OVERRIDE_REQUEST +action = drop +topic = + +[0x2400] +msg_id = MSG_ID_TD_PRESSURE_DATA +action = drop +topic = + +[0x2480] +msg_id = MSG_ID_TD_PINCH_VALVE_HOME_REQUEST +action = drop +topic = + +[0x24A0] +msg_id = MSG_ID_DD_HEATERS_DUTY_CYCLE_OVERRIDE_REQUEST +action = drop +topic = + +[0x24B0] +msg_id = MSG_ID_FP_BOOST_PUMP_TARGET_PRESSURE_OVERRIDE_REQUEST +action = drop +topic = + +[0x2500] +msg_id = MSG_ID_DD_CONCENTRATE_PUMP_DATA +action = drop +topic = + +[0x2580] +msg_id = MSG_ID_TD_BLOOD_PUMP_PUBLISH_INTERVAL_OVERRIDE_REQUEST +action = drop +topic = + +[0x25A0] +msg_id = MSG_ID_DD_LEVELS_PUBLISH_INTERVAL_OVERRIDE_REQUEST +action = drop +topic = + +[0x25B0] +msg_id = MSG_ID_FP_BOOST_PUMP_TARGET_FLOW_OVERRIDE_REQUEST +action = drop +topic = + +[0x2600] +msg_id = MSG_ID_DD_TEMPERATURE_DATA +action = drop +topic = + +[0x2680] +msg_id = MSG_ID_TD_BLOOD_PUMP_SET_FLOW_RATE_REQUEST +action = drop +topic = + +[0x26A0] +msg_id = MSG_ID_DD_LEVELS_STATUS_OVERRIDE_REQUEST +action = drop +topic = + +[0x26B0] +msg_id = MSG_ID_FP_BOOST_PUMP_TARGET_PWM_OVERRIDE_REQUEST +action = drop +topic = + +[0x2700] +msg_id = MSG_ID_DIALYSATE_PUMPS_DATA +action = drop +topic = + +[0x2780] +msg_id = MSG_ID_TD_BLOOD_PUMP_SET_SPEED_REQUEST +action = drop +topic = + +[0x27B0] +msg_id = MSG_ID_FP_BOOST_PUMP_STOP_REQUEST +action = drop +topic = + +[0x2800] +msg_id = MSG_ID_DD_HEATERS_DATA +action = drop +topic = + +[0x2880] +msg_id = MSG_ID_TD_BLOOD_PUMP_MEASURED_FLOW_RATE_OVERRIDE_REQUEST +action = drop +topic = + +[0x28A0] +msg_id = MSG_ID_DD_OP_MODE_STATUS_OVERRIDE_REQUEST +action = drop +topic = + +[0x28B0] +msg_id = MSG_ID_FP_RO_PUMP_STOP_REQUEST +action = drop +topic = + +[0x2900] +msg_id = MSG_ID_DD_LEVEL_DATA +action = drop +topic = + +[0x2980] +msg_id = MSG_ID_TD_BLOOD_PUMP_MEASURED_MOTOR_SPEED_OVERRIDE_REQUEST +action = drop +topic = + +[0x29A0] +msg_id = MSG_ID_DD_SET_OPERATION_MODE_OVERRIDE_REQUEST +action = drop +topic = + +[0x29B0] +msg_id = MSG_ID_FP_SAFETY_SHUTDOWN_OVERRIDE_REQUEST +action = drop +topic = + +[0x2A00] +msg_id = MSG_ID_TD_AIR_TRAP_DATA +action = drop +topic = + +[0x2A80] +msg_id = MSG_ID_TD_BLOOD_PUMP_MEASURED_ROTOR_SPEED_OVERRIDE_REQUEST +action = drop +topic = + +[0x2AA0] +msg_id = MSG_ID_DD_UF_DATA_PUBLISH_OVERRIDE_REQUEST +action = drop +topic = + +[0x2AB0] +msg_id = MSG_ID_FP_PERMEATE_TANK_PUBLISH_INTERVAL_OVERRIDE_REQUEST +action = drop +topic = + +[0x2B00] +msg_id = MSG_ID_TD_VALVES_DATA +action = drop +topic = + +[0x2B80] +msg_id = MSG_ID_TD_BLOOD_PUMP_ROTOR_COUNT_OVERRIDE_REQUEST +action = drop +topic = + +[0x2BA0] +msg_id = MSG_ID_DD_DIALYSATE_PUMPS_START_STOP_OVERRIDE_REQUEST +action = drop +topic = + +[0x2BB0] +msg_id = MSG_ID_FP_ALARM_STATE_OVERRIDE_REQUEST +action = drop +topic = + +[0x2C00] +msg_id = MSG_ID_FP_EVENT +action = drop +topic = + +[0x2C80] +msg_id = MSG_ID_TD_TMP_PRESSURE_OVERRIDE_REQUEST +action = drop +topic = + +[0x2CA0] +msg_id = MSG_ID_DD_GEND_MODE_DATA_PUBLISH_OVERRIDE_REQUEST +action = drop +topic = + +[0x2CB0] +msg_id = MSG_ID_FP_ALARM_CLEAR_ALL_ALARMS_REQUEST +action = drop +topic = + +[0x2D00] +msg_id = MSG_ID_FP_ALARM_INFO_DATA +action = drop +topic = + +[0x2D80] +msg_id = MSG_ID_TD_REQ_CURRENT_TREATMENT_PARAMETERS +action = drop +topic = + +[0x2DA0] +msg_id = MSG_ID_DD_CONCENTRATE_PUMPS_START_STOP_OVERRIDE_REQUEST +action = drop +topic = + +[0x2DB0] +msg_id = MSG_ID_FP_SET_TEST_CONFIGURATION +action = drop +topic = + +[0x2E00] +msg_id = MSG_ID_DD_BAL_CHAMBER_DATA +action = drop +topic = + +[0x2E80] +msg_id = MSG_ID_TD_RSP_CURRENT_TREATMENT_PARAMETERS +action = drop +topic = + +[0x2EA0] +msg_id = MSG_ID_DD_HEATERS_START_STOP_OVERRIDE_REQUEST +action = drop +topic = + +[0x2EB0] +msg_id = MSG_ID_FP_GET_TEST_CONFIGURATION +action = drop +topic = + +[0x2F00] +msg_id = MSG_ID_DD_GEN_DIALYSATE_MODE_DATA +action = drop +topic = + +[0x2F80] +msg_id = MSG_ID_TD_SET_TREATMENT_PARAMETER +action = drop +topic = + +[0x2FA0] +msg_id = MSG_ID_DD_VALVES_OPEN_CLOSE_STATE_OVERRIDE_REQUEST +action = drop +topic = + +[0x2FB0] +msg_id = MSG_ID_FP_RESET_ALL_TEST_CONFIGURATIONS +action = drop +topic = + +[0x3000] +msg_id = MSG_ID_DD_GEN_DIALYSATE_REQUEST_DATA +action = drop +topic = + +[0x3080] +msg_id = MSG_ID_TD_OP_MODE_PUBLISH_INTERVAL_OVERRIDE_REQUEST +action = drop +topic = + +[0x30B0] +msg_id = MSG_ID_FP_INLET_PRES_PUBLISH_INTERVAL_OVERRIDE_REQUEST +action = drop +topic = + +[0x3100] +msg_id = MSG_ID_FP_VALVES_STATES_DATA +action = drop +topic = + +[0x3180] +msg_id = MSG_ID_TD_OP_MODE_OVERRIDE_REQUEST +action = drop +topic = + +[0x31A0] +msg_id = MSG_ID_DD_BAL_CHAMBER_DATA_PUBLISH_OVERRIDE_REQUEST +action = drop +topic = + +[0x31B0] +msg_id = MSG_ID_FP_INLET_PRES_CHECK_TIME_OVERRIDE_REQUEST +action = drop +topic = + +[0x3200] +msg_id = MSG_ID_FP_RO_PUMP_DATA +action = drop +topic = + +[0x3280] +msg_id = MSG_ID_TD_EJECTOR_MOTOR_SET_SPEED_REQUEST +action = drop +topic = + +[0x32A0] +msg_id = MSG_ID_DD_BAL_CHAMBER_SWITCH_FREQ_OVERRIDE_REQUEST +action = drop +topic = + +[0x32B0] +msg_id = MSG_ID_FP_FILTERED_COND_SENSOR_READINGS_OVERRIDE_REQUEST +action = drop +topic = + +[0x3300] +msg_id = MSG_ID_FP_OP_MODE_DATA +action = drop +topic = + +[0x3380] +msg_id = MSG_ID_TD_EJECTOR_COMMAND +action = drop +topic = + +[0x33A0] +msg_id = MSG_ID_DD_DIAL_DELIVERY_IN_PROGRESS_OVERRIDE_REQUEST +action = drop +topic = + +[0x33B0] +msg_id = MSG_ID_FP_FILTERED_COND_SENSOR_TEMPERATURE_OVERRIDE_REQUEST +action = drop +topic = + +[0x3400] +msg_id = MSG_ID_FP_PRESSURES_DATA +action = drop +topic = + +[0x3480] +msg_id = MSG_ID_TD_EJECTOR_PUBLISH_INTERVAL_OVERRIDE_REQUEST +action = drop +topic = + +[0x34A0] +msg_id = MSG_ID_DD_DIAL_DELIVERY_GOOD_TO_DELIVER_OVERRIDE_REQUEST +action = drop +topic = + +[0x34B0] +msg_id = MSG_ID_FP_SET_START_STOP_OVERRIDE_REQUEST +action = drop +topic = + +[0x3500] +msg_id = MSG_ID_FP_LEVEL_DATA +action = drop +topic = + +[0x3580] +msg_id = MSG_ID_TD_SET_AIR_TRAP_CONTROL +action = drop +topic = + +[0x35A0] +msg_id = MSG_ID_DD_HEATERS_TARGET_TEMPERATURE_OVERRIDE_REQUEST +action = drop +topic = + +[0x35B0] +msg_id = MSG_ID_FP_RO_REJECTION_RATIO_PUBLISH_INTERVAL_OVERRIDE_REQUEST +action = drop +topic = + +[0x3600] +msg_id = MSG_ID_FP_FLOW_DATA +action = drop +topic = + +[0x3680] +msg_id = MSG_ID_TD_HOME_BLOOD_PUMP +action = drop +topic = + +[0x36A0] +msg_id = MSG_ID_DD_BC_VALVE_STATES_OVERRIDE_REQUEST +action = drop +topic = + +[0x36B0] +msg_id = MSG_ID_FP_RO_FILTERED_REJECTION_RATIO_OVERRIDE_REQUEST +action = drop +topic = + +[0x3700] +msg_id = MSG_ID_FP_CONDUCTIVITY_DATA +action = drop +topic = + +[0x3780] +msg_id = MSG_ID_TD_BLOOD_FLOW_STROKE_VOLUME_OVERRIDE_REQUEST +action = drop +topic = + +[0x37A0] +msg_id = MSG_ID_DD_BC_SWITCH_ONLY_START_STOP_OVERRIDE_REQUEST +action = drop +topic = + +[0x37B0] +msg_id = MSG_ID_FP_RO_GET_CALCULATED_DUTY_CYCLE_REQUEST +action = drop +topic = + +[0x3800] +msg_id = MSG_ID_AVAILABLE_6 +action = drop +topic = + +[0x3880] +msg_id = MSG_ID_TD_BLOOD_FLOW_WEAR_A_TERM_OVERRIDE_REQUEST +action = drop +topic = + +[0x38A0] +msg_id = MSG_ID_DD_HYD_CHAMBER_TARGET_TEMP_OVERRIDE_REQUEST +action = drop +topic = + +[0x38B0] +msg_id = MSG_ID_FP_RO_CALCULATED_DUTY_CYCLE_RESPONSE +action = drop +topic = + +[0x3900] +msg_id = MSG_ID_FP_TEMPERATURE_DATA +action = drop +topic = + +[0x3980] +msg_id = MSG_ID_TD_BLOOD_FLOW_WEAR_B_TERM_OVERRIDE_REQUEST +action = drop +topic = + +[0x39A0] +msg_id = MSG_ID_DD_ACID_DOSING_VOLUME_OVERRIDE_REQUEST +action = drop +topic = + +[0x39B0] +msg_id = MSG_ID_FP_FLUSH_FILTER_PUBLISH_INTERVAL_OVERRIDE_REQUEST +action = drop +topic = + +[0x3A00] +msg_id = MSG_ID_FP_HEATER_DATA +action = drop +topic = + +[0x3A80] +msg_id = MSG_ID_TD_SET_TEST_CONFIGURATION +action = drop +topic = + +[0x3AA0] +msg_id = MSG_ID_DD_BICARB_DOSING_VOLUME_OVERRIDE_REQUEST +action = drop +topic = + +[0x3AB0] +msg_id = MSG_ID_FP_FLUSH_FILTER_TIMER_OVERRIDE_REQUEST +action = drop +topic = + +[0x3B00] +msg_id = MSG_ID_TD_TREATMENT_TIME_DATA +action = drop +topic = + +[0x3B80] +msg_id = MSG_ID_TD_GET_TEST_CONFIGURATION +action = drop +topic = + +[0x3BA0] +msg_id = MSG_ID_DD_GEND_EXEC_STATE_OVERRIDE_REQUEST +action = drop +topic = + +[0x3BB0] +msg_id = MSG_ID_FP_FLUSH_PERMEATE_PUBLISH_INTERVAL_OVERRIDE_REQUEST +action = drop +topic = + +[0x3C00] +msg_id = MSG_ID_TD_TREATMENT_STATE_DATA +action = drop +topic = + +[0x3C80] +msg_id = MSG_ID_TD_RESET_ALL_TEST_CONFIGURATIONS +action = drop +topic = + +[0x3CA0] +msg_id = MSG_ID_DD_HEATERS_PWM_PERIOD_OVERIDE_REQUEST +action = drop +topic = + +[0x3CB0] +msg_id = MSG_ID_FP_FLUSH_PERMEATE_TIMER_OVERRIDE_REQUEST +action = drop +topic = + +[0x3D00] +msg_id = MSG_ID_TD_FLUID_BOLUS_DATA +action = drop +topic = + +[0x3D80] +msg_id = MSG_ID_TD_AIR_PUMP_POWER_RAISE_OVERRIDE_REQUEST +action = drop +topic = + +[0x3DA0] +msg_id = MSG_ID_DD_PRE_GEND_MODE_DATA_PUBLISH_OVERRIDE_REQUEST +action = drop +topic = + +[0x3DB0] +msg_id = MSG_ID_FP_FLUSH_PERMEATE_ALARM_TIMER_OVERRIDE_REQUEST +action = drop +topic = + +[0x3E00] +msg_id = MSG_ID_TD_ULTRAFILTRATION_DATA +action = drop +topic = + +[0x3E80] +msg_id = MSG_ID_TD_AIR_PUMP_POWER_LOWER_OVERRIDE_REQUEST +action = drop +topic = + +[0x3EA0] +msg_id = MSG_ID_DD_POST_GEND_MODE_DATA_PUBLISH_OVERRIDE_REQUEST +action = drop +topic = + +[0x3EB0] +msg_id = MSG_ID_FP_FLUSH_CONCENTRATE_PUBLISH_INTERVAL_OVERRIDE_REQUEST +action = drop +topic = + +[0x3F00] +msg_id = MSG_ID_UI_TREATMENT_PARAMS_TO_VALIDATE +action = drop +topic = + +[0x3F80] +msg_id = MSG_ID_TD_HARD_STOP_BLOOD_PUMP +action = drop +topic = + +[0x3FA0] +msg_id = MSG_ID_DD_SEND_BLOOD_LEAK_EMB_MODE_RESPONSE +action = drop +topic = + +[0x3FB0] +msg_id = MSG_ID_FP_FLUSH_CONCENTRATE_TIMER_OVERRIDE_REQUEST +action = drop +topic = + +[0x4000] +msg_id = MSG_ID_TD_RESP_TREATMENT_PARAMS_TO_VALIDATE +action = drop +topic = + +[0x4080] +msg_id = MSG_ID_TD_BARO_MFG_CRC_OVERRIDE +action = drop +topic = + +[0x40A0] +msg_id = MSG_ID_DD_SPENT_CHAMB_FILL_DATA_PUBLISH_OVERRIDE_REQUEST +action = drop +topic = + +[0x40B0] +msg_id = MSG_ID_FP_DEF_FLUSH_FILTER_PUBLISH_INTERVAL_OVERRIDE_REQUEST +action = drop +topic = + +[0x4100] +msg_id = MSG_ID_UI_TREATMENT_UF_VOLUME_VALIDATE_REQUEST +action = drop +topic = + +[0x4180] +msg_id = MSG_ID_TD_BARO_PRESSURE_OVERRIDE +action = drop +topic = + +[0x41A0] +msg_id = MSG_ID_DD_AVAILABLE_TO_USE_4 +action = drop +topic = + +[0x41B0] +msg_id = MSG_ID_FP_DEF_FLUSH_FILTER_TIMER_OVERRIDE_REQUEST +action = drop +topic = + +[0x4200] +msg_id = MSG_ID_TD_TREATMENT_UF_VOLUME_VALIDATE_RESPONSE +action = drop +topic = + +[0x4280] +msg_id = MSG_ID_TD_TEMPERATURE_OVERRIDE +action = drop +topic = + +[0x42A0] +msg_id = MSG_ID_DD_SAFETY_SHUTDOWN_OVERRIDE_REQUEST +action = drop +topic = + +[0x42B0] +msg_id = MSG_ID_FP_DEF_PRE_GEN_PUBLISH_INTERVAL_OVERRIDE_REQUEST +action = drop +topic = + +[0x4300] +msg_id = MSG_ID_TD_TREATMENT_PARAM_RANGES +action = drop +topic = + +[0x4380] +msg_id = MSG_ID_TD_TEMPERATURE_PUBLISH_INTERVAL_OVERRIDE_REQUEST +action = drop +topic = + +[0x43A0] +msg_id = MSG_ID_DD_SET_TEST_CONFIGURATION +action = drop +topic = + +[0x43B0] +msg_id = MSG_ID_FP_DEF_GEN_PUBLISH_INTERVAL_OVERRIDE_REQUEST +action = drop +topic = + +[0x4400] +msg_id = MSG_ID_TD_VALIDATED_TREATMENT_PARAMS +action = drop +topic = + +[0x4480] +msg_id = MSG_ID_TD_EJECTOR_OPT_SENSOR_OVERRIDE_REQUEST +action = drop +topic = + +[0x44A0] +msg_id = MSG_ID_DD_GET_TEST_CONFIGURATION +action = drop +topic = + +[0x44B0] +msg_id = MSG_ID_FP_DEF_STATUS_REQUEST +action = drop +topic = + +[0x4500] +msg_id = MSG_ID_UI_INITIATE_TREATMENT_WORKFLOW +action = drop +topic = + +[0x4580] +msg_id = MSG_ID_TD_BLOOD_PRIME_VOLUME_OVERRIDE +action = drop +topic = + +[0x45A0] +msg_id = MSG_ID_DD_RESET_ALL_TEST_CONFIGURATIONS +action = drop +topic = + +[0x45B0] +msg_id = MSG_ID_FP_DEF_STATUS_RESPONSE +action = drop +topic = + +[0x4600] +msg_id = MSG_ID_TD_RESP_INITIATE_TREATMENT_WORKFLOW +action = drop +topic = + +[0x4680] +msg_id = MSG_ID_TD_BLOOD_PRIME_PUBLISH_INTERVAL_OVERRIDE_REQUEST +action = drop +topic = + +[0x46A0] +msg_id = MSG_ID_AVAILABLE_1 +action = drop +topic = + +[0x46B0] +msg_id = MSG_ID_FP_SET_OPERATION_SUB_MODE_REQUEST +action = drop +topic = + +[0x4700] +msg_id = MSG_ID_UI_UF_PAUSE_RESUME_REQUEST +action = drop +topic = + +[0x4780] +msg_id = MSG_ID_TD_ENABLE_VENOUS_BUBBLE_ALARM +action = drop +topic = + +[0x47A0] +msg_id = MSG_ID_DD_BLOOD_LEAK_DATA_PUBLISH_INTERVAL_OVERRIDE_REQUEST +action = drop +topic = + +[0x47B0] +msg_id = MSG_ID_FP_CONDUCTIVITY_SENSOR_RESISTANCE_OVERRIDE_REQUEST +action = drop +topic = + +[0x4800] +msg_id = MSG_ID_TD_UF_PAUSE_RESUME_RESPONSE +action = drop +topic = + +[0x4880] +msg_id = MSG_ID_TD_SYRINGE_PUMP_OPERATION_REQUEST +action = drop +topic = + +[0x48A0] +msg_id = MSG_ID_DD_BLOOD_LEAK_STATUS_OVERRIDE_REQUEST +action = drop +topic = + +[0x48B0] +msg_id = MSG_ID_FP_SET_RECOVERY_VALVES_REQUEST +action = drop +topic = + +[0x4900] +msg_id = MSG_ID_FP_GEN_WATER_MODE_DATA +action = drop +topic = + +[0x4980] +msg_id = MSG_ID_HD_SYRINGE_PUMP_PUBLISH_INTERVAL_OVERRIDE +action = drop +topic = + +[0x49A0] +msg_id = MSG_ID_DD_BLOOD_LEAK_SET_TO_EMBEDDED_MODE_REQUEST +action = drop +topic = + +[0x49B0] +msg_id = MSG_ID_FP_BOOST_PUMP_INSTALL_STATUS_REQUEST +action = drop +topic = + +[0x4A00] +msg_id = MSG_ID_DD_PRE_GEN_DIALYSATE_STATE_DATA +action = drop +topic = + +[0x4AA0] +msg_id = MSG_ID_DD_BLOOD_LEAK_SET_EMBEDDED_MODE_CMD_REQUEST +action = drop +topic = + +[0x4AB0] +msg_id = MSG_ID_FP_BOOST_PUMP_INSTALL_STATUS_RESPONSE +action = drop +topic = + +[0x4B00] +msg_id = MSG_ID_DD_POST_GEN_DIALYSATE_STATE_DATA +action = drop +topic = + +[0x4BA0] +msg_id = MSG_ID_DD_BLOOD_LEAK_EMBEDDED_MODE_INFO_OVERRIDE_REQUEST +action = drop +topic = + +[0x4C00] +msg_id = MSG_ID_DD_PRE_GEN_DIALYSATE_REQUEST_DATA +action = drop +topic = + +[0x4CA0] +msg_id = MSG_ID_DD_BLOOD_LEAK_INTENSITY_MOVING_AVERAGE_OVERRIDE_REQUEST +action = drop +topic = + +[0x4CB0] +msg_id = MSG_ID_FP_CONDUCTIVITY_SENSOR_VERSION_RESPONSE +action = drop +topic = + +[0x4D00] +msg_id = MSG_ID_FP_PRE_GEN_WATER_MODE_DATA +action = drop +topic = + +[0x4DA0] +msg_id = MSG_ID_DD_BLOOD_LEAK_ZEROING_INTERVAL_IN_MS_OVERRIDE_REQUEST +action = drop +topic = + +[0x4E00] +msg_id = MSG_ID_TD_EJECTOR_DATA +action = drop +topic = + +[0x4EA0] +msg_id = MSG_ID_DD_BLOOD_LEAK_ZERO_REQUEST +action = drop +topic = + +[0x4EB0] +msg_id = MSG_ID_FP_CONDUCTIVITY_SENSOR_CAL_RESPONSE +action = drop +topic = + +[0x4F00] +msg_id = MSG_ID_TD_TREATMENT_SET_POINTS +action = drop +topic = + +[0x4FA0] +msg_id = MSG_ID_DD_FILTERED_COND_SENSOR_READINGS_OVERRIDE_REQUEST +action = drop +topic = + +[0x5000] +msg_id = MSG_ID_FP_BOOST_PUMP_DATA +action = drop +topic = + +[0x5080] +msg_id = MSG_ID_TD_SYRINGE_PUMP_RATE_OVERRIDE_REQUEST +action = drop +topic = + +[0x50A0] +msg_id = MSG_ID_DD_FILTERED_COND_SENSOR_TEMPERATURE_OVERRIDE_REQUEST +action = drop +topic = + +[0x5100] +msg_id = MSG_ID_TD_SERIAL_RESPONSE +action = drop +topic = + +[0x5180] +msg_id = MSG_ID_TD_SYRINGE_PUMP_FORCE_OVERRIDE_REQUEST +action = drop +topic = + +[0x51A0] +msg_id = MSG_ID_DD_VOLTAGE_DATA_PUBLISH_INTERVAL_OVERRIDE_REQUEST +action = drop +topic = + +[0x5200] +msg_id = MSG_ID_DD_SERIAL_RESPONSE +action = drop +topic = + +[0x5280] +msg_id = MSG_ID_TD_SYRINGE_PUMP_HOME_OVERRIDE_REQUEST +action = drop +topic = + +[0x52A0] +msg_id = MSG_ID_DD_MONITORED_VOLTAGE_OVERRIDE_REQUEST +action = drop +topic = + +[0x5300] +msg_id = MSG_ID_TD_TEMPERATURE_DATA +action = drop +topic = + +[0x5380] +msg_id = MSG_ID_TD_SYRINGE_PUMP_POSITION_OVERRIDE_REQUEST +action = drop +topic = + +[0x53A0] +msg_id = MSG_ID_DD_RINSE_PUMP_DATA_PUBLISH_INTERVAL_OVERRIDE_REQUEST +action = drop +topic = + +[0x5400] +msg_id = MSG_ID_TD_BATTERY_DATA +action = drop +topic = + +[0x5480] +msg_id = MSG_ID_TD_SYRINGE_PUMP_VOLUME_OVERRIDE_REQUEST +action = drop +topic = + +[0x54A0] +msg_id = MSG_ID_DD_RINSE_PUMP_PWM_PERCENT_OVERRIDE_REQUEST +action = drop +topic = + +[0x5500] +msg_id = MSG_ID_UI_PATIENT_DISCONNECT_CONFIRM_REQUEST +action = drop +topic = + +[0x5580] +msg_id = MSG_ID_TD_SYRINGE_PUMP_STATUS_OVERRIDE_REQUEST +action = drop +topic = + +[0x55A0] +msg_id = MSG_ID_DD_RINSE_PUMP_TURN_ON_OFF_REQUEST +action = drop +topic = + +[0x5600] +msg_id = MSG_ID_TD_PATIENT_DISCONNECT_CONFIRM_RESPONSE +action = drop +topic = + +[0x5680] +msg_id = MSG_ID_TD_SYRINGE_PUMP_ENCODER_STATUS_OVERRIDE_REQUEST +action = drop +topic = + +[0x56A0] +msg_id = MSG_ID_DD_DRY_BICART_DATA_PUBLISH_INTERVAL_OVERRIDE_REQUEST +action = drop +topic = + +[0x5700] +msg_id = MSG_ID_FP_CONCENTRATE_FLUSH_DATA +action = drop +topic = + +[0x5780] +msg_id = MSG_ID_TD_SYRINGE_PUMP_ADC_DAC_STATUS_OVERRIDE_REQUEST +action = drop +topic = + +[0x57A0] +msg_id = MSG_ID_DD_DRY_BICART_FILL_CYCLE_MAX_OVERRIDE_REQUEST +action = drop +topic = + +[0x5800] +msg_id = MSG_ID_FP_GENP_DEF_DATA +action = drop +topic = + +[0x5880] +msg_id = MSG_ID_TD_SYRINGE_PUMP_ADC_READ_COUNTER_OVERRIDE_REQUEST +action = drop +topic = + +[0x58A0] +msg_id = MSG_ID_DD_DRY_BICART_FILL_REQUEST_OVERRIDE_REQUEST +action = drop +topic = + +[0x5900] +msg_id = MSG_ID_FP_PRE_GEN_DEF_DATA +action = drop +topic = + +[0x5980] +msg_id = MSG_ID_TD_HEPARIN_BOLUS_TARGET_RATE_OVERRIDE_REQUEST +action = drop +topic = + +[0x59A0] +msg_id = MSG_ID_DD_BICARB_CHAMBER_FILL_REQUEST_OVERRIDE_REQUEST +action = drop +topic = + +[0x5A00] +msg_id = MSG_ID_FP_VERSION_RESPONSE +action = drop +topic = + +[0x5AA0] +msg_id = MSG_ID_DD_BICART_DRAIN_REQUEST_OVERRIDE_REQUEST +action = drop +topic = + +[0x5B00] +msg_id = MSG_ID_TD_TREATMENT_PAUSED_TIMER_DATA +action = drop +topic = + +[0x5BA0] +msg_id = MSG_ID_DD_BICART_CARTRIDGE_SELECT_OVERRIDE_REQUEST +action = drop +topic = + +[0x5C00] +msg_id = MSG_ID_DD_UF_DATA +action = drop +topic = + +[0x5CA0] +msg_id = MSG_ID_DD_SET_CONDUCTIVITY_MODEL_REQUEST +action = drop +topic = + +[0x5D00] +msg_id = MSG_ID_FP_PERMEATE_TANK_DATA +action = drop +topic = + +[0x5DA0] +msg_id = MSG_ID_DD_CONDUCTIVITY_SENSOR_RESISTANCE_OVERRIDE_REQUEST +action = drop +topic = + +[0x5E00] +msg_id = MSG_ID_DD_SPENT_CHAMBER_FILL_DATA +action = drop +topic = + +[0x5F00] +msg_id = MSG_ID_UI_FLUID_BOLUS_REQUEST +action = drop +topic = + +[0x5FA0] +msg_id = MSG_ID_DD_CONDUCTIVITY_SENSOR_VERSION_RESPONSE +action = drop +topic = + +[0x6000] +msg_id = MSG_ID_TD_FLUID_BOLUS_RESPONSE +action = drop +topic = + +[0x6080] +msg_id = MSG_ID_TD_SYRINGE_PUMP_FORCE_SENSOR_CALIBRATION_REQUEST +action = drop +topic = + +[0x60A0] +msg_id = MSG_ID_DD_BICARB_MIX_VOL_KP_GAIN_COEFF_OVERRIDE_REQUEST +action = drop +topic = + +[0x6100] +msg_id = MSG_ID_DD_BLOOD_LEAK_DATA +action = drop +topic = + +[0x6180] +msg_id = MSG_ID_TD_GET_ALARM_PROPERTIES_REQUEST +action = drop +topic = + +[0x61A0] +msg_id = MSG_ID_DD_BICARB_MIX_VOL_KI_GAIN_COEFF_OVERRIDE_REQUEST +action = drop +topic = + +[0x6200] +msg_id = MSG_ID_FP_INLET_PRESSURE_CHECK_DATA +action = drop +topic = + +[0x6280] +msg_id = MSG_ID_TD_ALARM_PROPERTIES_RESPONSE +action = drop +topic = + +[0x62A0] +msg_id = MSG_ID_DD_ACID_MIX_VOL_KP_GAIN_COEFF_OVERRIDE_REQUEST +action = drop +topic = + +[0x6300] +msg_id = MSG_ID_UI_BLOOD_PRESSURE_REQUEST +action = drop +topic = + +[0x63A0] +msg_id = MSG_ID_DD_ACID_MIX_VOL_KI_GAIN_COEFF_OVERRIDE_REQUEST +action = drop +topic = + +[0x6400] +msg_id = MSG_ID_TD_BLOOD_PRESSURE_READING +action = drop +topic = + +[0x64A0] +msg_id = MSG_ID_DD_ACID_MIX_VOL_OVERRIDE_REQUEST +action = drop +topic = + +[0x6500] +msg_id = MSG_ID_TD_BLOOD_PRESSURE_DATA +action = drop +topic = + +[0x65A0] +msg_id = MSG_ID_DD_BICARB_MIX_VOL_OVERRIDE_REQUEST +action = drop +topic = + +[0x6600] +msg_id = MSG_ID_UI_ULTRAFILTRATION_CHANGE_CONFIRM_REQUEST +action = drop +topic = + +[0x66A0] +msg_id = MSG_ID_DD_BICARB_TARGET_CONDUCTIVITY_OVERRIDE_REQUEST +action = drop +topic = + +[0x6700] +msg_id = MSG_ID_TD_ULTRAFILTRATION_CHANGE_CONFIRM_RESPONSE +action = drop +topic = + +[0x67A0] +msg_id = MSG_ID_DD_BICARB_DELTA_CONDUCTIVITY_OVERRIDE_REQUEST +action = drop +topic = + +[0x6800] +msg_id = MSG_ID_DD_VOLTAGES_DATA +action = drop +topic = + +[0x68A0] +msg_id = MSG_ID_DD_DIALYSATE_TARGET_CONDUCTIVITY_OVERRIDE_REQUEST +action = drop +topic = + +[0x6900] +msg_id = MSG_ID_DD_RINSE_PUMP_DATA +action = drop +topic = + +[0x69A0] +msg_id = MSG_ID_DD_DIALYSATE_DELTA_CONDUCTIVITY_OVERRIDE_REQUEST +action = drop +topic = + +[0x6A00] +msg_id = MSG_ID_TD_TREATMENT_LOG_ALARM_EVENT +action = drop +topic = + +[0x6AA0] +msg_id = MSG_ID_DD_BICART_UPPER_PRESSURE_OVERRIDE_REQUEST +action = drop +topic = + +[0x6B00] +msg_id = MSG_ID_TD_TREATMENT_LOG_EVENT +action = drop +topic = + +[0x6BA0] +msg_id = MSG_ID_DD_BICART_LOWER_PRESSURE_OVERRIDE_REQUEST +action = drop +topic = + +[0x6C00] +msg_id = MSG_ID_TD_DATE_AND_TIME_REQUEST +action = drop +topic = + +[0x6CA0] +msg_id = MSG_ID_DD_FLOATER_LEVEL_OVERRIDE_REQUEST +action = drop +topic = + +[0x6D00] +msg_id = MSG_ID_TD_DATE_AND_TIME_RESPONSE +action = drop +topic = + +[0x6DA0] +msg_id = MSG_ID_DD_SUBSTITUTION_PUMP_START_STOP_OVERRIDE_REQUEST +action = drop +topic = + +[0x6E00] +msg_id = MSG_ID_DD_DATE_AND_TIME_REQUEST +action = drop +topic = + +[0x6EA0] +msg_id = MSG_ID_DD_SUBSTITUTION_PUMP_BROADCAST_INTERVAL_OVERRIDE_REQUEST +action = drop +topic = + +[0x6F00] +msg_id = MSG_ID_DD_DATE_AND_TIME_RESPONSE +action = drop +topic = + +[0x6FA0] +msg_id = MSG_ID_DD_SUBSTITUTION_PUMP_TARGET_RATE_OVERRIDE_REQUEST +action = drop +topic = + +[0x7000] +msg_id = MSG_ID_DD_DRY_BICART_DATA +action = drop +topic = + +[0x7100] +msg_id = MSG_ID_FP_RO_REJECTION_RATIO_DATA +action = drop +topic = + +[0x71A0] +msg_id = MSG_ID_DD_CONDUCTIVITY_SENSOR_CAL_RESPONSE +action = drop +topic = + +[0x7200] +msg_id = MSG_ID_UI_PRESSURE_LIMITS_CHANGE_REQUEST +action = drop +topic = + +[0x72A0] +msg_id = MSG_ID_DD_MIXING_CONTROL_DATA +action = drop +topic = + +[0x7300] +msg_id = MSG_ID_TD_PRESSURE_LIMITS_CHANGE_RESPONSE +action = drop +topic = + +[0x73A0] +msg_id = MSG_ID_DD_MIXING_CONTROL_DATA_PUBLISH_INTERVAL_OVERRIDE_REQUEST +action = drop +topic = + +[0x7400] +msg_id = MSG_ID_UI_BOLUS_VOLUME_CHANGE_REQUEST +action = drop +topic = + +[0x74A0] +msg_id = MSG_ID_DD_BICART_DEPRESSURISE_REQUEST_OVERRIDE_REQUEST +action = drop +topic = + +[0x7500] +msg_id = MSG_ID_TD_BOLUS_VOLUME_CHANGE_RESPONSE +action = drop +topic = + +[0x75A0] +msg_id = MSG_ID_DD_TREATMENT_PARAMS_OVERRIDE_REQUEST +action = drop +topic = + +[0x7600] +msg_id = MSG_ID_UI_DURATION_VALIDATE_REQUEST +action = drop +topic = + +[0x7700] +msg_id = MSG_ID_TD_DURATION_VALIDATE_RESPONSE +action = drop +topic = + +[0x7800] +msg_id = MSG_ID_UI_DURATION_CONFIRM_REQUEST +action = drop +topic = + +[0x7900] +msg_id = MSG_ID_TD_DURATION_CONFIRM_RESPONSE +action = drop +topic = + +[0x7A00] +msg_id = MSG_ID_UI_TREATMENT_SET_POINTS_CHANGE_REQUEST +action = drop +topic = + +[0x7B00] +msg_id = MSG_ID_TD_TREATMENT_SET_POINTS_CHANGE_RESPONSE +action = drop +topic = + +[0x7C00] +msg_id = MSG_ID_UI_TREATMENT_SET_POINT_BLOOD_FLOW_CHANGE_REQUEST +action = drop +topic = + +[0x7D00] +msg_id = MSG_ID_TD_TREATMENT_SET_POINT_BLOOD_FLOW_CHANGE_RESPONSE +action = drop +topic = + +[0x7E00] +msg_id = MSG_ID_UI_TREATMENT_SET_POINT_DIALYSATE_FLOW_CHANGE_REQUEST +action = drop +topic = + +[0x7F00] +msg_id = MSG_ID_TD_TREATMENT_SET_POINT_DIALYSATE_FLOW_CHANGE_RESPONSE +action = drop +topic = + +[0x8000] +msg_id = MSG_ID_UI_TREATMENT_SET_POINT_DIALYSATE_TEMP_CHANGE_REQUEST +action = drop +topic = + +[0x8100] +msg_id = MSG_ID_TD_TREATMENT_SET_POINT_DIALYSATE_TEMP_CHANGE_RESPONSE +action = drop +topic = + +[0x8200] +msg_id = MSG_ID_TD_INSTITUTIONAL_RECORD_REQUEST +action = drop +topic = + +[0x8300] +msg_id = MSG_ID_TD_INSTITUTIONAL_RECORD_RESPONSE +action = drop +topic = + +[0x8400] +msg_id = MSG_ID_TD_ADJUST_INSTITUTIONAL_RECORD_REQUEST +action = drop +topic = + +[0x8500] +msg_id = MSG_ID_TD_ADJUST_INSTITUTIONAL_RECORD_RESPONSE +action = drop +topic = + +[0x8600] +msg_id = MSG_ID_TD_ADVANCED_INSTITUTIONAL_RECORD_REQUEST +action = drop +topic = + +[0x8700] +msg_id = MSG_ID_TD_ADVANCED_INSTITUTIONAL_RECORD_RESPONSE +action = drop +topic = + +[0x8800] +msg_id = MSG_ID_TD_ADVANCED_ADJUST_INSTITUTIONAL_RECORD_REQUEST +action = drop +topic = + +[0x8900] +msg_id = MSG_ID_TD_ADVANCED_ADJUST_INSTITUTIONAL_RECORD_RESPONSE +action = drop +topic = + +[0x8A00] +msg_id = MSG_ID_TD_HEPARIN_REQUEST +action = drop +topic = + +[0x8B00] +msg_id = MSG_ID_TD_HEPARIN_RESPONSE +action = drop +topic = + +[0x8C00] +msg_id = MSG_ID_TD_HEPARIN_DATA +action = drop +topic = + +[0x8D00] +msg_id = MSG_ID_TD_END_TREATMENT_REQUEST +action = drop +topic = + +[0x8E00] +msg_id = MSG_ID_TD_END_TREATMENT_RESPONSE +action = drop +topic = + +[0x8F00] +msg_id = MSG_ID_TD_RINSEBACK_PROGRESS +action = drop +topic = + +[0x9000] +msg_id = MSG_ID_UI_RINSEBACK_CMD_REQUEST +action = drop +topic = + +[0x9100] +msg_id = MSG_ID_TD_RINSEBACK_CMD_RESPONSE +action = drop +topic = + +[0x9200] +msg_id = MSG_ID_UI_ADJUST_DISPOSABLES_CONFIRM_REQUEST +action = drop +topic = + +[0x9300] +msg_id = MSG_ID_TD_ADJUST_DISPOSABLES_CONFIRM_RESPONSE +action = drop +topic = + +[0x9400] +msg_id = MSG_ID_UI_ADJUST_DISPOSABLES_REMOVAL_CONFIRM_REQUEST +action = drop +topic = + +[0x9500] +msg_id = MSG_ID_TD_ADJUST_DISPOSABLES_REMOVAL_CONFIRM_RESPONSE +action = drop +topic = + +[0x9600] +msg_id = MSG_ID_FP_FILTER_FLUSH_DEF_DATA +action = drop +topic = + +[0x9700] +msg_id = MSG_ID_TD_BLOOD_PRIME_PROGRESS_DATA +action = drop +topic = + +[0x9800] +msg_id = MSG_ID_UI_BLOOD_PRIME_CMD_REQUEST +action = drop +topic = + +[0x9900] +msg_id = MSG_ID_TD_BLOOD_PRIME_CMD_RESPONSE +action = drop +topic = + +[0x9A00] +msg_id = MSG_ID_TD_ISOLATED_UF_DATA +action = drop +topic = + +[0x9B00] +msg_id = MSG_ID_UI_ISOLATED_UF_DURATION_CHANGE_REQUEST +action = drop +topic = + +[0x9C00] +msg_id = MSG_ID_TD_ISOLATED_UF_DURATION_CHANGE_RESPONSE +action = drop +topic = + +[0x9D00] +msg_id = MSG_ID_UI_ISOLATED_UF_VOLUME_GOAL_CHANGE_REQUEST +action = drop +topic = + +[0x9E00] +msg_id = MSG_ID_TD_ISOLATED_UF_VOLUME_GOAL_CHANGE_RESPONSE +action = drop +topic = + +[0x9F00] +msg_id = MSG_ID_UI_ISOLATED_UF_CONFIRM_REQUEST +action = drop +topic = + +[0xA000] +msg_id = MSG_ID_TD_ISOLATED_UF_CONFIRM_RESPONSE +action = drop +topic = + +[0xA100] +msg_id = MSG_ID_UI_ADJUST_START_TREATMENT_REQUEST +action = drop +topic = + +[0xA200] +msg_id = MSG_ID_TD_ADJUST_START_TREATMENT_RESPONSE +action = drop +topic = + +[0xA300] +msg_id = MSG_ID_UI_WATER_SAMPLE_RESULT_REQUEST +action = drop +topic = + +[0xA400] +msg_id = MSG_ID_UI_PRESSURE_LIMIT_WIDEN_REQUEST +action = drop +topic = + +[0xA500] +msg_id = MSG_ID_TD_PRESSURE_LIMIT_WIDEN_RESPONSE +action = drop +topic = + +[0xA600] +msg_id = MSG_ID_UI_RECIRCULATE_REQUEST +action = drop +topic = + +[0xA700] +msg_id = MSG_ID_TD_RECIRCULATE_RESPONSE +action = drop +topic = + +[0xA800] +msg_id = MSG_ID_TD_RECIRCULATE_DATA +action = drop +topic = + +[0xA900] +msg_id = MSG_ID_UI_ADJUST_TREATMENT_LOGS_REQUEST +action = drop +topic = + +[0xAA00] +msg_id = MSG_ID_TD_ADJUST_TREATMENT_LOGS_RESPONSE +action = drop +topic = + +[0xAB00] +msg_id = MSG_ID_TD_WATER_SAMPLE_RESULT_RESPONSE +action = drop +topic = + +[0xAC00] +msg_id = MSG_ID_TD_WATER_SAMPLE_DATA +action = drop +topic = + +[0xAD00] +msg_id = MSG_ID_TD_TREATMENT_LOG_AVERAGE_DATA +action = drop +topic = + +[0xAE00] +msg_id = MSG_ID_TD_DRY_SELF_TEST_PROGRESS_DATA +action = drop +topic = + +[0xAF00] +msg_id = MSG_ID_TD_TUBE_SET_AUTHENTICATION_REQUEST +action = drop +topic = + +[0xB000] +msg_id = MSG_ID_TD_TUBE_SET_AUTHENTICATION_ACK_RESPONSE +action = drop +topic = + +[0xB100] +msg_id = MSG_ID_TD_SYRINGE_PUMP_DATA +action = drop +topic = + +[0xB200] +msg_id = MSG_ID_TD_HEPARIN_PAUSE_RESUME_RESPONSE +action = drop +topic = + +[0xB300] +msg_id = MSG_ID_FFU_SIGNAL_TD_UPDATE_AVAILABLE +action = drop +topic = + +[0xB400] +msg_id = MSG_ID_FFU_SIGNAL_DD_UPDATE_AVAILABLE +action = drop +topic = + +[0xB500] +msg_id = MSG_ID_TD_UI_GENERIC_CONFIRMATION_REQUEST +action = drop +topic = + +[0xB600] +msg_id = MSG_ID_UI_GENERIC_CONFIRMATION_RESULT_RESPONSE +action = drop +topic = + +[0xB700] +msg_id = MSG_ID_AVAILABLE_B7 +action = drop +topic = + +[0xB800] +msg_id = MSG_ID_UI_VITALS_ADJUSTMENT_REQUEST +action = drop +topic = + +[0xB900] +msg_id = MSG_ID_TD_VITALS_ADJUSTMENT_RESPONSE +action = drop +topic = + +[0xD600] +msg_id = MSG_ID_UI_SETUP_TUBING_SET_CONNECTIONS_CONFIRM_REQUEST +action = drop +topic = + +[0xD700] +msg_id = MSG_ID_TD_SETUP_TUBING_SET_CONNECTIONS_CONFIRM_RESPONSE +action = drop +topic = + +[0xDB00] +msg_id = MSG_ID_DD_SUBSTITUTION_PUMP_DATA +action = drop +topic = + +[0xDC00] +msg_id = MSG_ID_DD_CONDUCTIVITY_SENSOR_RESISTANCE_DATA +action = drop +topic = + +[0xDD00] +msg_id = MSG_ID_FP_FILTER_FLUSH_DATA +action = drop +topic = + +[0xDE00] +msg_id = MSG_ID_FP_PERMEATE_FLUSH_DATA +action = drop +topic = + +[0xF1FF] +msg_id = MSG_ID_TD_DEBUG_EVENT +action = drop +topic = + +[0xF2FF] +msg_id = MSG_ID_DD_DEBUG_EVENT +action = drop +topic = + +[0xF3FF] +msg_id = MSG_ID_FP_DEBUG_EVENT +action = drop +topic = + +[0xFFFF] +msg_id = MSG_ID_ACK_MESSAGE_THAT_REQUIRES_ACK +action = drop +topic = + Index: LeahiRt/main.cpp =================================================================== diff -u -rfa5e5d703424fbcf84d168c95fa9ca95ec836b06 -ra5781739bcbe58c754aff8861561495624bc5b67 --- LeahiRt/main.cpp (.../main.cpp) (revision fa5e5d703424fbcf84d168c95fa9ca95ec836b06) +++ LeahiRt/main.cpp (.../main.cpp) (revision a5781739bcbe58c754aff8861561495624bc5b67) @@ -66,6 +66,9 @@ LeahiRtController rtController(parser.value(configOption), parser.value(msgHandlingOption)); rtController.connectToAgent(); + if (!rtController.listenForApp()) { + return 1; + } return app.exec(); } Index: docs/SDD/Class_Overview.puml =================================================================== diff -u -r5703cc9be0f77b0fb405d60767a76033d8f9d2cb -ra5781739bcbe58c754aff8861561495624bc5b67 --- docs/SDD/Class_Overview.puml (.../Class_Overview.puml) (revision 5703cc9be0f77b0fb405d60767a76033d8f9d2cb) +++ docs/SDD/Class_Overview.puml (.../Class_Overview.puml) (revision a5781739bcbe58c754aff8861561495624bc5b67) @@ -16,7 +16,7 @@ -_dispatcher: Can::MessageDispatcher -_msgHandling: QHash -_msgCache: QMap> - -_agentInterface: AgentInterface + -_agentInterface: RtInterface -_agentThread: QThread -_txSequence: quint16 __ @@ -53,12 +53,12 @@ -onFrameWritten(qint64) <> } - class AgentInterface { + class RtInterface { -_socket: QLocalSocket -_reconnectTimer: QTimer -_socketPath: QString -_rxBuf: QByteArray - -_rxMsg: AgentMessage + -_rxMsg: RtMessage __ +init(socketPath, reconnectIntervalMs, thread): bool +send(msgId, sequence, payload): bool @@ -74,9 +74,7 @@ -onReadyRead() <> -onReconnectTimer() <> } -} -package "MsgUtils (lib)" { class "Can::MessageDispatcher" as MessageDispatcher { -_messageList: QHash -_rxSequence: Sequence @@ -96,7 +94,7 @@ +enableConsoleOut(bool) } - class AgentMessage { + class RtMessage { -_headerBuf: QByteArray -_rxMsgId: MsgId -_rxSequence: quint16 @@ -111,7 +109,7 @@ +reset() } - enum "AgentMessage::MsgId" as AgentMsgId { + enum "RtMessage::MsgId" as AgentMsgId { ClinicalData = 0x0001 Diagnostic = 0x0002 Ack = 0x0003 @@ -122,7 +120,7 @@ CloudSyncLogFile = 0x0008 } - enum "AgentMessage::FeedResult" as FeedResult { + enum "RtMessage::FeedResult" as FeedResult { Incomplete Complete HeaderError @@ -136,10 +134,10 @@ -_server: QLocalServer -_client: QLocalSocket* -_rxBuf: QByteArray - -_rxMsg: AgentMessage + -_rxMsg: RtMessage __ +listen(): bool - -handleMessage(AgentMessage) + -handleMessage(RtMessage) -logMessage(MsgId, seq, payload) __ -onNewConnection() <> @@ -161,21 +159,21 @@ ' --- composition --- LeahiRtController *-- CanInterface -LeahiRtController *-- AgentInterface +LeahiRtController *-- RtInterface LeahiRtController *-- MessageDispatcher ' --- internal composition --- MessageDispatcher *-- MessageBuilder ' --- usage --- -AgentInterface ..> AgentMessage : uses -AgentSimController ..> AgentMessage : uses +RtInterface ..> RtMessage : uses +AgentSimController ..> RtMessage : uses ' --- enum nesting --- -AgentMessage +-- AgentMsgId -AgentMessage +-- FeedResult +RtMessage +-- AgentMsgId +RtMessage +-- FeedResult ' --- layout hints --- -CanInterface -[hidden]- AgentInterface +CanInterface -[hidden]- RtInterface @enduml Index: docs/SDD/Comms_Overview.puml =================================================================== diff -u -r5703cc9be0f77b0fb405d60767a76033d8f9d2cb -ra5781739bcbe58c754aff8861561495624bc5b67 --- docs/SDD/Comms_Overview.puml (.../Comms_Overview.puml) (revision 5703cc9be0f77b0fb405d60767a76033d8f9d2cb) +++ docs/SDD/Comms_Overview.puml (.../Comms_Overview.puml) (revision a5781739bcbe58c754aff8861561495624bc5b67) @@ -5,7 +5,7 @@ participant "Can::CanInterface" as CANI participant "LeahiRtController" as LRC participant "Can::MessageDispatcher" as MDISP -participant "AgentInterface" as AI +participant "RtInterface" as AI participant "Unix Domain\nSocket" as UDS participant "AgentSimController" as ASC @@ -19,7 +19,7 @@ LRC -> LRC : look up policy in _msgHandling\n(drop / send_always / send_delta) LRC -> AI : send(topic, sequence, protobuf payload) -AI -> UDS : write(AgentMessage frame) +AI -> UDS : write(RtMessage frame) == AgentSim receives == Index: docs/SDD/Seq_RealtimeDataTransfer.puml =================================================================== diff -u -r5703cc9be0f77b0fb405d60767a76033d8f9d2cb -ra5781739bcbe58c754aff8861561495624bc5b67 --- docs/SDD/Seq_RealtimeDataTransfer.puml (.../Seq_RealtimeDataTransfer.puml) (revision 5703cc9be0f77b0fb405d60767a76033d8f9d2cb) +++ docs/SDD/Seq_RealtimeDataTransfer.puml (.../Seq_RealtimeDataTransfer.puml) (revision a5781739bcbe58c754aff8861561495624bc5b67) @@ -25,9 +25,9 @@ LRC -> LRC : discard (no change) else action == send_always OR send_delta with new payload LRC -> LRC : canMessageToProtobufByteArray()\n→ serialised protobuf bytes - LRC -> UDS : AgentMessage frame\n(msgId=topic, seq++, protobuf payload) + LRC -> UDS : RtMessage frame\n(msgId=topic, seq++, protobuf payload) UDS -> ASC : readyRead - ASC -> ASC : AgentMessage::feed() → Complete\nlogMessage() + Envelope parse\n(see Seq_AgentSim for detail) + ASC -> ASC : RtMessage::feed() → Complete\nlogMessage() + Envelope parse\n(see Seq_AgentSim for detail) end @enduml Index: docs/SDD/SoftwareArchitecture.puml =================================================================== diff -u -r5703cc9be0f77b0fb405d60767a76033d8f9d2cb -ra5781739bcbe58c754aff8861561495624bc5b67 --- docs/SDD/SoftwareArchitecture.puml (.../SoftwareArchitecture.puml) (revision 5703cc9be0f77b0fb405d60767a76033d8f9d2cb) +++ docs/SDD/SoftwareArchitecture.puml (.../SoftwareArchitecture.puml) (revision a5781739bcbe58c754aff8861561495624bc5b67) @@ -14,18 +14,19 @@ node "LeahiRt (process)" { component "LeahiRtController" as LRC component "Can::CanInterface" as CAN - component "AgentInterface" as AI + component "RtInterface" as AI } - node "MsgUtils (lib)" { + node "Comms (lib)" { + component "RtInterface\nimpl" as AI_IMPL + component "RtServer\n(local-socket server)" as RS component "Can::MessageDispatcher\n(per-CAN-id reassembly)" as MDISP component "Can::MessageBuilder\n(frame ↔ message)" as MB - component "AgentMessage\n(framing + CRC)" as AM - component "LeahiMsgDefs\n(generated C++)" as MD <> + component "RtMessage\n(framing + CRC)" as AM } - node "Comms (lib)" { - component "AgentInterface\nimpl" as AI_IMPL + node "MsgUtils (lib)" { + component "LeahiMsgDefs\n(generated C++)" as MD <> } interface "CAN Bus\n(SocketCAN)" as CANBUS @@ -38,7 +39,7 @@ node "AgentSim (tool / future Agent)" { component "AgentSimController" as ASC - component "AgentMessage\n(framing + CRC)" as AM2 + component "RtMessage\n(framing + CRC)" as AM2 } cloud "Cloud Service\n(out of scope)" as CLOUD <> Index: leahi-realtime-cdt.pro =================================================================== diff -u --- leahi-realtime-cdt.pro (revision 0) +++ leahi-realtime-cdt.pro (revision a5781739bcbe58c754aff8861561495624bc5b67) @@ -0,0 +1,19 @@ +TEMPLATE = subdirs +CONFIG += ordered + +SUBDIRS += \ + Comms \ + MsgUtils \ + AgentSim \ + CANDumpPlayer \ + LeahiRt + +Comms.subdir = lib/Comms +MsgUtils.subdir = lib/MsgUtils +AgentSim.subdir = tools/AgentSim +CANDumpPlayer.subdir = tools/CANDumpPlayer +LeahiRt.subdir = LeahiRt + +MsgUtils.depends = Comms +AgentSim.depends = Comms MsgUtils +LeahiRt.depends = Comms MsgUtils Index: lib/CMakeLists.txt =================================================================== diff -u -rcfc0df719cb5033078d0cac45ce0f6243810f2e7 -ra5781739bcbe58c754aff8861561495624bc5b67 --- lib/CMakeLists.txt (.../CMakeLists.txt) (revision cfc0df719cb5033078d0cac45ce0f6243810f2e7) +++ lib/CMakeLists.txt (.../CMakeLists.txt) (revision a5781739bcbe58c754aff8861561495624bc5b67) @@ -1,2 +1,2 @@ -add_subdirectory(MsgUtils) add_subdirectory(Comms) +add_subdirectory(MsgUtils) Index: lib/Comms/CMakeLists.txt =================================================================== diff -u -rf9c6b488aa4135e8cd47ccd3fdc6c3ae1cd831aa -ra5781739bcbe58c754aff8861561495624bc5b67 --- lib/Comms/CMakeLists.txt (.../CMakeLists.txt) (revision f9c6b488aa4135e8cd47ccd3fdc6c3ae1cd831aa) +++ lib/Comms/CMakeLists.txt (.../CMakeLists.txt) (revision a5781739bcbe58c754aff8861561495624bc5b67) @@ -7,16 +7,33 @@ 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/AgentInterface.h include/CanInterface.h + include/CanMessage.h + include/crc.h + include/format.h + include/FrameInterface.h + include/main.h + include/MessageBuilder.h + include/MessageDispatcher.h + include/RtInterface.h + include/RtMessage.h + include/RtServer.h + include/types.h ) set(SRCS - src/AgentInterface.cpp src/CanInterface.cpp + src/crc.cpp + src/format.cpp + src/FrameInterface.cpp + src/MessageBuilder.cpp + src/MessageDispatcher.cpp + src/RtInterface.cpp + src/RtMessage.cpp + src/RtServer.cpp + src/types.cpp ) add_library(${PROJECT_NAME} SHARED) @@ -35,14 +52,12 @@ ) 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} @@ -53,7 +68,7 @@ LIBRARY DESTINATION "${CMAKE_CURRENT_SOURCE_DIR}/lib" COMPONENT dev ) -# Add all targets to the build-tree export set +# Export the targets so a sibling component can find this one without installing export( TARGETS ${PROJECT_NAME} FILE "${PROJECT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/${PROJECT_NAME}Targets.cmake" @@ -62,22 +77,22 @@ 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 +# Config files find_package() consumes, written straight to the source cmake/ dir 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 +# Same files staged in the binary dir, for the install() below to copy from 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 the staged config files and the export set. Note the DESTINATIONs are +# absolute paths into the source tree, so `install` writes back to cmake/ here +# rather than to CMAKE_INSTALL_PREFIX. 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.pri =================================================================== diff -u --- lib/Comms/Comms.pri (revision 0) +++ lib/Comms/Comms.pri (revision a5781739bcbe58c754aff8861561495624bc5b67) @@ -0,0 +1,13 @@ +# Usage file for the Comms library. Include/link this library in another using: +# include(/path/to/lib/Comms/Comms.pri) +# +# Defaults to in-tree. +# For an installed copy, COMMS_ROOT is the install PREFIX. +# qmake COMMS_ROOT= ... + +isEmpty(COMMS_ROOT): COMMS_ROOT = $$PWD + +QT *= core network serialbus + +INCLUDEPATH += $$COMMS_ROOT/include +LIBS += -L$$COMMS_ROOT/lib -lComms Index: lib/Comms/Comms.pro =================================================================== diff -u --- lib/Comms/Comms.pro (revision 0) +++ lib/Comms/Comms.pro (revision a5781739bcbe58c754aff8861561495624bc5b67) @@ -0,0 +1,51 @@ +# Communications Library + +TEMPLATE = lib +TARGET = Comms +CONFIG += shared c++20 moc +QT += core network serialbus + +QMAKE_CXXFLAGS += -Wall -Werror -Wextra + +DESTDIR = $$PWD/lib + +HEADERS = \ + include/CanInterface.h \ + include/CanMessage.h \ + include/crc.h \ + include/format.h \ + include/FrameInterface.h \ + include/main.h \ + include/MessageBuilder.h \ + include/MessageDispatcher.h \ + include/RtInterface.h \ + include/RtMessage.h \ + include/RtServer.h \ + include/types.h + +SOURCES = \ + src/CanInterface.cpp \ + src/crc.cpp \ + src/format.cpp \ + src/FrameInterface.cpp \ + src/MessageBuilder.cpp \ + src/MessageDispatcher.cpp \ + src/RtInterface.cpp \ + src/RtMessage.cpp \ + src/RtServer.cpp \ + src/types.cpp + +INCLUDEPATH += $$PWD/include + +isEmpty(PREFIX): PREFIX = $$PWD/../../install + +target.path = $$PREFIX/lib +INSTALLS += target + +export_headers.path = $$PREFIX/include +export_headers.files = $$HEADERS +INSTALLS += export_headers + +export_pri.path = $$PREFIX/lib/Comms +export_pri.files = $$PWD/Comms.pri +INSTALLS += export_pri Fisheye: Tag a5781739bcbe58c754aff8861561495624bc5b67 refers to a dead (removed) revision in file `lib/Comms/include/AgentInterface.h'. Fisheye: No comparison available. Pass `N' to diff? Index: lib/Comms/include/CanMessage.h =================================================================== diff -u --- lib/Comms/include/CanMessage.h (revision 0) +++ lib/Comms/include/CanMessage.h (revision a5781739bcbe58c754aff8861561495624bc5b67) @@ -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/Comms/include/FrameInterface.h =================================================================== diff -u --- lib/Comms/include/FrameInterface.h (revision 0) +++ lib/Comms/include/FrameInterface.h (revision a5781739bcbe58c754aff8861561495624bc5b67) @@ -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/Comms/include/MessageBuilder.h =================================================================== diff -u --- lib/Comms/include/MessageBuilder.h (revision 0) +++ lib/Comms/include/MessageBuilder.h (revision a5781739bcbe58c754aff8861561495624bc5b67) @@ -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/Comms/include/MessageDispatcher.h =================================================================== diff -u --- lib/Comms/include/MessageDispatcher.h (revision 0) +++ lib/Comms/include/MessageDispatcher.h (revision a5781739bcbe58c754aff8861561495624bc5b67) @@ -0,0 +1,202 @@ +/*! + * + * 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.h + * \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.h. +// SQ Only the inbound per-CAN-id frame reassembly path is kept active; the +// SQ GUI/action/acknowledge machinery is commented out (not deleted) so the +// SQ original luis structure stays recognizable and re-enabling stays trivial. +#pragma once + +// Qt +#include +#include + +// Project +// #include "main.h" // Doxygen : do not remove // SQ commented out: luis singleton/logging infra +#include "MessageBuilder.h" +// #include "MessageInterpreter.h" // SQ commented out: GUI message interpretation + +// define +// #define _MessageDispatcher Can::MessageDispatcher::I() // SQ commented out: singleton accessor not used here + +// forward declarations +// class tst_canbus; // SQ commented out: luis test friends +// class tst_acknow; +// class tst_messaging; + +// since this class is the interface between GUI and Can +// it needs to use Gui namespace otherwise it makes code hard to read. +// using namespace Gui; // SQ commented out: no Gui namespace in this project +namespace Can { +/*! + * \brief The MessageDispatcher class \n + * \details Message Dispatcher is the class which is the mediator between CanBus Frames \n + * and Application Messages. \n + * The massages and frames need to be interpreted form/to hex \n + * and also need to be split into frames or constructor from frames to be a message. \n + * ---------------------------------------------------------------------------------------- \n + * \n + * Interpreter : message [ toHex , fromHex] \n + * \n + * Builder : message [ toFrame , fromFrame ] \n + * \n + * Dispatcher : signal->Handler( .. frame .. ) \n + * \n + * Handler : signal->Dispatcher( .. frame .. ) \n + * \n + * ---------------------------------------------------------------------------------------- \n + * \n + * *** UI <-> AppController <-> Dispatcher <-> Handler <-> HD *** \n + * \n + * ---------------------------------------------------------------------------------------- \n + * \n + * UI -> message -> \n + * AppController { \n + * signal->Dispatcher ( .. message .. ) \n + * } \n + * \n + * Dispatcher { \n + * .. \n + * messageList[ch][frameList] += Builder.toFrame ( Interpreter.toHex ( message ) ) \n + * .. \n + * signal->Handler( .. frame .. ) \n + * } \n + * \n + * ---------------------------------------------------------------------------------------- \n + * \n + * HD -> frame -> \n + * Handler { \n + * signal->Dispatcher ( .. frame .. ) \n + * } \n + * \n + * Dispatcher { \n + * messageList[ch][frameList] += Interpreter.fromHex( frame ) \n + * isComplete => Builder.fromFrame( frameList ) : signal->AppController \n + * } \n + * \n + * ---------------------------------------------------------------------------------------- \n + */ +class MessageDispatcher : public QObject +{ + Q_OBJECT + + // Singleton + // SINGLETON(MessageDispatcher) // SQ commented out: not a singleton here, owned by LeahiRtController + + // friends + // friend class ::tst_canbus; // SQ commented out: luis test friends + // friend class ::tst_acknow; + // friend class ::tst_messaging; + + QHash _messageList; // SQ Can_Id -> CanId (this project's enum name) + + Sequence _txSequence = 0; + Sequence _rxSequence = 0; + + MessageBuilder _builder; + // MessageInterpreter _interpreter; // SQ commented out: GUI message interpretation + + // QThread *_thread = nullptr; // SQ commented out: thread owned by LeahiRtController + // bool _init = false; // SQ commented out: init()/thread mgmt removed + + // SQ commented out: list of transmit(request) messages that require AckBack -- GUI/action specific. +#if 0 + // List of the transmit(request) only, messages which require acknowledge back(AckBack). + QList _needsAcknow { + GuiActionType::ID_TDCheckIn , + // ... (full luis list of ~60 GuiActionType ids) ... + GuiActionType::ID_ResetHDInServiceModeReq , + }; +#endif + +// public slots: // SQ commented out: init()/quit() thread lifecycle (controller-managed) +// bool init(); +// bool init(QThread &vThread); +// void quit(); + +public: + explicit MessageDispatcher(QObject *parent = nullptr); // SQ replaced singleton with plain ctor + + // void enableConsoleOut(bool vEnable) { _builder.enableConsoleOut(vEnable); } // SQ commented out: console-out passthrough + +private: + // void initConnections(); // SQ commented out: wiring now done in LeahiRtController + + // void initThread(QThread &vThread); // SQ commented out: thread mgmt removed + // void quitThread(); + + // SQ commented out: outbound action/frame transmit + acknowledge path (GUI/action specific) +#if 0 + void actionTransmit (GuiActionType vActionId, const QVariantList &vData, Sequence vSequence = 0, Can_Id vCanId = Can::Can_Id::eChlid_UI_TD); + void framesTransmit (Can_Id vCan_Id, const FrameList &vFrameList); + + bool needsAcknow (GuiActionType vActionId); + bool needsAcknow (Can_Id vCan_Id); +#endif + + bool buildMessage (CanId vCanId, const QByteArray &vPayload); // SQ Can_Id -> CanId + // bool interpretMessage(const Message &vMessage); // SQ commented out: GUI interpretation + + // SQ commented out: tx/rx sequence counters -- rxCount() kept, txCount() unused here + // Sequence txCount(); // SQ commented out: no outbound path here + Sequence rxCount(); + + // SQ commented out: acknowledge check/transmit (GUI/action specific, needs Message::actionId) +#if 0 + bool checkAcknowReceived(const Message &vMessage, const QString &vSrcText); + bool checkAcknowTransmit(const Message &vMessage, const QString &vSrcText); +#endif + +signals: + // SQ commented out: GUI action-level receive signal (needs GuiActionType) + // void didActionReceive (GuiActionType vAction , const QVariantList &vData); + + /*! + * \brief didActionReceive + * \details Emitted when a complete message has been reassembled from CAN frame(s). + * \param vMessage - the completed message // SQ documented param (luis left it blank) + */ + void didActionReceive (const Message &vMessage); // SQ kept: repurposed as the message-complete signal + + // SQ commented out: acknowledge + outbound-transmit signals (GUI/action specific) +#if 0 + void didAcknowReceive (Sequence vSequence); + void didAcknowTransmit(Can_Id vCan_Id, Sequence vSequence, const FrameList &vFrameList); + void didFrameTransmit (Can_Id vCan_Id, const QByteArray &vPayload); + void didFailedTransmit(Sequence vSequence); +#endif + +public slots: // SQ was private slots in luis; public so controller can connect/forward + // A Frame has been received from CanInterface + void onFrameReceive (CanId vCanId, const QByteArray &vPayload); // SQ Can_Id -> CanId + + // SQ commented out: outbound transmit + action/settings slots (GUI/action specific) +#if 0 + void onFramesTransmit (Can_Id vCan_Id, Sequence vSequence, const FrameList &vFrameList); + void onFailedTransmit ( Sequence vSequence); + + // An Action has been requested to be transmitted. + void onActionTransmit (GuiActionType vActionId, const QVariantList &vData); + + void onSettingsDone (); + + // ---- Signal/Slots + ADJUST_TRANSMT_MODEL_BRIDGE_DEFINITIONS_NOEMIT + ACTION_RECEIVE_MODEL_BRIDGE_DEFINITIONS + + ACTION_RECEIVE_PRIVATE_SLOT(UIPostFinalResultHDRequestData) +#endif + +}; +} Index: lib/Comms/include/RtInterface.h =================================================================== diff -u --- lib/Comms/include/RtInterface.h (revision 0) +++ lib/Comms/include/RtInterface.h (revision a5781739bcbe58c754aff8861561495624bc5b67) @@ -0,0 +1,85 @@ +/*! + * + * Copyright (c) 2024-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 RtInterface.h + * \author (original) Stephen Quong + * \date (original) 24-May-2026 + * + */ +#pragma once + +#include +#include +#include +#include +#include +#include + +#include "RtMessage.h" + +/*! + * \brief UDS interface to the Connectivity Agent + * \details Manages the QLocalSocket connection to the Connectivity Agent + * including automatic reconnection on disconnect or error. + * Outbound: call send() to write an RtMessage frame to the socket. + * Inbound: emits didMessageReceive() for each complete parsed frame. + */ +class RtInterface : public QObject +{ + Q_OBJECT + +public: + RtInterface(QObject *parent = nullptr); + + bool init(const QString &socketPath, int reconnectIntervalMs); + bool init(const QString &socketPath, int reconnectIntervalMs, QThread &thread); + bool send(RtMessage::MsgId msgId, quint16 sequence, const QByteArray &payload = {}); + +public Q_SLOTS: + void quit(); + +Q_SIGNALS: + /*! + * \brief didMessageReceive + * \details Emitted when a complete inbound RtMessage frame has been parsed. + * \param msgId - message identifier from the frame header + * \param sequence - sequence number from the frame header + * \param payload - decoded payload bytes, empty for zero-length frames + */ + void didMessageReceive(RtMessage::MsgId msgId, quint16 sequence, QByteArray payload); + + /*! + * \brief didConnect + * \details Emitted when the socket connects successfully. + */ + void didConnect(); + + /*! + * \brief didDisconnect + * \details Emitted when the socket disconnects. + */ + void didDisconnect(); + +private Q_SLOTS: + void onConnected(); + void onDisconnected(); + void onError(QLocalSocket::LocalSocketError error); + void onReadyRead(); + void onReconnectTimer(); + +private: + void connectToServer(); + void initThread(QThread &thread); + void quitThread(); + + QLocalSocket _socket; + QTimer _reconnectTimer; + QString _socketPath; + QByteArray _rxBuf; + RtMessage _rxMsg; + bool _init = false; +}; Index: lib/Comms/include/RtMessage.h =================================================================== diff -u --- lib/Comms/include/RtMessage.h (revision 0) +++ lib/Comms/include/RtMessage.h (revision a5781739bcbe58c754aff8861561495624bc5b67) @@ -0,0 +1,95 @@ +/*! + * + * Copyright (c) 2024-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 RtMessage.h + * \author (original) Stephen Quong + * \date (original) 24-May-2026 + * + */ +#pragma once + +#include + +/*! + * \brief LeahiRt to Connectivity Agent message framing + * \details Transport-agnostic binary framing. build() makes a wire-ready frame; + * feed() parses inbound bytes (see FeedResult). On Complete, read + * msgId()/sequence()/payload(), then reset(). + * + * Frame layout — header only (payload_length == 0): + * + * Byte: 0 1 2 3 4 5 6-9 10 11 + * ┌─────────┬────────┬────────┬───────────┬────────┐ + * │ AA 55 │ msg_id │ seq_num│ pay_length│hdr_crc │ + * │ sync │uint16BE│uint16BE│ uint32 BE │uint16BE│ + * └─────────┴────────┴────────┴───────────┴────────┘ + * + * Frame layout — with payload (payload_length > 0): + * + * ┌── 12-byte header ──┬── N bytes payload ──┬── pay_crc (4 B) ──┐ + * │ (see above) │ uint8[] │ CRC-32/ISO-HDLC │ + * └────────────────────┴─────────────────────┴───────────────────┘ + * + * Header CRC: CRC-16/CCITT (poly 0x1021, init 0xFFFF, no reflection). + * Payload CRC: CRC-32/ISO-HDLC (IEEE 802.3, reflected poly 0xEDB88320). + */ +class RtMessage +{ +public: + /*! + * \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 { + ClinicalData = 0x0001, + Diagnostic = 0x0002, + Ack = 0x0003, + Alarms = 0x0004, + Audit = 0x0005, + DeviceLogFile = 0x0006, + TreatmentLogFile = 0x0007, + CloudSyncLogFile = 0x0008, + }; + + /*! + * \brief Result returned by feed() after processing each byte chunk + */ + enum class FeedResult { + Incomplete, ///< More bytes needed — continue feeding. + Complete, ///< Full valid frame assembled — read accessors, then call reset(). + HeaderError, ///< Header CRC mismatch — frame dropped, state reset automatically. + PayloadError, ///< Payload CRC mismatch or oversized payload — frame dropped, state reset automatically. + }; + + static QByteArray build(MsgId msgId, quint16 sequence, const QByteArray &payload = {}); + FeedResult feed(QByteArray &bytes); + + MsgId msgId() const; + quint16 sequence() const; + QByteArray payload() const; + + void reset(); + +private: + static quint16 crc16ccitt(const quint8 *data, int len); + static quint32 crc32isohdlc(const quint8 *data, int len); + + static constexpr int SYNC_SIZE = 2; + static constexpr quint8 SYNC[SYNC_SIZE] = {0xAA, 0x55}; + static constexpr int HEADER_SIZE = 12; + static constexpr int MSGID_SIZE = 2; + static constexpr int SEQUENCE_SIZE = 2; + static constexpr int HEADER_CRC_SIZE = 2; + static constexpr int PAYLOAD_CRC_SIZE = 4; + static constexpr quint32 MAX_PAYLOAD_LEN = 64 * 1024; + + QByteArray _headerBuf; + MsgId _rxMsgId = MsgId::ClinicalData; + quint16 _rxSequence = 0; + quint32 _rxPayloadLen = 0; + QByteArray _rxPayload; +}; Index: lib/Comms/include/RtServer.h =================================================================== diff -u --- lib/Comms/include/RtServer.h (revision 0) +++ lib/Comms/include/RtServer.h (revision a5781739bcbe58c754aff8861561495624bc5b67) @@ -0,0 +1,78 @@ +/*! + * + * Copyright (c) 2024-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 RtServer.h + * \author (original) Stephen Quong + * \date (original) 15-Jul-2026 + * + */ +#pragma once + +#include +#include +#include +#include +#include + +#include "RtMessage.h" + +/*! + * \brief Unix domain socket server for RtMessage frames. + * \details Base class providing local-socket server support. Listens on a Unix + * domain socket, accepts one client at a time, parses inbound + * RtMessage frames, and emits didMessageReceive() for each complete + * frame. Outbound: call send() to reply to the connected client. + * Parser state is cleared automatically when the client disconnects. + * + * Intended to be inherited by controllers (e.g. AgentSimController, + * LeahiRtController) that need to accept a connection and exchange + * framed messages over a local socket. + */ +class RtServer : public QObject +{ + Q_OBJECT + +public: + explicit RtServer(QObject *parent = nullptr); + + bool listen(const QString &socketPath); + bool send(RtMessage::MsgId msgId, quint16 sequence, const QByteArray &payload = {}); + bool isConnected() const; + +Q_SIGNALS: + /*! + * \brief didMessageReceive + * \details Emitted when a complete inbound RtMessage frame has been parsed. + * \param msgId - message identifier from the frame header + * \param sequence - sequence number from the frame header + * \param payload - decoded payload bytes, empty for zero-length frames + */ + void didMessageReceive(RtMessage::MsgId msgId, quint16 sequence, QByteArray payload); + + /*! + * \brief didConnect + * \details Emitted when a client connects. + */ + void didConnect(); + + /*! + * \brief didDisconnect + * \details Emitted when the connected client disconnects. + */ + void didDisconnect(); + +private Q_SLOTS: + void onNewConnection(); + void onDisconnected(); + void onReadyRead(); + +private: + QLocalServer _server; + QLocalSocket *_client = nullptr; + QByteArray _rxBuf; + RtMessage _rxMsg; +}; Index: lib/Comms/include/crc.h =================================================================== diff -u --- lib/Comms/include/crc.h (revision 0) +++ lib/Comms/include/crc.h (revision a5781739bcbe58c754aff8861561495624bc5b67) @@ -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/Comms/include/format.h =================================================================== diff -u --- lib/Comms/include/format.h (revision 0) +++ lib/Comms/include/format.h (revision a5781739bcbe58c754aff8861561495624bc5b67) @@ -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/Comms/include/main.h =================================================================== diff -u --- lib/Comms/include/main.h (revision 0) +++ lib/Comms/include/main.h (revision a5781739bcbe58c754aff8861561495624bc5b67) @@ -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/Comms/include/types.h =================================================================== diff -u --- lib/Comms/include/types.h (revision 0) +++ lib/Comms/include/types.h (revision a5781739bcbe58c754aff8861561495624bc5b67) @@ -0,0 +1,186 @@ +/*! * + * 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)]; + }; + + using BOOL = U32; + + + 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; +} Fisheye: Tag a5781739bcbe58c754aff8861561495624bc5b67 refers to a dead (removed) revision in file `lib/Comms/src/AgentInterface.cpp'. Fisheye: No comparison available. Pass `N' to diff? Index: lib/Comms/src/FrameInterface.cpp =================================================================== diff -u --- lib/Comms/src/FrameInterface.cpp (revision 0) +++ lib/Comms/src/FrameInterface.cpp (revision a5781739bcbe58c754aff8861561495624bc5b67) @@ -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/Comms/src/MessageBuilder.cpp =================================================================== diff -u --- lib/Comms/src/MessageBuilder.cpp (revision 0) +++ lib/Comms/src/MessageBuilder.cpp (revision a5781739bcbe58c754aff8861561495624bc5b67) @@ -0,0 +1,470 @@ +/*! + * + * 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) ? (quint8)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 mMsgId = vPayload.mid(0, eLenActionId).toHex().toUInt(0,16); + vPayload = vPayload.mid(eLenActionId); + return mMsgId; +} + +/*! + * \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/Comms/src/MessageDispatcher.cpp =================================================================== diff -u --- lib/Comms/src/MessageDispatcher.cpp (revision 0) +++ lib/Comms/src/MessageDispatcher.cpp (revision a5781739bcbe58c754aff8861561495624bc5b67) @@ -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: lib/Comms/src/RtInterface.cpp =================================================================== diff -u --- lib/Comms/src/RtInterface.cpp (revision 0) +++ lib/Comms/src/RtInterface.cpp (revision a5781739bcbe58c754aff8861561495624bc5b67) @@ -0,0 +1,202 @@ +/*! + * + * Copyright (c) 2024-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 RtInterface.cpp + * \author (original) Stephen Quong + * \date (original) 24-May-2026 + * + */ +#include "RtInterface.h" + +#include +#include +#include + +static void qRegister() { qRegisterMetaType("RtMessage::MsgId"); } +Q_COREAPP_STARTUP_FUNCTION(qRegister) + +/*! + * \brief RtInterface::RtInterface + * \details Constructor + * \param parent - optional QObject parent + */ +RtInterface::RtInterface(QObject *parent) : QObject(parent) +{ + connect(&_socket, &QLocalSocket::connected, this, &RtInterface::onConnected); + connect(&_socket, &QLocalSocket::disconnected, this, &RtInterface::onDisconnected); + connect(&_socket, &QLocalSocket::errorOccurred, this, &RtInterface::onError); + connect(&_socket, &QLocalSocket::readyRead, this, &RtInterface::onReadyRead); + + _reconnectTimer.setSingleShot(false); + connect(&_reconnectTimer, &QTimer::timeout, this, &RtInterface::onReconnectTimer); +} + +/*! + * \brief RtInterface::init + * \details Stores the socket path and reconnect interval and initiates the + * first connection attempt. Guards against double-initialisation. + * \param socketPath - path to the Unix domain socket + * \param reconnectIntervalMs - milliseconds between reconnect attempts + * \return true on success, false if already initialised + */ +bool RtInterface::init(const QString &socketPath, int reconnectIntervalMs) +{ + if (_init) { + return false; + } + _init = true; + + _socketPath = socketPath; + _reconnectTimer.setInterval(reconnectIntervalMs); + connectToServer(); + return true; +} + +/*! + * \brief RtInterface::init + * \details Calls init() then moves this object onto vThread. + * Must be called from the main thread. + * \param socketPath - path to the Unix domain socket + * \param reconnectIntervalMs - milliseconds between reconnect attempts + * \param thread - the thread to move this object onto + * \return true on success, false if already initialised + */ +bool RtInterface::init(const QString &socketPath, int reconnectIntervalMs, QThread &thread) +{ + if (!init(socketPath, reconnectIntervalMs)) { + return false; + } + initThread(thread); + return true; +} + +/*! + * \brief RtInterface::send + * \details Builds an RtMessage frame and writes it to the socket. + * \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 + */ +bool RtInterface::send(RtMessage::MsgId msgId, quint16 sequence, const QByteArray &payload) +{ + if (_socket.state() != QLocalSocket::ConnectedState) { + return false; + } + const QByteArray frame = RtMessage::build(msgId, sequence, payload); + _socket.write(frame); + _socket.flush(); + return true; +} + +/*! + * \brief RtInterface::quit + * \details Moves this object back to the main thread for safe destruction. + */ +void RtInterface::quit() { quitThread(); } + +/*! + * \brief RtInterface::connectToServer + * \details Initiates a connection to the stored socket path. + */ +void RtInterface::connectToServer() { _socket.connectToServer(_socketPath, QLocalSocket::ReadWrite); } + +/*! + * \brief RtInterface::initThread + * \details Moves this object onto vThread and starts it. + * Must be called from the main thread. + * \param thread - the thread to move this object onto + */ +void RtInterface::initThread(QThread &thread) +{ + Q_ASSERT_X(QThread::currentThread() == qApp->thread(), __func__, + "RtInterface::init must be called from the main thread"); + thread.setObjectName(QString("%1_Thread").arg(metaObject()->className())); + connect(qApp, &QCoreApplication::aboutToQuit, this, &RtInterface::quit); + moveToThread(&thread); + thread.start(); +} + +/*! + * \brief RtInterface::quitThread + * \details Moves this object back to the main thread. + */ +void RtInterface::quitThread() +{ + if (QThread::currentThread() != qApp->thread()) { + moveToThread(qApp->thread()); + } +} + +/*! + * \brief RtInterface::onConnected + * \details Stops the reconnect timer and emits didConnect(). + */ +void RtInterface::onConnected() +{ + qDebug().noquote() << "Agent socket connected to" << _socketPath; + _reconnectTimer.stop(); + emit didConnect(); +} + +/*! + * \brief RtInterface::onDisconnected + * \details Clears inbound parser state, starts the reconnect timer, and emits didDisconnect(). + */ +void RtInterface::onDisconnected() +{ + qDebug().noquote() << "Agent socket disconnected — retrying in" << _reconnectTimer.interval() / 1000 << "s"; + _rxBuf.clear(); + _rxMsg.reset(); + _reconnectTimer.start(); + emit didDisconnect(); +} + +/*! + * \brief RtInterface::onError + * \details Logs the socket error and starts the reconnect timer if not already running. + * \param error - the socket error code + */ +void RtInterface::onError(QLocalSocket::LocalSocketError error) +{ + qDebug().noquote() << "Agent socket error:" << _socket.errorString() << QString("(%1)").arg(error); + if (!_reconnectTimer.isActive()) { + _reconnectTimer.start(); + } +} + +/*! + * \brief RtInterface::onReadyRead + * \details Drains incoming bytes through the RtMessage parser and emits + * didMessageReceive() for each complete frame. + */ +void RtInterface::onReadyRead() +{ + _rxBuf.append(_socket.readAll()); + + RtMessage::FeedResult result; + do { + result = _rxMsg.feed(_rxBuf); + if (result == RtMessage::FeedResult::Complete) { + emit didMessageReceive(_rxMsg.msgId(), _rxMsg.sequence(), _rxMsg.payload()); + _rxMsg.reset(); + } + } while (result == RtMessage::FeedResult::Complete && !_rxBuf.isEmpty()); +} + +/*! + * \brief RtInterface::onReconnectTimer + * \details Attempts to reconnect if the socket is in the unconnected state. + */ +void RtInterface::onReconnectTimer() +{ + if (_socket.state() != QLocalSocket::UnconnectedState) { + return; + } + qDebug().noquote() << "Agent socket reconnecting to" << _socketPath; + connectToServer(); +} Index: lib/Comms/src/RtMessage.cpp =================================================================== diff -u --- lib/Comms/src/RtMessage.cpp (revision 0) +++ lib/Comms/src/RtMessage.cpp (revision a5781739bcbe58c754aff8861561495624bc5b67) @@ -0,0 +1,233 @@ +/*! + * + * Copyright (c) 2024-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 RtMessage.cpp + * \author (original) Stephen Quong + * \date (original) 24-May-2026 + * + */ +#include "RtMessage.h" + +#include + +// --------------------------------------------------------------------------- +// Outbound +// --------------------------------------------------------------------------- + +/*! + * \brief RtMessage::build + * \details Builds a complete wire-ready frame with header and optional payload CRCs. + * \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 + */ +QByteArray RtMessage::build(MsgId msgId, quint16 sequence, const QByteArray &payload) +{ + const quint32 payloadLen = static_cast(payload.size()); + + // Header: sync(2) + msg_id(2) + sequence(2) + payload_length(4) + header_crc(2) = 12 bytes + QByteArray msg(HEADER_SIZE, Qt::Uninitialized); + quint8 *header = reinterpret_cast(msg.data()); + + header[0] = SYNC[0]; + header[1] = SYNC[1]; + qToBigEndian(static_cast(msgId), header + SYNC_SIZE); + qToBigEndian(sequence, header + SYNC_SIZE + MSGID_SIZE); + qToBigEndian(payloadLen, header + SYNC_SIZE + MSGID_SIZE + SEQUENCE_SIZE); + + const quint16 hCrc = crc16ccitt(header, HEADER_SIZE - HEADER_CRC_SIZE); + qToBigEndian(hCrc, header + HEADER_SIZE - HEADER_CRC_SIZE); + + if (payloadLen > 0) { + msg.append(payload); + const quint32 crc = crc32isohdlc(reinterpret_cast(payload.constData()), payload.size()); + QByteArray crcBytes(PAYLOAD_CRC_SIZE, Qt::Uninitialized); + qToBigEndian(crc, reinterpret_cast(crcBytes.data())); + msg.append(crcBytes); + } + + return msg; +} + +// --------------------------------------------------------------------------- +// Inbound +// --------------------------------------------------------------------------- + +/*! + * \brief RtMessage::feed + * \details Feeds raw bytes into the inbound parser state machine. + * Consumed bytes are removed from the front of the buffer. + * On HeaderError or PayloadError the caller may call feed() again + * immediately if the buffer is non-empty. + * \param bytes - raw bytes from the transport; modified in-place + * \return FeedResult indicating the parser outcome + */ +RtMessage::FeedResult RtMessage::feed(QByteArray &bytes) +{ + int pos = 0; + FeedResult result = FeedResult::Incomplete; + + // scan for a valid header — skipped when _headerBuf is already populated from a prior feed() call + while (_headerBuf.size() == 0 && bytes.size() - pos >= HEADER_SIZE && result != FeedResult::HeaderError) { + if (static_cast(bytes.at(pos)) == SYNC[0] && static_cast(bytes.at(pos + 1)) == SYNC[1]) { + _headerBuf.append(bytes.constData() + pos, HEADER_SIZE); + if (crc16ccitt(reinterpret_cast(_headerBuf.constData()), HEADER_SIZE - HEADER_CRC_SIZE) == + qFromBigEndian(reinterpret_cast(_headerBuf.constData() + HEADER_SIZE - + HEADER_CRC_SIZE))) + { + const quint8 *header = reinterpret_cast(_headerBuf.constData()); + int header_pos = SYNC_SIZE; + _rxMsgId = static_cast(qFromBigEndian(header + header_pos)); + header_pos += MSGID_SIZE; + _rxSequence = qFromBigEndian(header + header_pos); + header_pos += SEQUENCE_SIZE; + _rxPayloadLen = qFromBigEndian(header + header_pos); + pos += HEADER_SIZE; + } + else { + // TODO: log the header CRC failure + _headerBuf.clear(); + pos += SYNC_SIZE; + result = FeedResult::HeaderError; + } + } + else { + ++pos; + } + } + + // process payload if a valid header has been accumulated + if (result != FeedResult::HeaderError && _headerBuf.size() == HEADER_SIZE) { + if (_rxPayloadLen == 0) { + result = FeedResult::Complete; + } + else if (_rxPayloadLen > MAX_PAYLOAD_LEN) { + // TODO: log the oversized payload + _headerBuf.clear(); + result = FeedResult::PayloadError; + } + else if (bytes.size() - pos >= static_cast(_rxPayloadLen) + PAYLOAD_CRC_SIZE) { + const quint8 *payload = reinterpret_cast(bytes.constData() + pos); + if (crc32isohdlc(payload, static_cast(_rxPayloadLen)) == + qFromBigEndian(payload + _rxPayloadLen)) + { + _rxPayload = QByteArray(reinterpret_cast(payload), static_cast(_rxPayloadLen)); + pos += static_cast(_rxPayloadLen) + PAYLOAD_CRC_SIZE; + result = FeedResult::Complete; + } + else { + // TODO: log the payload CRC failure + _headerBuf.clear(); + result = FeedResult::PayloadError; + } + } + } + + // remove the consumed bytes from the input buffer + bytes.remove(0, pos); + + return result; +} + +/*! + * \brief RtMessage::msgId + * \details Message identifier of the last complete frame. + * Valid only after feed() returns FeedResult::Complete. + * \return MsgId of the last complete frame + */ +RtMessage::MsgId RtMessage::msgId() const +{ + return _rxMsgId; +} + +/*! + * \brief RtMessage::sequence + * \details Sequence number of the last complete frame. + * Valid only after feed() returns FeedResult::Complete. + * \return sequence number of the last complete frame + */ +quint16 RtMessage::sequence() const +{ + return _rxSequence; +} + +/*! + * \brief RtMessage::payload + * \details Payload bytes of the last complete frame. Empty for zero-length frames. + * Valid only after feed() returns FeedResult::Complete. + * \return payload of the last complete frame + */ +QByteArray RtMessage::payload() const +{ + return _rxPayload; +} + +/*! + * \brief RtMessage::reset + * \details Resets the inbound parser to its initial sync-scanning state. + * Must be called after consuming a Complete frame. + */ +void RtMessage::reset() +{ + _headerBuf.clear(); + _rxMsgId = MsgId::ClinicalData; + _rxSequence = 0; + _rxPayloadLen = 0; + _rxPayload.clear(); +} + +/*! + * \brief RtMessage::crc16ccitt + * \details CRC-16/CCITT: poly 0x1021, init 0xFFFF, no reflection, no final XOR. + * Check value: crc16ccitt("123456789", 9) == 0x29B1. + * \param data - input data bytes + * \param len - number of bytes to process + * \return 16-bit CRC value + */ +quint16 RtMessage::crc16ccitt(const quint8 *data, int len) +{ + quint16 crc = 0xFFFF; + for (int i = 0; i < len; ++i) { + crc ^= static_cast(data[i]) << 8; + for (int bit = 0; bit < 8; ++bit) { + if (crc & 0x8000) { + crc = (crc << 1) ^ 0x1021; + } + else { + crc <<= 1; + } + } + } + return crc; +} + +/*! + * \brief RtMessage::crc32isohdlc + * \details CRC-32/ISO-HDLC: reflected poly 0xEDB88320 (IEEE 802.3), init 0xFFFFFFFF, + * input and output reflected, final XOR 0xFFFFFFFF. + * Check value: crc32isohdlc("123456789", 9) == 0xCBF43926. + * \param data - input data bytes + * \param len - number of bytes to process + * \return 32-bit CRC value + */ +quint32 RtMessage::crc32isohdlc(const quint8 *data, int len) +{ + quint32 crc = 0xFFFFFFFF; + for (int i = 0; i < len; ++i) { + crc ^= data[i]; + for (int bit = 0; bit < 8; ++bit) { + if (crc & 1) { + crc = (crc >> 1) ^ 0xEDB88320; + } + else { + crc >>= 1; + } + } + } + return crc ^ 0xFFFFFFFF; +} Index: lib/Comms/src/RtServer.cpp =================================================================== diff -u --- lib/Comms/src/RtServer.cpp (revision 0) +++ lib/Comms/src/RtServer.cpp (revision a5781739bcbe58c754aff8861561495624bc5b67) @@ -0,0 +1,140 @@ +/*! + * + * Copyright (c) 2024-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 RtServer.cpp + * \author (original) Stephen Quong + * \date (original) 15-Jul-2026 + * + */ +#include "RtServer.h" + +#include + +/*! + * \brief RtServer::RtServer + * \details Constructor. Wires the server newConnection signal. + * \param parent Optional QObject parent. + */ +RtServer::RtServer(QObject *parent) : QObject(parent) +{ + connect(&_server, &QLocalServer::newConnection, this, &RtServer::onNewConnection); +} + +/*! + * \brief RtServer::listen + * \details Removes any stale socket file, then starts the server. + * \param socketPath Path to the Unix domain socket. + * \return true on success, false if the server could not bind. + */ +bool RtServer::listen(const QString &socketPath) +{ + QLocalServer::removeServer(socketPath); + if (!_server.listen(socketPath)) { + qCritical().noquote() << metaObject()->className() << ": failed to listen on" << socketPath + << "—" << _server.errorString(); + return false; + } + qInfo().noquote() << metaObject()->className() << ": listening on" << socketPath; + return true; +} + +/*! + * \brief RtServer::send + * \details Builds an RtMessage frame and writes it to the connected client. + * \param msgId Message identifier for the frame header. + * \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 no client is connected. + */ +bool RtServer::send(RtMessage::MsgId msgId, quint16 sequence, const QByteArray &payload) +{ + if (_client == nullptr || _client->state() != QLocalSocket::ConnectedState) { + return false; + } + const QByteArray frame = RtMessage::build(msgId, sequence, payload); + _client->write(frame); + _client->flush(); + return true; +} + +/*! + * \brief RtServer::isConnected + * \details Reports whether a client is currently attached to the server. + * \return true if a client is currently connected. + */ +bool RtServer::isConnected() const +{ + return _client != nullptr; +} + +/*! + * \brief RtServer::onNewConnection + * \details Accepts the pending connection. If a client is already connected the + * new socket is discarded with a warning. + */ +void RtServer::onNewConnection() +{ + if (_client) { + qWarning().noquote() << metaObject()->className() + << ": second connection attempt rejected — already connected"; + _server.nextPendingConnection()->deleteLater(); + return; + } + + _client = _server.nextPendingConnection(); + qInfo().noquote() << metaObject()->className() << ": client connected"; + + connect(_client, &QLocalSocket::readyRead, this, &RtServer::onReadyRead); + connect(_client, &QLocalSocket::disconnected, this, &RtServer::onDisconnected); + + emit didConnect(); +} + +/*! + * \brief RtServer::onDisconnected + * \details Releases the client socket and resets inbound parser state. + */ +void RtServer::onDisconnected() +{ + qInfo().noquote() << metaObject()->className() << ": client disconnected"; + _client->deleteLater(); + _client = nullptr; + _rxBuf.clear(); + _rxMsg.reset(); + + emit didDisconnect(); +} + +/*! + * \brief RtServer::onReadyRead + * \details Appends incoming bytes to the receive buffer and drains it through + * the RtMessage parser, emitting didMessageReceive() for each + * complete frame. + */ +void RtServer::onReadyRead() +{ + _rxBuf.append(_client->readAll()); + + RtMessage::FeedResult result; + do { + result = _rxMsg.feed(_rxBuf); + switch (result) { + case RtMessage::FeedResult::Complete: + emit didMessageReceive(_rxMsg.msgId(), _rxMsg.sequence(), _rxMsg.payload()); + _rxMsg.reset(); + break; + case RtMessage::FeedResult::HeaderError: + qWarning().noquote() << metaObject()->className() << ": header CRC error — frame dropped"; + break; + case RtMessage::FeedResult::PayloadError: + qWarning().noquote() << metaObject()->className() << ": payload CRC error — frame dropped"; + break; + case RtMessage::FeedResult::Incomplete: + break; + } + } while (result == RtMessage::FeedResult::Complete && !_rxBuf.isEmpty()); +} Index: lib/Comms/src/crc.cpp =================================================================== diff -u --- lib/Comms/src/crc.cpp (revision 0) +++ lib/Comms/src/crc.cpp (revision a5781739bcbe58c754aff8861561495624bc5b67) @@ -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/Comms/src/format.cpp =================================================================== diff -u --- lib/Comms/src/format.cpp (revision 0) +++ lib/Comms/src/format.cpp (revision a5781739bcbe58c754aff8861561495624bc5b67) @@ -0,0 +1,231 @@ +/*! + * + * 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( +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + vData.typeId() +#else + vData.type() +#endif + )) { + 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/Comms/src/types.cpp =================================================================== diff -u --- lib/Comms/src/types.cpp (revision 0) +++ lib/Comms/src/types.cpp (revision a5781739bcbe58c754aff8861561495624bc5b67) @@ -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: lib/MsgUtils/CMakeLists.txt =================================================================== diff -u -rf9c6b488aa4135e8cd47ccd3fdc6c3ae1cd831aa -ra5781739bcbe58c754aff8861561495624bc5b67 --- lib/MsgUtils/CMakeLists.txt (.../CMakeLists.txt) (revision f9c6b488aa4135e8cd47ccd3fdc6c3ae1cd831aa) +++ lib/MsgUtils/CMakeLists.txt (.../CMakeLists.txt) (revision a5781739bcbe58c754aff8861561495624bc5b67) @@ -12,49 +12,33 @@ find_package(Protobuf REQUIRED) find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Core) find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Core Network SerialBus) +find_package(Comms HINTS ${CMAKE_CURRENT_SOURCE_DIR}/../Comms REQUIRED) set(MSGUTILS_SCRIPTS_DIR ${CMAKE_SOURCE_DIR}/scripts/MsgUtils) -# The project-wide venv ({CMAKE_BINARY_DIR}/.venv) is set up by -# cmake/PythonVenv.cmake at the top level; ${PROJECT_PYTHON} resolves to its -# interpreter. We install the msgutils package editable so the generator -# scripts below can `import msgutils` regardless of how they're invoked. +# The top-level CMakeLists includes cmake/PythonVenv.cmake, which creates the +# project-wide venv (${CMAKE_BINARY_DIR}/.venv) and sets ${PROJECT_PYTHON}. +# Installing msgutils editable lets the generators below `import msgutils`. include(cmake/MsgUtils.cmake) register_python_package(${MSGUTILS_SCRIPTS_DIR}) # 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(LEAHI_MSG_CSV ${CMAKE_CURRENT_SOURCE_DIR}/../../data/Leahi_Staging_FW_Messages_List.csv) set(LEAHI_MSG_CONF ${CMAKE_CURRENT_SOURCE_DIR}/../../data/LeahiUnhandled.conf) set(INCLUDES - include/AgentMessage.h - 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/AgentMessage.cpp - 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 @@ -102,6 +86,7 @@ ) target_link_libraries(${PROJECT_NAME} PUBLIC + Comms ${Protobuf_LIBRARIES} Qt${QT_VERSION_MAJOR}::Core Qt${QT_VERSION_MAJOR}::Network @@ -124,7 +109,7 @@ LIBRARY DESTINATION "${CMAKE_CURRENT_SOURCE_DIR}/lib" COMPONENT dev ) -# Add all targets to the build-tree export set +# Export the targets so a sibling component can find this one without installing export( TARGETS ${PROJECT_NAME} FILE "${PROJECT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/${PROJECT_NAME}Targets.cmake" @@ -133,22 +118,22 @@ 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 +# Config files find_package() consumes, written straight to the source cmake/ dir 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 +# Same files staged in the binary dir, for the install() below to copy from 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 the staged config files and the export set. Note the DESTINATIONs are +# absolute paths into the source tree, so `install` writes back to cmake/ here +# rather than to CMAKE_INSTALL_PREFIX. 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.pri =================================================================== diff -u --- lib/MsgUtils/MsgUtils.pri (revision 0) +++ lib/MsgUtils/MsgUtils.pri (revision a5781739bcbe58c754aff8861561495624bc5b67) @@ -0,0 +1,15 @@ +# Usage file for the MsgUtils library. A consuming project links MsgUtils with: +# include(/path/to/lib/MsgUtils/MsgUtils.pri) +# +# Defaults to in-tree. +# For an installed copy, MSGUTILS_ROOT is the install PREFIX. +# qmake MSGUTILS_ROOT= ... + +isEmpty(MSGUTILS_ROOT): MSGUTILS_ROOT = $$PWD + +include($$PWD/../Comms/Comms.pri) + +QT *= core network serialbus + +INCLUDEPATH += $$MSGUTILS_ROOT/include +LIBS += -L$$MSGUTILS_ROOT/lib -lMsgUtils -lprotobuf Index: lib/MsgUtils/MsgUtils.pro =================================================================== diff -u --- lib/MsgUtils/MsgUtils.pro (revision 0) +++ lib/MsgUtils/MsgUtils.pro (revision a5781739bcbe58c754aff8861561495624bc5b67) @@ -0,0 +1,168 @@ +# Message Utilities Library + +TEMPLATE = lib +TARGET = MsgUtils +CONFIG += shared c++20 moc +QT += core network serialbus + +QMAKE_CXXFLAGS += -Wall -Werror -Wextra + +DESTDIR = $$PWD/lib + +include(codegen.pri) + +LEAHI_MSG_CONF = $$PWD/../../data/LeahiUnhandled.conf + +GEN_INCLUDE_DIR = $$PWD/include +GEN_SRC_DIR = $$PWD/src +GEN_DATA_DIR = $$OUT_PWD/data + +# ============================================================================= +# Code generation +# ============================================================================= +# 1. Message Definitions +# qmake extra-compilers map one input -> one declared output, so this drives off +# the .conf and declares the primary output; the other three get rules below. +msgdefs.name = leahi_msgdefs +msgdefs.input = LEAHI_MSG_CONF +msgdefs.output = $$GEN_INCLUDE_DIR/LeahiMsgDefs.h +msgdefs.variable_out = HEADERS +msgdefs.commands = \ + $$shell_quote($$PROJECT_PYTHON) \ + $$MSGUTILS_SCRIPTS_DIR/GenerateMsgDefsCpp.py \ + --namespace leahi \ + --header_dir $$GEN_INCLUDE_DIR \ + --source_dir $$GEN_SRC_DIR \ + --proto \ + ${QMAKE_FILE_IN} \ + Leahi +msgdefs.depends = \ + $$MSGUTILS_SCRIPTS_DIR/msgutils/MsgData.py \ + $$MSGUTILS_SCRIPTS_DIR/msgutils/MsgCpp.py \ + $$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 +msgdefs.CONFIG += no_link target_predeps +QMAKE_EXTRA_COMPILERS += msgdefs + +# 2. Protobuf Definitions (.proto) +proto.name = leahi_proto +proto.input = LEAHI_MSG_CONF +proto.output = $$GEN_DATA_DIR/LeahiMsgDefs.proto +proto.commands = \ + $$shell_quote($$PROJECT_PYTHON) \ + $$MSGUTILS_SCRIPTS_DIR/GenerateProtobuf.py \ + --namespace leahi \ + --output_dir $$GEN_DATA_DIR \ + ${QMAKE_FILE_IN} \ + LeahiMsgDefs.proto +proto.depends = \ + $$MSGUTILS_SCRIPTS_DIR/msgutils/MsgData.py \ + $$MSGUTILS_SCRIPTS_DIR/msgutils/MsgProtobuf.py \ + $$MSGUTILS_SCRIPTS_DIR/GenerateProtobuf.py \ + $$MSGUTILS_SCRIPTS_DIR/msgutils/templates/MsgDefs_proto.jinja +proto.CONFIG += no_link target_predeps +QMAKE_EXTRA_COMPILERS += proto + +# 3. Protobuf C++ (.proto -> .pb.h/.pb.cc) +protoc_cpp.name = leahi_protoc +protoc_cpp.input = LEAHI_MSG_CONF +protoc_cpp.output = $$GEN_SRC_DIR/LeahiMsgDefs.pb.cc +protoc_cpp.variable_out = SOURCES +protoc_cpp.commands = \ + $$PROTOC \ + --proto_path $$GEN_DATA_DIR \ + --cpp_out $$OUT_PWD \ + $$GEN_DATA_DIR/LeahiMsgDefs.proto && \ + $$QMAKE_COPY $$OUT_PWD/LeahiMsgDefs.pb.h $$GEN_INCLUDE_DIR/LeahiMsgDefs.pb.h && \ + $$QMAKE_COPY $$OUT_PWD/LeahiMsgDefs.pb.cc $$GEN_SRC_DIR/LeahiMsgDefs.pb.cc && \ + $$QMAKE_DEL_FILE $$OUT_PWD/LeahiMsgDefs.pb.h $$OUT_PWD/LeahiMsgDefs.pb.cc +protoc_cpp.depends = $$GEN_DATA_DIR/LeahiMsgDefs.proto +protoc_cpp.CONFIG += no_link target_predeps +QMAKE_EXTRA_COMPILERS += protoc_cpp + +# 4. additional generated file dependencies +# Each generator above emits several files but can only declare one as output. +# Give the rest of the generated files their own rules, so anything depending +# on them resolves. +pb_header.target = $$GEN_INCLUDE_DIR/LeahiMsgDefs.pb.h +pb_header.depends = $$GEN_SRC_DIR/LeahiMsgDefs.pb.cc +QMAKE_EXTRA_TARGETS += pb_header + +msgdefs_secondary.target = $$GEN_SRC_DIR/LeahiMsgDefs.cpp +msgdefs_secondary.depends = $$GEN_INCLUDE_DIR/LeahiMsgDefs.h +QMAKE_EXTRA_TARGETS += msgdefs_secondary + +protoutils_h.target = $$GEN_INCLUDE_DIR/LeahiMsgProtoUtils.h +protoutils_h.depends = $$GEN_INCLUDE_DIR/LeahiMsgDefs.h +QMAKE_EXTRA_TARGETS += protoutils_h + +protoutils_cpp.target = $$GEN_SRC_DIR/LeahiMsgProtoUtils.cpp +protoutils_cpp.depends = $$GEN_INCLUDE_DIR/LeahiMsgDefs.h +QMAKE_EXTRA_TARGETS += protoutils_cpp + +# ============================================================================= +# Generated headers and sources +# ============================================================================= + +GENERATED_INCLUDES = \ + $$GEN_INCLUDE_DIR/LeahiMsgDefs.pb.h \ + $$GEN_INCLUDE_DIR/LeahiMsgProtoUtils.h + +GENERATED_SRCS = \ + $$GEN_SRC_DIR/LeahiMsgDefs.cpp \ + $$GEN_SRC_DIR/LeahiMsgProtoUtils.cpp + +HEADERS += $$GENERATED_INCLUDES +SOURCES += $$GENERATED_SRCS + +# Create the dependencies between the object files and generated files so generated files +# are made before trying to compile them into objects. +protoutils_obj.target = LeahiMsgProtoUtils.o +protoutils_obj.depends = $$GEN_INCLUDE_DIR/LeahiMsgDefs.pb.h +QMAKE_EXTRA_TARGETS += protoutils_obj + +msgdefs_obj.target = LeahiMsgDefs.o +msgdefs_obj.depends = $$GEN_INCLUDE_DIR/LeahiMsgDefs.h +QMAKE_EXTRA_TARGETS += msgdefs_obj + +pb_obj.target = LeahiMsgDefs.pb.o +pb_obj.depends = $$GEN_INCLUDE_DIR/LeahiMsgDefs.pb.h +QMAKE_EXTRA_TARGETS += pb_obj + +INCLUDEPATH += $$PWD/include + +LIBS += -lprotobuf + +# Comms library +INCLUDEPATH += $$PWD/../Comms/include +LIBS += -L$$PWD/../Comms/lib -lComms + +# ============================================================================= +# Install / export +# ============================================================================= +# `make install` stages the library and the usage .pri into $$PREFIX, so another +# project can include them using the following: +# include($$PREFIX/lib/MsgUtils/MsgUtils.pri) +# +# Override PREFIX during build time using: +# qmake PREFIX= ... +isEmpty(PREFIX): PREFIX = $$PWD/../../install + +target.path = $$PREFIX/lib +INSTALLS += target + +export_pri.path = $$PREFIX/lib/MsgUtils +export_pri.files = $$PWD/MsgUtils.pri +INSTALLS += export_pri + +QMAKE_CLEAN += \ + $$GEN_INCLUDE_DIR/LeahiMsgDefs.h \ + $$GEN_INCLUDE_DIR/LeahiMsgDefs.pb.h \ + $$GEN_INCLUDE_DIR/LeahiMsgProtoUtils.h \ + $$GEN_SRC_DIR/LeahiMsgDefs.cpp \ + $$GEN_SRC_DIR/LeahiMsgDefs.pb.cc \ + $$GEN_SRC_DIR/LeahiMsgProtoUtils.cpp \ + $$GEN_DATA_DIR/LeahiMsgDefs.proto Index: lib/MsgUtils/codegen.pri =================================================================== diff -u --- lib/MsgUtils/codegen.pri (revision 0) +++ lib/MsgUtils/codegen.pri (revision a5781739bcbe58c754aff8861561495624bc5b67) @@ -0,0 +1,30 @@ +# Project Python venv and generator script paths. +# Included by the components that generate code (e.g. MsgUtils and LeahiRt). +# +# qmake evaluates system() at qmake time, while QMAKE_EXTRA_COMPILERS / +# QMAKE_EXTRA_TARGETS run at make time. The venv is created here (once, shared); +# the generators are wired as make-time rules in the including .pro so they +# re-run on input change. + +PROJECT_ROOT = $$clean_path($$PWD/../..) +MSGUTILS_SCRIPTS_DIR = $$PROJECT_ROOT/scripts/MsgUtils + +# BUILD_ROOT is set by the repo-root .qmake.conf — identical for every component +# in the tree, so all components share ONE venv. Fall back to OUT_PWD when built +# without the .qmake.conf in scope (e.g. a leaf .pro copied elsewhere). +isEmpty(BUILD_ROOT): BUILD_ROOT = $$OUT_PWD + +PROJECT_VENV_DIR = $$BUILD_ROOT/.venv +PROJECT_PYTHON = $$PROJECT_VENV_DIR/bin/python + +# Create the venv, then editable-install the msgutils package so the generator +# scripts can `import msgutils`. Runs once per qmake invocation; pip is a skipped +# when already satisfied. +!exists($$PROJECT_PYTHON) { + message("Creating project Python venv at $$PROJECT_VENV_DIR") + system(python3 -m venv $$shell_quote($$PROJECT_VENV_DIR)) +} +system($$shell_quote($$PROJECT_PYTHON) -m pip install --upgrade pip --quiet) +system($$shell_quote($$PROJECT_PYTHON) -m pip install -e $$shell_quote($$MSGUTILS_SCRIPTS_DIR) --quiet) + +PROTOC = protoc Fisheye: Tag a5781739bcbe58c754aff8861561495624bc5b67 refers to a dead (removed) revision in file `lib/MsgUtils/include/AgentMessage.h'. Fisheye: No comparison available. Pass `N' to diff? Fisheye: Tag a5781739bcbe58c754aff8861561495624bc5b67 refers to a dead (removed) revision in file `lib/MsgUtils/include/CanMessage.h'. Fisheye: No comparison available. Pass `N' to diff? Fisheye: Tag a5781739bcbe58c754aff8861561495624bc5b67 refers to a dead (removed) revision in file `lib/MsgUtils/include/FrameInterface.h'. Fisheye: No comparison available. Pass `N' to diff? Fisheye: Tag a5781739bcbe58c754aff8861561495624bc5b67 refers to a dead (removed) revision in file `lib/MsgUtils/include/MessageBuilder.h'. Fisheye: No comparison available. Pass `N' to diff? Fisheye: Tag a5781739bcbe58c754aff8861561495624bc5b67 refers to a dead (removed) revision in file `lib/MsgUtils/include/MessageDispatcher.h'. Fisheye: No comparison available. Pass `N' to diff? Fisheye: Tag a5781739bcbe58c754aff8861561495624bc5b67 refers to a dead (removed) revision in file `lib/MsgUtils/include/crc.h'. Fisheye: No comparison available. Pass `N' to diff? Fisheye: Tag a5781739bcbe58c754aff8861561495624bc5b67 refers to a dead (removed) revision in file `lib/MsgUtils/include/format.h'. Fisheye: No comparison available. Pass `N' to diff? Fisheye: Tag a5781739bcbe58c754aff8861561495624bc5b67 refers to a dead (removed) revision in file `lib/MsgUtils/include/main.h'. Fisheye: No comparison available. Pass `N' to diff? Fisheye: Tag a5781739bcbe58c754aff8861561495624bc5b67 refers to a dead (removed) revision in file `lib/MsgUtils/include/types.h'. Fisheye: No comparison available. Pass `N' to diff? Fisheye: Tag a5781739bcbe58c754aff8861561495624bc5b67 refers to a dead (removed) revision in file `lib/MsgUtils/src/AgentMessage.cpp'. Fisheye: No comparison available. Pass `N' to diff? Fisheye: Tag a5781739bcbe58c754aff8861561495624bc5b67 refers to a dead (removed) revision in file `lib/MsgUtils/src/FrameInterface.cpp'. Fisheye: No comparison available. Pass `N' to diff? Fisheye: Tag a5781739bcbe58c754aff8861561495624bc5b67 refers to a dead (removed) revision in file `lib/MsgUtils/src/MessageBuilder.cpp'. Fisheye: No comparison available. Pass `N' to diff? Fisheye: Tag a5781739bcbe58c754aff8861561495624bc5b67 refers to a dead (removed) revision in file `lib/MsgUtils/src/MessageDispatcher.cpp'. Fisheye: No comparison available. Pass `N' to diff? Fisheye: Tag a5781739bcbe58c754aff8861561495624bc5b67 refers to a dead (removed) revision in file `lib/MsgUtils/src/crc.cpp'. Fisheye: No comparison available. Pass `N' to diff? Fisheye: Tag a5781739bcbe58c754aff8861561495624bc5b67 refers to a dead (removed) revision in file `lib/MsgUtils/src/format.cpp'. Fisheye: No comparison available. Pass `N' to diff? Fisheye: Tag a5781739bcbe58c754aff8861561495624bc5b67 refers to a dead (removed) revision in file `lib/MsgUtils/src/types.cpp'. Fisheye: No comparison available. Pass `N' to diff? Index: tools/AgentSim/AgentSim.pro =================================================================== diff -u --- tools/AgentSim/AgentSim.pro (revision 0) +++ tools/AgentSim/AgentSim.pro (revision a5781739bcbe58c754aff8861561495624bc5b67) @@ -0,0 +1,23 @@ +TEMPLATE = app +TARGET = AgentSim +CONFIG += c++20 +QT += core network +QT -= gui + +CONFIG += moc + +QMAKE_CXXFLAGS += -Wall -Werror -Wextra + +DESTDIR = $$PWD/../bin + +HEADERS = \ + AgentSimController.h + +SOURCES = \ + AgentSimController.cpp \ + main.cpp + +INCLUDEPATH += $$PWD + +# MsgUtils dependency +include($$PWD/../../lib/MsgUtils/MsgUtils.pri) Index: tools/AgentSim/AgentSimController.cpp =================================================================== diff -u -rf9c6b488aa4135e8cd47ccd3fdc6c3ae1cd831aa -ra5781739bcbe58c754aff8861561495624bc5b67 --- tools/AgentSim/AgentSimController.cpp (.../AgentSimController.cpp) (revision f9c6b488aa4135e8cd47ccd3fdc6c3ae1cd831aa) +++ tools/AgentSim/AgentSimController.cpp (.../AgentSimController.cpp) (revision a5781739bcbe58c754aff8861561495624bc5b67) @@ -23,7 +23,7 @@ * \brief AgentSimController::AgentSimController * \details Constructor. Loads the INI configuration and wires the server newConnection signal. * \param configPath Path to the INI configuration file. - * \param parent Optional QObject parent. + * \param parent QObject parent. */ AgentSimController::AgentSimController(const QString &configPath, QObject *parent) : QObject(parent), _settings(configPath, QSettings::IniFormat) @@ -35,7 +35,7 @@ * \brief AgentSimController::listen * \details Reads the socket path from the INI configuration, removes any stale * socket file, then starts the server. - * \return true on success, false if the server could not bind. + * \return true on success, false if the server could not bind to the socket. */ bool AgentSimController::listen() { @@ -85,39 +85,39 @@ /*! * \brief AgentSimController::onReadyRead * \details Appends incoming bytes to the receive buffer and drains it through - * the AgentMessage parser. Logs each complete frame and sends an Ack. + * the RtMessage parser. Logs each complete frame and sends an Ack. */ void AgentSimController::onReadyRead() { _rxBuf.append(_client->readAll()); - AgentMessage::FeedResult result; + RtMessage::FeedResult result; do { result = _rxMsg.feed(_rxBuf); switch (result) { - case AgentMessage::FeedResult::Complete: + case RtMessage::FeedResult::Complete: handleMessage(_rxMsg); _rxMsg.reset(); break; - case AgentMessage::FeedResult::HeaderError: + case RtMessage::FeedResult::HeaderError: qWarning().noquote() << "AgentSim: header CRC error — frame dropped"; break; - case AgentMessage::FeedResult::PayloadError: + case RtMessage::FeedResult::PayloadError: qWarning().noquote() << "AgentSim: payload CRC error — frame dropped"; break; - case AgentMessage::FeedResult::Incomplete: + case RtMessage::FeedResult::Incomplete: break; } - } while (result == AgentMessage::FeedResult::Complete && !_rxBuf.isEmpty()); + } while (result == RtMessage::FeedResult::Complete && !_rxBuf.isEmpty()); } /*! * \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. + * \param msg The complete RtMessage frame. */ -void AgentSimController::handleMessage(const AgentMessage &msg) +void AgentSimController::handleMessage(const RtMessage &msg) { const QByteArray payload = msg.payload(); logMessage(msg.msgId(), msg.sequence(), payload); @@ -173,7 +173,7 @@ * \param sequence Sequence number from the parsed frame. * \param payload Payload bytes of the parsed frame. */ -void AgentSimController::logMessage(AgentMessage::MsgId msgId, quint16 sequence, const QByteArray &payload) +void AgentSimController::logMessage(RtMessage::MsgId msgId, quint16 sequence, const QByteArray &payload) { qInfo().noquote() << QString("AgentSim: rx MsgId=0x%1 seq=%2 payloadLen=%3") .arg(static_cast(msgId), 4, 16, QChar('0')) Index: tools/AgentSim/AgentSimController.h =================================================================== diff -u -rf9c6b488aa4135e8cd47ccd3fdc6c3ae1cd831aa -ra5781739bcbe58c754aff8861561495624bc5b67 --- tools/AgentSim/AgentSimController.h (.../AgentSimController.h) (revision f9c6b488aa4135e8cd47ccd3fdc6c3ae1cd831aa) +++ tools/AgentSim/AgentSimController.h (.../AgentSimController.h) (revision a5781739bcbe58c754aff8861561495624bc5b67) @@ -19,31 +19,18 @@ #include #include -#include "AgentMessage.h" +#include "RtMessage.h" /*! - * \brief Simulates the server side of the LeahiRt ↔ Agent UDS connection. - * \details Listens on a Unix domain socket, accepts one client at a time, parses - * inbound AgentMessage frames, logs them, and replies with an Ack frame. - * Parser state is cleared automatically when the client disconnects. + * \brief Simulates the Agent app that connects the Leahi device to the Cloud System */ class AgentSimController : public QObject { Q_OBJECT public: - /*! - * \brief Construct an AgentSimController. - * \param configPath Path to the INI configuration file. - * \param parent Optional QObject parent. - */ explicit AgentSimController(const QString &configPath, QObject *parent = nullptr); - /*! - * \brief Begin listening on the configured socket path. - * \details Removes any stale socket file before binding. - * \return true on success, false if the server could not bind. - */ bool listen(); private Q_SLOTS: @@ -52,12 +39,12 @@ void onReadyRead(); private: - void handleMessage(const AgentMessage &msg); - void logMessage(AgentMessage::MsgId msgId, quint16 sequence, const QByteArray &payload); + void handleMessage(const RtMessage &msg); + void logMessage(RtMessage::MsgId msgId, quint16 sequence, const QByteArray &payload); QSettings _settings; QLocalServer _server; QLocalSocket *_client = nullptr; QByteArray _rxBuf; - AgentMessage _rxMsg; + RtMessage _rxMsg; }; Index: tools/CANDumpPlayer/CANDumpPlayer.pro =================================================================== diff -u --- tools/CANDumpPlayer/CANDumpPlayer.pro (revision 0) +++ tools/CANDumpPlayer/CANDumpPlayer.pro (revision a5781739bcbe58c754aff8861561495624bc5b67) @@ -0,0 +1,14 @@ +TEMPLATE = app +TARGET = CANDumpPlayer +CONFIG += c++20 +QT += core serialbus +QT -= gui + +QMAKE_CXXFLAGS += -Wall -Werror -Wextra + +DESTDIR = $$PWD/../bin + +SOURCES = \ + main.cpp + +INCLUDEPATH += $$PWD