Search code examples
cgccenumerated-types

using different enumerated type in different levels


Though enums are integer constants, I have issue with usage/organizing. I have organized serial data reception into three levels. Lowest level0 is an interrupt service routine, above that I have a function called receiveData() in level 1 and above that I have written processReceivedFrame() in level2. I have two enumerated types, one for level 0 and level 1 and the other is for level 2. Similarly one enum type for transmission.

   enum FRAME_RECEIVE_STATUS
    {
        FR_STS_FRAME_NOT_RECEIVED=0,            // Initial state by default
        FRAME_RECEIVED,                 // Indicates that frame is received by interrupt vector

        FIRST_START_BYTE_RECVD,        // Set as soon as first start byte is received in case of multiple start bytes
        RECV_PROGRESS,                 //Indicates that the receiving is in progress
        INVALID_DATA_LENGTH,        // This status is updated if length byte is invalid. We are verifying only against minimum possible length.
        RX_BUFFER_FULL,             // ISR will update this status in case buffer is full
        INVALID_START_BYTE,         // This will be set when first start byte is received, but second byte is incorrect.
        FIRST_END_BYTE_RECEIVED,    // Set as soon as first end byte is received.
       };

    enum FRAME_PROCESS_STATUS
    {
        FRAME_VALID=100,
        FP_STS_FRAME_NOT_RECEIVED,
        CHECKSUM_SUCCESS,               //
        CHECKSUM_ERROR,
    };
enum TRANSMIT_STATES
{
    FRAME_SEND_SUCCESS=0,
    FRAME_SEND_RESPONSE_TIMEOUT,
    FRAME_SEND_RESP_WAITING,
    INVALID_RESPONSE_BYTE,
    EXCEEDED_MAX_ATTEMPTS,
    TRANSMIT_BUFFER_FULL,
};

But now I feel I have difficulty if I go for different enumerated types. Especially in level1, I end up in returning level two enum type, based on certain condition. In some places I get warnings if different type is used.

So usually what is the best way of using enumerated type for error handling? May be a single enumerated type for related functions such as one for reception (RECEIVE_STATES) and one for transmission (TRANSMIT_STATES)? But even in this case, we may end up in mixing differet types. For example, assume that I am transmitting some data and then waiting for response. The response may be RECEIVE_BUFFER_FULL of type enum RECEIVE_STATES. If the function return type is of enum TRANSMIT_STATES, but gets the enum of RECEIVE_STATES during the process, we may again have issues and handle that in code by replacing enum element of appropriate type. For example:-

enum TRANSMIT_STATES sendDataByte(char );     // Forward declaration.
enum RECEIVE_STATES receiveData(char *);  // Forward declaration.
enum TRANSMIT_STATES transmitData(char *data)
{
     for(.....)
     {
     enum TRANSMIT_STATES t_Status = sendDataByte(*(data+i));   // Transmit the data
      if(t_Status==TRANSMIT_BUFFER_FULL)
       return(t_Status);
     }
     // Wait for the response
     enum RECEIVE_STATES r_Status = receiveData(data);
     // Alternatively, t_Status = receiveData(data);
     if(rStatus==RX_BUFFER_FULL)
     t_Status=RX_BUFFER_FULL;  // Assigning different type
     return(t_Status);
}

In above code, if I go for:-

t_Status = receiveData(); 

I have issue of assigning different types. Even in case of:-

r_Status = receiveData(data);

I have to check the status and return appropriate code from TRANSMIT_STATES. I cannot have same enum element in two different types. So maintaining with different names is also an issue. So in such case, one combined enum for all types of errors is suggested? But if we use some standard libraries for lower levels and those libraries may use different types of enums or normal int values. Here also I am not sure what is the recommended practice.


Solution

  • The simplest is to combine all the enums into one. A slightly more sophisticated approach is to use a "tagged union" aka variant:

    enum ERROR_TYPE {
        SUCCESS = 0,
        TRANSMIT,
        RECEIVE
    };
    struct result_t {
        enum ERROR_TYPE err; // 0 means no error, i.e. success
        union {
            enum TRANSMIT_STATES err_send; // err 1
            enum RECEIVE_STATES err_recv; // err 2
        };
    };
    

    Then your code looks like this:

    struct result_t transmitData(char *data)
    {
        struct result_t result = {SUCCESS};
        for(...)
        {
            result.err_send = sendDataByte(*(data+i));   // Transmit the data
            if(result.err_send==TRANSMIT_BUFFER_FULL) {
                result.err = TRANSMIT;
                return(result);
            }
        }
        // Wait for the response
        result.err_recv = receiveData(data);
        if(result.err_recv==RX_BUFFER_FULL) {
            result.err = RECEIVE;
        }
        return(result);
    }