Message Queue

Messages Queues are foundation of communication and data exchange in a Weld State-Machine. Messages can be used to send data between threads, threads and ISRs or between states of a parallel state-machine. For example, messages can be used to send packets of data from a communication ISR to a Thread. Another queue can be used to pass the data from a data processing Thread to a display Thread which is responsible for displaying the data.

Each message queue in Weld is a structure that contains the queue and message information, including a pointer to an area of memory that is reserved for the queue mailbox. Here is the typedef that defines a message queue.

typedef struct wT_QUEUE_STRUCT
{
    uint8_t _quque_id;
    uint16_t _message_size;
    uint8_t _queue_depth;
    uint8_t _messages_in_queue;
    uint8_t _quque_popped;
    uint8_t *_queue_start;
    uint8_t *_queue_end;
    uint8_t *_read_ptr;
    uint8_t *_write_ptr;
} wT_QUEUE_TYPE;

Message queues in Weld are implemented as FIFO, meaning that the first message that was sent to the queue, will be the first getting extracted.

Message Queue Settings

Adding Message Queue

In order to add a message queue to you state machine, simple drag the Mail Icon from the toolbar and drop it on the body of a state. You can modify the message properties by selecting the dropped icon and then navigating to the properties panel of the application.

Each message queue has the following properties that should be set before using the queue.

  1. Queue Name, is a unique identifier that is used to address this specific queue.
  2. Queue Length, is length of the queue, or the number of messages than can be held in the queue before the queue overflows.
  3. Queue Type, define the relative location of the sender and receiver state machines. If both sender and receiver are in the same Thread, the Single Thread option should be used, otherwise, chose the Multi Thread option.
  4. Data, represents the data-types that ate in a message. Think of this as a struct that contains the different data types. using the Add Data Item on the bottom, you can add more data types to this struct.
    • Data Name, is the name of the struct item representing the data. The name should be unique in the scope of this message queue.
    • Data Size, represents the size of the data. Leave it at "1" for non-array data.
    • Data Type, represents the type that is used to define this data item.
Note
  • The Queue name should be unique in the scope of your project
  • The Data name shoudl be unique in the scope of your message.

Benefits of Weld Message Queue Implementation

  • Direct access to the queue mailbox (buffer) for send and receive operations.
  • Passing data by copy, which ensures the receiver get the correct data.
  • Ability to pass data by references, which could be helpful in low memory devices.
  • Ability to pass multiple data types and arrays using structures that hold that type
  • Built-in API calls for send and receive and queue monitoring

Usage

In order to use the built-in Message Queue of the Weld platform, you have to first add a Message object to your state machine using dragging the Mail icon from to toolbar. After setting the properties of the queue according to your requirements, You can start calling the queue APIs on the defined message object.

Sending a Message

Sending a message means adding a message struct to the queue. You can accomplish that by filling the Transmit Buffer of the queue with the desired data and then call the send() API on the message object.

  1. Load the Transmit Buffer using the txBuff property of the define message queue
  2. Call the send() API with the message name as its argument.
queue_name.txBuff.data1 = (uint8_t)12;
queue_name.txBuff.data2 = (float)54.7;
send(queue_name);

In the above code example, it is assumed the the queue name is queue_name and each message contains a uint8_t data field named data1 and a float type data field named data2.

After calling the send() API, the Transmit Buffer will be empty again (if the queue is not full) and its ready for additional message data to be loaded.

Receiving a Message

Messages can be fetched from the message queue bu using the receive() API with the message name as the argument. After receiving a message from the queue, its data can be accessed using the rxBuff property of the defined message. So, in order to fetch a message from the queue:

  1. Call the receive() API with the message name as its argument
  2. use the rxBuff property of the queue to access the received message data structure

Please note that:

Note

The received message from the queue will remain in scope till the end of the current execution cycle, unless the discard() API is used to discard the message.

This means that, without discarding the received message, calling the receive() API will always return the same message data and does not receive new messages from the queue.

Using the previously defined queue example, we can use the following code to receive the message data.

receive(queue_name); //receive a message, load data to rxBuff
do_something_with(queue_name.rxBuff.data1);
so_something_with(queue_name.rxBuff.data2)
discard(queue_name); //discard the rxBuff data
receive(queue_name); //load the next message to rxBuff

Discarding a Message

As we have already mentioned, once a message is received from the queue, its data will remain valid until the end of the current execution cycle of the receiving Thread. In order to receive more messages from the queue the discard() API has to be called on the message in order to empty the Receive Buffer and make it ready for the next receive() call.

If a discard() is not called on the message, the received message will be automatically discarded at the end of the current execution cycle and the queue will be ready to accept more receive() called at the next cycle.

Best Practices

Pointer Message

It is possible to define a message queue that conveys pointers between the sender and the receiver. However, it is important that in this case, the actual data remains in scope, otherwise, the sender will receive a pointer to a data that might be already out of scope, hence, destroyed. This implies that, you would be looking for trouble if you send pointers to local variables, or a global variable that might be altered before it is receive by the intended receiver.

This is why we recommend that you use the built-in methods to copy the message data to the queue at the time of sending the message, instead of defining a queue that carries pointers. By using the built-in methods, you would have direct access to the mailbox (message pool), and you can be sure that the data will be manipulated exactly as you intend.

Queue Length

In the Weld implementation of Message Queue, just like most other implementations, the sent message will be discarded if the queue is full and false (0) value will be returned from the send function. In order to avoid any problems with the queue being full:

  • Define a queue that is sufficiently long, if there is enough memory available
  • Monitor the number of messages in the queue or return value of the send function and implement logic to recover from the situation