/*! * * 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 CloudConnectController.h * \author (original) Stephen Quong * \date (original) 24-May-2026 * */ #pragma once #include #include #include #include #include #include #include #include #include "CanInterface.h" #include "CanMessage.h" #include "MessageDispatcher.h" #include "MqttClient.h" // BEGIN: Protobuf capture #include #include // END: Protobuf capture // BEGIN: Mesg Stats #include // END: Mesg Stats using namespace Can; /*! * \brief CloudConnect controller */ class CloudConnectController : public QObject { Q_OBJECT public: explicit CloudConnectController(QObject *parent = nullptr); ~CloudConnectController(); bool loadConfig(const QString &configPath); void initThread(QThread &thread); bool startCan(); bool connectToCloud(); private: /*! * \brief CAN message routing actions */ enum class CanAction { Drop, SendAlways, SendDelta, }; enum class MqttTopic : quint16 { HighPriority = 0x0001, NormalPriority = 0x0002, DeviceLogFile = 0x0003, TreatmentLogFile = 0x0004, CloudSyncLogFile = 0x0005, }; /*! * \brief CAN message routing entry loaded from CAN handling INI. */ struct CanRouting { CanAction action = CanAction::Drop; MqttTopic topic = MqttTopic::NormalPriority; }; bool loadCanRouting(const QString &canRoutingPath); QString mqttTopic(MqttTopic topic) const; Can::CanInterface _canInterface; Can::MessageDispatcher _dispatcher; QMap> _canCache; QHash _canRouting; MqttClient _mqttClient; QString _topicPrefix; // TODO: define in INI? QString _deviceId; // TODO: this needs to be sent from Leahi app or retrieved from somewhere // BEGIN: Protobuf capture void captureProtobuf(const QByteArray &payload); QPointer _captureSerFile; QPointer _captureJsonFile; // END: Protobuf capture // BEGIN: Mesg Stats struct MsgStats { quint64 published = 0; ///< handed to the broker quint64 echoed = 0; ///< came back on our own subscription and matched a publish quint64 stale = 0; ///< came back but matched no in-flight publish quint64 expired = 0; ///< published, never echoed within the echo window }; struct Inflight { enum Status { Unknown, Sent, ACK, Published, }; Status status = Unknown; Can::MsgId msgId = 0; Can::Sequence sequence = 0; qint64 sentUs = 0; ///< _msgTimer reading when the publish was handed to the broker }; QString canStats(Can::MsgId msgId, qint64 sinceLastUs) const; qint64 msgElapsedUs(); QString msgStats(const QString &label, Can::MsgId msgId, qint16 sequence, const QString &latencyLabel, qint64 latencyUs, qint64 avgUs) const; quint64 _canCount = 0; qint64 _canAvgTime = 0; qint64 _canTotalTime = 0; qint64 _canLastRecv = 0; QElapsedTimer _canTimer; QElapsedTimer _msgTimer; QHash _inflight; quint64 _msgSentCount = 0; quint64 _msgAckCount = 0; quint64 _msgRecvCount = 0; quint64 _msgRecvMatchCount = 0; qint64 _msgAckTotalTime = 0; qint64 _msgRecvTotalTime = 0; qint64 _msgAckAvgTime = 0; qint64 _msgRecvAvgTime = 0; // END: Mesg Stats private Q_SLOTS: void onCanFrameReceive(const QCanBusFrame &frame); void onCanMessageReceive(const Can::Message &msg); void onCloudStateChanged(QMqttClient::ClientState state); void onCloudMessageStatusChanged(qint32 id, QMqtt::MessageStatus status, const QMqttMessageStatusProperties &properties); void onCloudMessageReceived(const QByteArray &message, const QMqttTopicName &topic); };