Index: CloudConnect/CloudConnectController.cpp =================================================================== diff -u -rc6a4b63a37f3beb1e8a51702ec3a56a1c32cfe8f -r6d2ef8c97f4bb34204e95811839b3995000c47c1 --- CloudConnect/CloudConnectController.cpp (.../CloudConnectController.cpp) (revision c6a4b63a37f3beb1e8a51702ec3a56a1c32cfe8f) +++ CloudConnect/CloudConnectController.cpp (.../CloudConnectController.cpp) (revision 6d2ef8c97f4bb34204e95811839b3995000c47c1) @@ -38,43 +38,27 @@ _canInterface.init(_canThread); connect(&_canInterface, &Can::CanInterface::didFrameReceive, this, &CloudConnectController::onFrameReceive); connect(&_dispatcher, &Can::MessageDispatcher::didActionReceive, this, &CloudConnectController::onMessageReceive); - connect(&_agentInterface, &CloudConnectClient::didDisconnect, this, &CloudConnectController::onAgentDisconnect); } /*! * \brief CloudConnectController::~CloudConnectController - * \details Destructor. Stops and joins the CAN and Agent worker threads. + * \details Destructor. Stops and joins the CAN worker thread. */ CloudConnectController::~CloudConnectController() { _canThread.quit(); _canThread.wait(); - - _agentThread.quit(); - _agentThread.wait(); } /*! - * \brief CloudConnectController::connectToAgent - * \details Initialises the CloudConnectClient using the socket path and reconnect - * interval from the settings file. - */ -void CloudConnectController::connectToAgent() -{ - const QString socketPath = _settings.value("Socket/AgentSocketName", "/tmp/cloudconnect_agent.sock").toString(); - const int reconnectIntervalMs = _settings.value("Socket/ReconnectIntervalMs", 5000).toInt(); - _agentInterface.init(socketPath, reconnectIntervalMs, _agentThread); -} - -/*! * \brief CloudConnectController::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 CloudConnectController::listenForApp() { - const QString appSocketPath = _settings.value("Socket/AppSocketName", "/tmp/cloudconnect_app.sock").toString(); + const QString appSocketPath = _settings.value("Socket/AppSocketName", "/tmp/cloudconnect.sock").toString(); return _appServer.listen(appSocketPath); } @@ -193,20 +177,8 @@ QStringLiteral("test_device"), msg); const quint16 sequence = _txSequence++; - _agentInterface.send(it->topic, sequence, payload); + _appServer.send(it->topic, sequence, payload); received = true; cachedMsg = msg; } - -/*! - * \brief CloudConnectController::onAgentDisconnect - * \details Resets the received flag on all cache entries so that send_delta - * messages are treated as new on the next connection. - */ -void CloudConnectController::onAgentDisconnect() -{ - for (auto &[received, msg] : _msgCache) { - received = false; - } -} Index: CloudConnect/CloudConnectController.h =================================================================== diff -u -rc6a4b63a37f3beb1e8a51702ec3a56a1c32cfe8f -r6d2ef8c97f4bb34204e95811839b3995000c47c1 --- CloudConnect/CloudConnectController.h (.../CloudConnectController.h) (revision c6a4b63a37f3beb1e8a51702ec3a56a1c32cfe8f) +++ CloudConnect/CloudConnectController.h (.../CloudConnectController.h) (revision 6d2ef8c97f4bb34204e95811839b3995000c47c1) @@ -22,7 +22,6 @@ #include #include -#include "CloudConnectClient.h" #include "CloudConnectFrame.h" #include "CanInterface.h" #include "CanMessage.h" @@ -42,7 +41,6 @@ explicit CloudConnectController(const QString &configPath, const QString &msgHandlingPath, QObject *parent = nullptr); ~CloudConnectController(); - void connectToAgent(); bool listenForApp(); Q_SIGNALS: @@ -80,13 +78,10 @@ Can::MessageDispatcher _dispatcher; QMap> _msgCache; QHash _msgHandling; - CloudConnectClient _agentInterface; - QThread _agentThread; CloudConnectServer _appServer; quint16 _txSequence = 0; private Q_SLOTS: void onFrameReceive(const QCanBusFrame frame); void onMessageReceive(const Can::Message &msg); - void onAgentDisconnect(); }; Index: CloudConnect/config/CloudConnect.ini =================================================================== diff -u -rcaca75be9a284ac5f98c078ec47c2e826ac5e980 -r6d2ef8c97f4bb34204e95811839b3995000c47c1 --- CloudConnect/config/CloudConnect.ini (.../CloudConnect.ini) (revision caca75be9a284ac5f98c078ec47c2e826ac5e980) +++ CloudConnect/config/CloudConnect.ini (.../CloudConnect.ini) (revision 6d2ef8c97f4bb34204e95811839b3995000c47c1) @@ -1,4 +1,2 @@ [Socket] -AgentSocketName=/tmp/cloudconnect_agent.sock -AppSocketName=/tmp/cloudconnect_app.sock -ReconnectIntervalMs=5000 +AppSocketName=/tmp/cloudconnect.sock Index: CloudConnect/main.cpp =================================================================== diff -u -rc6a4b63a37f3beb1e8a51702ec3a56a1c32cfe8f -r6d2ef8c97f4bb34204e95811839b3995000c47c1 --- CloudConnect/main.cpp (.../main.cpp) (revision c6a4b63a37f3beb1e8a51702ec3a56a1c32cfe8f) +++ CloudConnect/main.cpp (.../main.cpp) (revision 6d2ef8c97f4bb34204e95811839b3995000c47c1) @@ -65,7 +65,6 @@ CloudConnectController ccController(parser.value(configOption), parser.value(msgHandlingOption)); - ccController.connectToAgent(); if (!ccController.listenForApp()) { return 1; } Index: docs/SDD/Class_Overview.png =================================================================== diff -u -r5703cc9be0f77b0fb405d60767a76033d8f9d2cb -r6d2ef8c97f4bb34204e95811839b3995000c47c1 Binary files differ Index: docs/SDD/Class_Overview.puml =================================================================== diff -u -rcaca75be9a284ac5f98c078ec47c2e826ac5e980 -r6d2ef8c97f4bb34204e95811839b3995000c47c1 --- docs/SDD/Class_Overview.puml (.../Class_Overview.puml) (revision caca75be9a284ac5f98c078ec47c2e826ac5e980) +++ docs/SDD/Class_Overview.puml (.../Class_Overview.puml) (revision 6d2ef8c97f4bb34204e95811839b3995000c47c1) @@ -16,17 +16,15 @@ -_dispatcher: Can::MessageDispatcher -_msgHandling: QHash -_msgCache: QMap> - -_agentInterface: CloudConnectClient - -_agentThread: QThread + -_appServer: CloudConnectServer -_txSequence: quint16 __ +CloudConnectController(configPath, msgHandlingPath) - +connectToAgent() + +listenForApp(): bool -loadMsgHandling(msgHandlingPath) __ -onFrameReceive(QCanBusFrame) -onMessageReceive(Can::Message) - -onAgentDisconnect() } } @@ -61,10 +59,10 @@ -_rxMsg: CloudConnectFrame __ +init(socketPath, reconnectIntervalMs, thread): bool - +send(msgId, sequence, payload): bool + +send(type, sequence, payload): bool +quit() <> __ - +didMessageReceive(MsgId, seq, payload) <> + +didMessageReceive(Type, seq, payload) <> +didConnect() <> +didDisconnect() <> __ @@ -75,6 +73,25 @@ -onReconnectTimer() <> } + class CloudConnectServer { + -_server: QLocalServer + -_client: QLocalSocket* + -_rxBuf: QByteArray + -_rxMsg: CloudConnectFrame + __ + +listen(socketPath): bool + +send(type, sequence, payload): bool + +isConnected(): bool + __ + +didMessageReceive(Type, seq, payload) <> + +didConnect() <> + +didDisconnect() <> + __ + -onNewConnection() <> + -onDisconnected() <> + -onReadyRead() <> + } + class "Can::MessageDispatcher" as MessageDispatcher { -_messageList: QHash -_rxSequence: Sequence @@ -96,56 +113,35 @@ class CloudConnectFrame { -_headerBuf: QByteArray - -_rxMsgId: MsgId + -_rxType: Type -_rxSequence: quint16 -_rxPayloadLen: quint32 -_rxPayload: QByteArray __ - +{static} build(msgId, sequence, payload): QByteArray - +feed(bytes: QByteArray&): FeedResult - +msgId(): MsgId + +{static} build(type, sequence, payload): QByteArray + +read(bytes: QByteArray&): ReadState + +type(): Type +sequence(): quint16 +payload(): QByteArray +reset() } - enum "CloudConnectFrame::MsgId" as AgentMsgId { - ClinicalData = 0x0001 - Diagnostic = 0x0002 - Ack = 0x0003 - Alarms = 0x0004 - Audit = 0x0005 - DeviceLogFile = 0x0006 - TreatmentLogFile = 0x0007 - CloudSyncLogFile = 0x0008 + enum "CloudConnectFrame::Type" as Type { + HighPriority = 0x0001 + NormalPriority = 0x0002 + DeviceLogFile = 0x0003 + TreatmentLogFile = 0x0004 + CloudSyncLogFile = 0x0005 } - enum "CloudConnectFrame::FeedResult" as FeedResult { + enum "CloudConnectFrame::ReadState" as ReadState { Incomplete Complete HeaderError PayloadError } } -package "AgentSim (process)" { - class AgentSimController <> { - -_settings: QSettings - -_server: QLocalServer - -_client: QLocalSocket* - -_rxBuf: QByteArray - -_rxMsg: CloudConnectFrame - __ - +listen(): bool - -handleMessage(CloudConnectFrame) - -logMessage(MsgId, seq, payload) - __ - -onNewConnection() <> - -onDisconnected() <> - -onReadyRead() <> - } -} - package "CANDumpPlayer (process)" { class "CANDumpPlayer\n(main)" <> { +can_interface: string @@ -159,19 +155,19 @@ ' --- composition --- CloudConnectController *-- CanInterface -CloudConnectController *-- CloudConnectClient +CloudConnectController *-- CloudConnectServer CloudConnectController *-- MessageDispatcher ' --- internal composition --- MessageDispatcher *-- MessageBuilder ' --- usage --- +CloudConnectServer ..> CloudConnectFrame : uses CloudConnectClient ..> CloudConnectFrame : uses -AgentSimController ..> CloudConnectFrame : uses ' --- enum nesting --- -CloudConnectFrame +-- AgentMsgId -CloudConnectFrame +-- FeedResult +CloudConnectFrame +-- Type +CloudConnectFrame +-- ReadState ' --- layout hints --- CanInterface -[hidden]- CloudConnectClient Index: docs/SDD/Comms_Overview.png =================================================================== diff -u -r5703cc9be0f77b0fb405d60767a76033d8f9d2cb -r6d2ef8c97f4bb34204e95811839b3995000c47c1 Binary files differ Index: docs/SDD/Comms_Overview.puml =================================================================== diff -u -rcaca75be9a284ac5f98c078ec47c2e826ac5e980 -r6d2ef8c97f4bb34204e95811839b3995000c47c1 --- docs/SDD/Comms_Overview.puml (.../Comms_Overview.puml) (revision caca75be9a284ac5f98c078ec47c2e826ac5e980) +++ docs/SDD/Comms_Overview.puml (.../Comms_Overview.puml) (revision 6d2ef8c97f4bb34204e95811839b3995000c47c1) @@ -5,9 +5,9 @@ participant "Can::CanInterface" as CANI participant "CloudConnectController" as LRC participant "Can::MessageDispatcher" as MDISP -participant "CloudConnectClient" as AI -participant "Unix Domain\nSocket" as UDS -participant "AgentSimController" as ASC +participant "CloudConnectServer" as RS +participant "Unix Domain\nSocket (app)" as UDS +participant "Luis application" as APP == CAN frame → assembled message == @@ -17,19 +17,25 @@ == Message handling & forwarding == -LRC -> LRC : look up policy in _msgHandling\n(drop / send_always / send_delta) -LRC -> AI : send(topic, sequence, protobuf payload) -AI -> UDS : write(CloudConnectFrame frame) +LRC -> LRC : look up policy in _msgHandling\n(Drop / SendAlways / SendDelta) +LRC -> RS : send(topic, sequence, protobuf payload) +RS -> UDS : write(CloudConnectFrame frame) +UDS -> APP : readyRead -== AgentSim receives == +== Cloud delivery (planned — Phase 2/3) == -UDS -> ASC : readyRead → onReadyRead() -ASC -> ASC : feed() → didMessageReceive() +note over LRC + The Connectivity Agent socket has been removed. + The spool/publish pipeline that replaces it + (MessageSpool → Router → MqttPublisher, with + SpoolDrain replaying on reconnect) is not yet + implemented — see docs/SDD/AgentMigrationPlan.md. +end note == Connection events == -AI -> LRC : didConnect()\n→ (future) -AI -> LRC : didDisconnect()\n→ onAgentDisconnect()\n resets _msgCache received flags -AI -> LRC : didMessageReceive()\n→ (future inbound) +RS -> LRC : didConnect()\n→ (future) serial-number handshake +RS -> LRC : didDisconnect()\n→ (future) +RS -> LRC : didMessageReceive()\n→ (future inbound) @enduml Index: docs/SDD/Seq_RealtimeDataTransfer.png =================================================================== diff -u -r5703cc9be0f77b0fb405d60767a76033d8f9d2cb -r6d2ef8c97f4bb34204e95811839b3995000c47c1 Binary files differ Index: docs/SDD/Seq_RealtimeDataTransfer.puml =================================================================== diff -u -rcaca75be9a284ac5f98c078ec47c2e826ac5e980 -r6d2ef8c97f4bb34204e95811839b3995000c47c1 --- docs/SDD/Seq_RealtimeDataTransfer.puml (.../Seq_RealtimeDataTransfer.puml) (revision caca75be9a284ac5f98c078ec47c2e826ac5e980) +++ docs/SDD/Seq_RealtimeDataTransfer.puml (.../Seq_RealtimeDataTransfer.puml) (revision 6d2ef8c97f4bb34204e95811839b3995000c47c1) @@ -5,8 +5,9 @@ participant "CANDumpPlayer" as CDP participant "CAN Bus\n(SocketCAN)" as CANBUS participant "CloudConnect\n(CloudConnectController)" as LRC -participant "Unix Domain\nSocket" as UDS -participant "AgentSim\n(AgentSimController)" as ASC +participant "CloudConnectServer" as RS +participant "Luis application" as APP +participant "Cloud pipeline\n(planned)" as PIPE note over CDP Replays a candump log onto SocketCAN @@ -19,15 +20,34 @@ LRC -> LRC : MessageDispatcher reassembles\nmulti-frame Can::Message\n(see Seq_CANToCloud for detail) -alt action == drop (or msgId not in _msgHandling) +alt action == Drop (or msgId not in _msgHandling) LRC -> LRC : discard -else action == send_delta AND payload unchanged\n(cached data.chopped(1) == new data.chopped(1)) +else action == SendDelta AND payload unchanged\n(cached data.chopped(1) == new data.chopped(1)) LRC -> LRC : discard (no change) -else action == send_always OR send_delta with new payload +else action == SendAlways OR SendDelta with new payload LRC -> LRC : canMessageToProtobufByteArray()\n→ serialised protobuf bytes - LRC -> UDS : CloudConnectFrame frame\n(msgId=topic, seq++, protobuf payload) - UDS -> ASC : readyRead - ASC -> ASC : CloudConnectFrame::feed() → Complete\nlogMessage() + Envelope parse\n(see Seq_AgentSim for detail) + + LRC -> RS : send(topic, seq++, protobuf payload) + RS -> APP : CloudConnectFrame frame over\n/tmp/cloudconnect.sock + + LRC -->> PIPE : route → spool → publish end +note over PIPE + NOT YET IMPLEMENTED. + + The Connectivity Agent process and its UDS have been + removed. The replacement in-process pipeline is: + + Router → resolve MQTT topic from Type, + build Envelope + MessageSpool → durable insert BEFORE publish + MqttPublisher → QoS 1 PUBLISH to AWS IoT Core + → PUBACK → MessageSpool::remove() + SpoolDrain → replay spooled messages on reconnect + + Until this lands, nothing is sent to the cloud. + See docs/SDD/AgentMigrationPlan.md Phases 2-3. +end note + @enduml Index: docs/SDD/SoftwareArchitecture.png =================================================================== diff -u -r5703cc9be0f77b0fb405d60767a76033d8f9d2cb -r6d2ef8c97f4bb34204e95811839b3995000c47c1 Binary files differ Index: docs/SDD/SoftwareArchitecture.puml =================================================================== diff -u -rcaca75be9a284ac5f98c078ec47c2e826ac5e980 -r6d2ef8c97f4bb34204e95811839b3995000c47c1 --- docs/SDD/SoftwareArchitecture.puml (.../SoftwareArchitecture.puml) (revision caca75be9a284ac5f98c078ec47c2e826ac5e980) +++ docs/SDD/SoftwareArchitecture.puml (.../SoftwareArchitecture.puml) (revision 6d2ef8c97f4bb34204e95811839b3995000c47c1) @@ -5,20 +5,28 @@ skinparam component { BackgroundColor<> LightYellow BackgroundColor<> LightGray + BackgroundColor<> #E8E8F8 + BorderStyle<> dashed } title Leahi Real-time CDT — Component Architecture +note as N1 + The Connectivity Agent process and its Unix domain socket + have been removed; CloudConnect is becoming the single + conduit to the cloud (docs/SDD/AgentMigrationPlan.md). + Dashed = planned, not yet implemented. +end note + node "Leahi Device" { node "CloudConnect (process)" { component "CloudConnectController" as LRC component "Can::CanInterface" as CAN - component "CloudConnectClient" as AI + component "Cloud pipeline\n(MessageSpool, Router,\nMqttPublisher, SpoolDrain)" as PIPE <> } node "Comms (lib)" { - component "CloudConnectClient\nimpl" as AI_IMPL component "CloudConnectServer\n(local-socket server)" as RS component "Can::MessageDispatcher\n(per-CAN-id reassembly)" as MDISP component "Can::MessageBuilder\n(frame ↔ message)" as MB @@ -30,20 +38,22 @@ } interface "CAN Bus\n(SocketCAN)" as CANBUS - interface "Unix Domain Socket\n(/tmp/cloudconnect_agent.sock)" as UDS + interface "Unix Domain Socket\n(/tmp/cloudconnect.sock)" as APPUDS } +node "Luis application" { + component "Application client" as APP +} + node "CANDumpPlayer (tool)" { component "CAN dump replay\n(writeFrame → SocketCAN)" as CDP } -node "AgentSim (tool / future Agent)" { - component "AgentSimController" as ASC - component "CloudConnectFrame\n(framing + CRC)" as AM2 +cloud "AWS Cloud" as CLOUD <> { + component "AWS IoT Core\n(MQTT broker)" as IOTCORE <> + component "Amazon S3\n(log bundles)" as S3 <> } -cloud "Cloud Service\n(out of scope)" as CLOUD <> - node "Build-time (scripts)" { component "MsgUtils Python\n(msgutils)" as PYUTILS <> component "Jinja2 Templates\n(MsgDefs_*.jinja)" as JINJA <> @@ -62,14 +72,14 @@ MDISP --> LRC : didActionReceive(Can::Message) LRC --> MD : canMessageToProtobufByteArray() MD --> AM : protobuf bytes -LRC --> AI : send(MsgId, seq, payload) -AI --> AI_IMPL -AI_IMPL --> AM : build() -AI_IMPL --> UDS : write frame +LRC --> RS : send(Type, seq, payload) +RS --> AM : build() +RS --> APPUDS : write frame +APPUDS --> APP : readyRead -UDS --> ASC : readyRead -ASC --> AM2 : feed() -ASC --> CLOUD : (future) forward payload +LRC ..> PIPE : route + spool (planned) +PIPE ..> IOTCORE : PUBLISH QoS 1 (mTLS 8883) +PIPE ..> S3 : multipart upload (HTTPS) PYUTILS --> JINJA : renders JINJA --> PROTO : generates Index: leahi-realtime-cdt.pro =================================================================== diff -u -rb915ccc1a72fcca21908c35140af7ac7df1f1f37 -r6d2ef8c97f4bb34204e95811839b3995000c47c1 --- leahi-realtime-cdt.pro (.../leahi-realtime-cdt.pro) (revision b915ccc1a72fcca21908c35140af7ac7df1f1f37) +++ leahi-realtime-cdt.pro (.../leahi-realtime-cdt.pro) (revision 6d2ef8c97f4bb34204e95811839b3995000c47c1) @@ -4,16 +4,13 @@ SUBDIRS += \ Comms \ MsgUtils \ - AgentSim \ CANDumpPlayer \ CloudConnect Comms.subdir = lib/Comms MsgUtils.subdir = lib/MsgUtils -AgentSim.subdir = tools/AgentSim CANDumpPlayer.subdir = tools/CANDumpPlayer CloudConnect.subdir = CloudConnect MsgUtils.depends = Comms -AgentSim.depends = Comms MsgUtils CloudConnect.depends = Comms MsgUtils Fisheye: Tag 6d2ef8c97f4bb34204e95811839b3995000c47c1 refers to a dead (removed) revision in file `tools/AgentSim/AgentSim.pro'. Fisheye: No comparison available. Pass `N' to diff? Fisheye: Tag 6d2ef8c97f4bb34204e95811839b3995000c47c1 refers to a dead (removed) revision in file `tools/AgentSim/AgentSimController.cpp'. Fisheye: No comparison available. Pass `N' to diff? Fisheye: Tag 6d2ef8c97f4bb34204e95811839b3995000c47c1 refers to a dead (removed) revision in file `tools/AgentSim/AgentSimController.h'. Fisheye: No comparison available. Pass `N' to diff? Fisheye: Tag 6d2ef8c97f4bb34204e95811839b3995000c47c1 refers to a dead (removed) revision in file `tools/AgentSim/CMakeLists.txt'. Fisheye: No comparison available. Pass `N' to diff? Fisheye: Tag 6d2ef8c97f4bb34204e95811839b3995000c47c1 refers to a dead (removed) revision in file `tools/AgentSim/config/AgentSim.ini'. Fisheye: No comparison available. Pass `N' to diff? Fisheye: Tag 6d2ef8c97f4bb34204e95811839b3995000c47c1 refers to a dead (removed) revision in file `tools/AgentSim/main.cpp'. Fisheye: No comparison available. Pass `N' to diff? Index: tools/CMakeLists.txt =================================================================== diff -u -rf9c6b488aa4135e8cd47ccd3fdc6c3ae1cd831aa -r6d2ef8c97f4bb34204e95811839b3995000c47c1 --- tools/CMakeLists.txt (.../CMakeLists.txt) (revision f9c6b488aa4135e8cd47ccd3fdc6c3ae1cd831aa) +++ tools/CMakeLists.txt (.../CMakeLists.txt) (revision 6d2ef8c97f4bb34204e95811839b3995000c47c1) @@ -1,4 +1,3 @@ -add_subdirectory(AgentSim) if(UNIX AND NOT APPLE) add_subdirectory(CANDumpPlayer) endif()