简体   繁体   中英

C++ Designing a software architecture for a multilayer communication

as you all know, every communication channel has different max data length capabilities. For example the max. Data length in USB HID is 64 byte, or 256 byte for a UART modbus channel. However as a user, i do not want to care about these restrictions. My communication should be able the split large packets in multiple smaller ones (depending on the max data length of the channel). The actual split routine is the same, only the data length per single packet varies.

That's my motivation for writing a generalised communication API.

So let's start with the higher levels:

template<size_t user_data_length>
class CSlave_Com_API: protected CSlave_Com_Mid_level<user_data_length>
{
    /*
     * all of these Functions need elements from CSlave_Com_Mid_level to
     * work correctly
     */
public:

    /**
     * This Function is called by the user thread. It need access to some elements from CSlave_Com_Mid_level.
     * These elements are not shown here to make this problem better understandable
     * 
     * Processes a Received Request and generates the answer. The answer is copied to pRequest as well
     * @param pRequest Pointer to the Request
     * @return true wenn an answer was generated
     */
    bool Process_Request(CRequest* const pRequest);

private:

    /**
     * This Function is called within Process_Request (by the user thread). 
     * It need access to some elements from CSlave_Com_Mid_level.
     * These elements are not shown here to make this problem better understandable
     * 
     * Tells the LL Driver that the answer has been prepared (and stored to pRequest).
     * The answer is no ready to be sent
     * @param pRequest Pointer to the request
     * @return true if the LL Driver has been notified correctly
     */
    bool Send_Answer(CRequest* const pRequest);

    /**
     * This Function is called within Process_Request (by the user thread). 
     * It need access to some elements from CSlave_Com_Mid_level.
     * These elements are not shown here to make this problem better understandable
     * 
     * Tells the LL driver to abort the request because something went wrong
     * @param pRequest Pointer to the Request
     * @return true if the LL driver has been notified correctly
     */
    bool Abort_Request(CRequest* const pRequest);
};

template<size_t user_data_length>
class CSlave_Com_Mid_level
{

public:
    ///This is the structure of a single packet with is sent or received
    struct Single_Packet_T
    {
        //some header Info...
        uint8_t data[user_data_length]; ///< the actual user data
    };

    /**
     * This Function is called by the driver thread. It need access to some elements from CSlave_Com_Mid_level.
     * These elements are not shown here to make this problem better understandable
     * 
     * Called be the LL Driver when new data was received
     * @param rx_packet Received data
     * @return true when the received data could be processed
     */
    bool Process_received_Packet(const Single_Packet_T& rx_packet);

    /**
     * This Function is called by the driver thread. It need access to some elements from CSlave_Com_Mid_level.
     * These elements are not shown here to make this problem better understandable
     * 
     * Called by the LL driver. Stores new data to sent in tx_packet
     * @param tx_packet Storage buffer for the new TX Data
     * @return true when the buffer was filled correctly
     */
    bool Get_Next_Packet_To_Send(Single_Packet_T& tx_packet);

private:
    /// This queue contains all currently processed requests
    QueueHandle_t requests;
};

The idea is that you can initialise an instance of the API for each channel. ie

CSlave_Com_API<64> usb_slave_com;
CSlave_Com_API<256> modbus_slave_com;

Now the LL is obviously very different for every channel. The problem is, that i have 2 different tasks: The high level task is triggered by a user request, the actual hardware communication runs in an own separate task to ensure data is only sent when the hardware is actually ready to send data.

A sample LL class could look like that:

class CSlave_Com_LL_Slave_Com
{
public:
    /**
     *Constructor
     * @param mid_level_reference reference to the used midlevel class instance. This instance is needed to correctly
     *  pass the received data to the higher levels and to get the next data to be send
     */
    CSlave_Com_LL_Slave_Com(const CSlave_Com_Mid_level<message_length>& mid_level_reference);

    ///Destruktor
    ~CSlave_Com_LL_Slave_Com();

private:
    ///Midlevel Reference
    const CSlave_Com_Mid_level<message_length>& mid_level_instance;
};

The problem is: My LL class needed to access the midlevel base of some API instance. However this base is protected. Obviously i could make the mid level base public, but that would mean that the user can (accidentally) access the midlevel layer. I do not want that.

do you have any recommendations how to solve my problem?

thx for your help :)

Update: Sorry I miss understood you, here is the solution, I think you asked for:

#include <string>
#include <iostream>


template<size_t user_data_length>
class CSlave_Com_Mid_level
{

public:
    ///This is the structure of a single packet with is sent or received
    struct Single_Packet_T
    {
        //some header Info...
        uint8_t data[user_data_length]; ///< the actual user data
    };

    /**
     * Called be the LL Driver when new data was received
     * @param rx_packet Received data
     * @return true when the received data could be processed
     */
    bool Process_received_Packet(const Single_Packet_T& rx_packet) const {
        std::cout << "Test\n";
        return false;
    }

    /**
     * Called by the LL driver. Stores new data to sent in tx_packet
     * @param tx_packet Storage buffer for the new TX Data
     * @return true when the buffer was filled correctly
     */
    bool Get_Next_Packet_To_Send(Single_Packet_T& tx_packet);


};

template<size_t user_data_length>
class CSlave_Com_API;

template <size_t message_length>
class CSlave_Com_LL_Slave_Com
{
public:
    /**
     *Constructor
     * @param mid_level_reference reference to the used midlevel class instance. This instance is needed to correctly
     *  pass the received data to the higher levels and to get the next data to be send
     */
    CSlave_Com_LL_Slave_Com(const CSlave_Com_API<message_length>& mid_level_reference)
    : mid_level_instance(mid_level_reference) {
        typename CSlave_Com_Mid_level<message_length>::Single_Packet_T packet{};
        this->mid_level_instance.Process_received_Packet(packet);
    }

    ///Destruktor
    ~CSlave_Com_LL_Slave_Com() = default;

private:
    ///Midlevel Reference
    const CSlave_Com_Mid_level<message_length>& mid_level_instance;
};

template<size_t user_data_length>
class CSlave_Com_API: protected CSlave_Com_Mid_level<user_data_length> {
    friend class CSlave_Com_LL_Slave_Com<user_data_length>;
};

int main()
{
    CSlave_Com_API<42UL> foo{};
    CSlave_Com_LL_Slave_Com<42UL> bar{foo};
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM