Menu

Managing Events for Orthogonal Components

2021-07-27
2021-08-03
  • Artem Dovhal

    Artem Dovhal - 2021-07-27

    Hello, Miro!

    I'm trying to simplify the creation of "orthogonal component".
    I was faced with the need to break the base way of inheritance within your framework for events creation.

    struct MyEvt_s {
            QHsm*                                   owner;  /*!< inherits ::DeviceEvt */
            QEvt                                    super;  /*!< inherits ::QEvt */
            bool                                    status; /*!< event data */
        } MyEvt;
    

    In such case, QEvt is based as second filed of struct.

    I want to use Q_NEW and QF_gc to create events dynamically, but these functions require QEvt as first element to work correctly.

    Q_NEW fills the event signal, pool id and reference counter from the start of mem block returned by mem pool, so that data will be written to owner field, and that is incorrect. For the Q_NEW I can workaround by using "custom event constructor" and simple memory move to move QEvt data filled by Q_NEW from the start of struct to the place, where the QEvt data must be located.

    MyEvt* MyEvt_ctor(MyEvt* const e, enum_t sig) {
        memcpy(&e->super, e, sizeof(e->super));
    
        return e;
    }
    

    And then I have to clean the event properly. QF_gc function requires the QEvt address (QEvt*) as argument to process the memory cleanup. And also QF_gc function requires the QEvt address as the start address of memory block.

    To workaround last requirement I suggest to add simple fix into QMPool_put:

    b = &((uint8_t*)b)[-(((unsigned int)b) % me->blockSize)];
    
    void QMPool_put(QMPool * const me, void *b, uint_fast8_t const qs_id) {
        QF_CRIT_STAT_
    
        /** @pre # free blocks cannot exceed the total # blocks and
        * the block pointer must be from this pool.
        */
        Q_REQUIRE_ID(200, (me->nFree < me->nTot)
                          && QF_PTR_RANGE_(b, me->start, me->end));
    
        (void)qs_id; /* unused parameter (outside Q_SPY build configuration) */
    
        QF_CRIT_E_();
        /* Fix: Move to the start address of current block */
        b = &((uint8_t*)b)[-(((unsigned int)b) % me->blockSize)];
        /* Fix */
        ((QFreeBlock *)b)->next = (QFreeBlock *)me->free_head;/* link into list */
        me->free_head = b;      /* set as new head of the free list */
        ++me->nFree;            /* one more free block in this pool */
    
        QS_BEGIN_NOCRIT_PRE_(QS_QF_MPOOL_PUT, qs_id)
            QS_TIME_PRE_();         /* timestamp */
            QS_OBJ_PRE_(me);        /* this memory pool */
            QS_MPC_PRE_(me->nFree); /* the number of free blocks in the pool */
        QS_END_NOCRIT_PRE_()
    
        QF_CRIT_X_();
    }
    

    What do you think about it?

     
  • Quantum Leaps

    Quantum Leaps - 2021-07-27

    This obviously destroys the inheritance relationship between your MyEvt and the QEvt base class, and therefore it breaks everything else.

    It is not unusual for the events intended for "Orthogonal Components" to carry the ID of the component. But this is typically the payload (event parameters) and I don't understand why you need to put it necessarily as the first member of your event subclass.

    What do you think about it?

    For these reasons, I can't really like it, can I? I would really look hard for alternatives before doing something like that. This includes foregoing QP entirely and going with something else, because if you do this, you might just as well throw the whole framework out.

    --MMS

     
  • Artem Dovhal

    Artem Dovhal - 2021-07-28

    It is not unusual for the events intended for "Orthogonal Components" to carry the ID of the component. But this is typically the payload (event parameters) and I don't understand why you need to put it necessarily as the first member of your event subclass.

    I don't really want to use a large number of switches to determine which of the orthogonal HSM belongs to a particular event. I want to encapsulate each HSM (orthogonal component) as reusable module, wich can be used inside other many other AO's. Also, I do not want to reveal the details of the signals that exist in each of the modules (QSignal enum for each component is private and defined in .c file)

    I can't follow the rule of inheritance "QEvt first" and put the QHsm* owner as last field. Because resultant size of QEvt inheritors may be different (for example QTimeEvt). So I took exactly this approach.

    If at any time I need to extend my orthogonal component with an additional module, I do not have to change the behavior of the main event handler.

    In my case, the code for the main event handler is always the same. OrthogonalDispatcher_Dispatch Function

    typedef struct OrthogonalEvt_s {
        QHsm* owner;
    
    } OrthogonalEvt;
    
    #define Q_ORTHOGONAL_EVT_GET_HSM() \
        (*(QHsm**)&(((uint8_t*)(e))[-(sizeof(OrthogonalEvt)-offsetof(OrthogonalEvt, owner))]))
    
    struct I2C_Device_ComEvt {
            OrthogonalEvt comp; /*!< inherits ::DeviceEvt */
            QEvt super; /*!< inherits ::QEvt */
            enum {COM_EVT_OK = 0, COM_EVT_ERROR} status; /*!< event data */
        } comEvt;
    
    static QState OrthogonalDispatcher_Dispatch(QActive* const me, QEvt const * const e)
    {
        QState status;
    
        if ( e->sig >= MAX_PUB_SIG )
        {
            QHSM_DISPATCH(Q_ORTHOGONAL_EVT_GET_HSM(), e, NULL);
        }
    
        status = Q_SUPER(&QHsm_top);
    
        return status;
    }
    
     
  • Gawie de Vos

    Gawie de Vos - 2021-07-28

    Why can this not work:

    typedef struct OrthogonalEvt_s {
    QEvt super;
    QHsm* owner;
    } OrthogonalEvt;

     
    • Artem Dovhal

      Artem Dovhal - 2021-07-28

      Can you suggest how to deal with QTimeEvt in such manner?

       
  • Quantum Leaps

    Quantum Leaps - 2021-07-28

    QTimeEvt can be subclassed as well, so you could add the owner member in the subclass.
    In any case, I would probably name that member "recipient" rather than "owner":

    typedef struct {
        QTimeEvt super;
        QHsm *recipient;
    } MyTimeEvt;
    

    One restriction with QTimeEvts or subclasses of them is that they can NOT be allocated dynamically. This means that the only option for them is static allocation, but because they cannot be dynamic, they should NOT be shared.

    --MMS

     

    Last edit: Quantum Leaps 2021-07-28
    • Artem Dovhal

      Artem Dovhal - 2021-07-28

      They are static, but still require proper dispatching.
      How can I do it?

      For example, I am using 3 HSM inside one AO. Each HSM may contain at least one QTimeEvt or more. For each QTimeEvt I define separate signal in enum as it requires.

      These HSM modules are reused by multiple different AO.

      I don't want to make definition of HSM's signals as public. They shall be encapsulated inside each module in implementation file (*.c).

      Can you suggest how to reuse ready-made HSM across multiple different AO?

       

      Last edit: Artem Dovhal 2021-07-28
      • Quantum Leaps

        Quantum Leaps - 2021-07-28

        This question is about managing your signal space, which is a broader issue than just signals used by Orthogonal Components.

        Well, assuming 16-bit signal space, you have 64K unique signals to work with. I'm sure you can carve out of several signals that are off limits to the other parts of the application, so they can be used inside your reusable Orthogonal Components.

        Alternatively, you can reuse the same numeric signal values in each component. The uniqueness requirement is only within a scope of a single state machine, so different state machines can use the same numerical values of signals for different purposes.

        But even though the signal reuse is a theoretical possibility, I prefer to work with unique signals that are used for one purpose only. This is easier to debug. And the signal space is so big that there are no compelling reasons to overload signals.

        --MMS

         
        • Artem Dovhal

          Artem Dovhal - 2021-07-29

          In this case, you have to transfer all possible signals that exist in the project into one common header file. This creates some inconvenience when making changes to individual parts of the system and a general dependence of each module on this file appears. I would like to have full encapsulation and independence of modules from each other. So I came up with the idea that I have already described in this post.

           
          • Quantum Leaps

            Quantum Leaps - 2021-07-30

            No, you don't have to use the most primitive signal management policy. This is done in the examples just because they are so simple. But in a more complex project you can invent many other ways of more intelligent management of your signals.

            For example, you can divide your 64K signal space into "signal groups" (say 1000 signals each, which is plenty). This means that your "common header file" contains an enumeration of those "signal groups", which will be offsets:

            /* common_header.h */
            enum SignalGroups {
                GROUP0_SIG = Q_USER_SIG,
                GROUP1_SIG = 1000,
                GROUP2_SIG = 2000,
                GROUP3_SIG = 3000,
                /* ... */
            }
            

            The sub-modules then define specific (private) signals based on those offsets. These enumerations don't need to be exposed globally, so your other modules don't need to recompile if you change the private signals. I hope you get the idea.

            /* submodule2_header.h */
            enum Submodule2Signals {
                MYTRIGGER1_SIG = GROUP2_SIG,
                MYTRIGGER2_SIG,
                /* ... */
            }
            

            Of course, you can come up with other signal management policies. The point is that you have many ways to do this.

            --MMS

             
            👍
            1
  • Harry Rostovtsev

    I would like to extend/follow up on this thread regarding the use of timer events that extend the QTimeEvt "class". Specifically, if I extend the QTimeEvt to include an id of some sort, can I use the same time evt with same signal enum but different IDs at the same time. For example, if I have 3 HSMs where this timer is being handled, can I post arm the same timer with different IDs for 2 of the HSMs at the same time?

     
    • Quantum Leaps

      Quantum Leaps - 2021-08-03

      Time events in QP are currently available only as static events, meaning that for all intents and purposes they must be immutable. For this reason, you cannot change the attributes of a time event, such as its signal or the associated active object, after the constructor.

      The same exact limitation applies to the derived time events (subclasses of QTimeEvt), because all these events are still static. This means that all attributes, including the attributes added through inheritance, must be unchanging after the constructor.

      I hope that this argumentation answers all your questions. (The answers are No and No).

      --MMS

       
      • Harry Rostovtsev

        I hope that this argumentation answers all your questions. (The answers are No and No).

        Yeah, I kinda figured that I am trying to cheat by using the same time event for multiple things at the same time but thanks for the clarification.

         

Log in to post a comment.