Handles MCF packed messages from the higher protocol layer. More...
#include "errorcode.h"
#include "cfg_msg.h"
#include <stdint.h>
#include <stdbool.h>
#include "util/databuffer.h"
#include "net/net.h"
#include "net/mcf.h"
Go to the source code of this file.
Data Structures | |
struct | MsgId |
A MsgId is a struct which contains all information required to generate a valid reply. More... | |
Defines | |
#define | E_MSG_TYPE_UNKNOWN ( E_MSG + 0x01 ) |
Message unknown. | |
#define | E_MSG_TYPE_UNKNOWN_DESCR "Unknown message" |
#define | E_MSG_FUNCTION ( E_MSG + 0x02 ) |
Message function returned false. | |
#define | E_MSG_FUNCTION_DESCR "Function error" |
#define | E_MSG_CLASS_UNSUPPORT ( E_MSG + 0x03 ) |
Message class received not supported. | |
#define | E_MSG_CLASS_UNSUPPORT_DESCR "Class not supported" |
#define | MSG_CMD(CMD_TYPE, CMD_STUB) bool _msg ## CMD_STUB (MsgId * id, DataBuffer * buf); |
Generate all message callbacks. | |
Functions | |
void | msgRx (SockAddr *addr, uint8_t *data, int len) |
Receive a packet with data as a command. | |
void | _msgRxCmd (MsgId id, DataBuffer *buf) |
Invoked when a command is received. | |
bool | msgTxReply (MsgId *id, DataBuffer *buf) |
Invoke to send a reply. | |
bool | msgTxError (MsgId *id, int errCode, const char *errMsg) |
Invoke to send an error response. | |
void | _msgTx (SockAddr *addr, uint8_t *data, int len) |
Stub function, invoked by RMT when sending a MCF packet. |
Handles MCF packed messages from the higher protocol layer.
Commands & Replies ================== From the lower-level protocol layer (in this case SRP, but it might just as well be TCP/IP), the _msgRx() command is invoked. The received packets are unpacked, and its contents decoded.
For each command a function prototype is generated, which should be implemented by the higher level functions. These commands get a message identifier and a DataBuffer containing the content of the message.
For example, the ReadAddr message is defined as follows: MSG_CMD(GROUP_DEBUG, 1, ReadAddr)
The message module will then generate a stub-signature as follows:
bool _msgReadAddr(MsgId * msgId, DataBuffer * content)
However, the implementation is left to the higher-level layers.
There are 3 ways in which you can generate a reply: 1. Synchronous (returns < 100ms) 2. ASynchronous (returns < 1 second) 3. Synchronous + Event (anything > second)
Which to take depends on the situation, however, take care with option 1, since it will stall the network stack.
Synchronous ----------- Synchronous replies are replies which yield a result immediately. They will be used most common situations.
For example, the following code returns a number of memory addresses read.
bool _msgReadAddr(MsgId * msgId, DataBuffer * cmd) { uint32_t * addr; uint16_t count; // parse command arguments dbReadUInt(cmd, &addr); // read first argument, the offset address dbReadUShort(cmd, &count); // read second argument the count if (count > MAX_READS) { // can't read that many addresses msgTxError(msgId, E_INVALID_ARGUMENT); // return an error return true; // true only means 'I've handled the error' } // create reply arguments DataBuffer rply = DB_BUF_INIT(replyBuf, MAX_SIZE); // initialize reply buffer dbWriteUShort(&reply, count); // return no of elements in return array for (i = 0; i < count; ++i, ++addr) // for each address dbWriteUInt(&reply, *addr); // load the data into the reply buffer msgTxReply(msgId, &reply); // send the reply to the message processing return true; }
ASynchronous ------------ However, it is not required to generate a reply inside the processing function. Functions which require some asynchronous processing, but don't take more than a second, can use asynchronous processing.
static volatile MsgId _asyncMsgId; static volatile bool _asyncBusy; bool _msgCalibrateTDCs(MsgId * msgId, DataBuffer * cmd) { if (_eventBusy) return false; // its better to generate a nice reply, but this will do _asyncMsgId = *msgId; // must copy it: pointer no longer valid after function! _asyncBusy = true; tdcCalibrateAll(); return true; // don't generate a reply } void replyTDCCalibratResult() { int i; DataBuffer rply = DB_BUF_INIT(replyBuf, MAX_SIZE); // initialize reply buffer dbWriteUShort(&reply, TDC_CHANNELS); // return no of elements in return array for (i = 0; i < TDC_CHANNELS; ++i) // for each channel dbWriteUInt(&reply, tdcGetCalibValue(i)); // load the TDC calibration value msgTxReypl(&_asyncMsgId, &reply); // create the event _asyncBusy = false; // allow new async replies return true; } // TDC IRQ void IRQ_HANDLER(TDC_IRQ) { // take it outside of the interrupt handler, message processing is not thread safe. if (tdcGetIrq() & TDC_IRQ_CALIBRATION_DONE) { schdCallOnce(replyTDCCalibratResult); // command, not yet implemented. tdcClearIrq(TDC_IRQ_CALIBRATION_DONE); } }
Event Reply ----------- An event reply is used when the function takes even more time. For simplicity sake we will use the same code as before, and modify it to be an event.
First of all the code immediately generates an OK reply. Second the function msgTxReplyEvent is used instead of msgTxReply. Those are the only differences at this side.
In practice what will happen on the shore-station side is that the function is invoked, and returns immediately, later on the reply will be passed to the shore-station API and returned through an asynchronous listener.
static volatile MsgId _eventMsgId; static volatile bool _eventBusy; bool _msgCalibrateTDCs(MsgId * msgId, DataBuffer * cmd) { if (_eventBusy) return false; // its better to generate a nice reply, but this will do _eventMsgId = *msgId; // must copy it, pointer no longer valid after this function! _eventBusy = true; tdcCalibrateAll(); msgTxReply(msgId, NULL); // generate simple Ok. return true; } void replyTDCCalibratResult() { int i; DataBuffer reply = DB_BUF_INIT(replyBuf, MAX_SIZE); // initialize reply buffer dbWriteUShort(&reply, TDC_CHANNELS); // return no of elements in return array for (i = 0; i < TDC_CHANNELS; ++i) // for each channel dbWriteUInt(&reply, tdcGetCalibValue(i)); // load the TDC calibration value msgTxReplyEvent(&_eventMsgId, &reply); // create the reply event _eventBusy = false; // allow new events return true; } // TDC IRQ void IRQ_HANDLER(TDC_IRQ) { // take it outside of the interrupt handler, message processing is not thread safe. if (tdcGetIrq() & TDC_IRQ_CALIBRATION_DONE) { schdCallOnce(replyTDCCalibratResult); // command, not yet implemented. tdcClearIrq(TDC_IRQ_CALIBRATION_DONE); } }
Stand-Alone Events ================== The Msg module can also be used to generate periodic stand-alone events. These can be used to communicate status updates, or maybe unexpected failures.
However, the address to send the event to should be known before hand. To do this we first need to configure the receiver. For this we have defined the 'ConfStatusReceiver' command which takes the sender of the command to be the receiver for asynchronous events. This command should be invoked by the shore station before events can be send.
static SockAddr _statusRecv; static bool _statusRecvValid; // configures the address to send status events to. bool _msgConfStatusReceiver(MsgId * msgId, DataBuffer * cmd) { _statusRecv = msgId->addr; // copy socket address from message ID; _statusRecvValid = true; msgTxReply(msgId, NULL); // generate simple Ok. return true; }
Once this is done, we can schedule a periodic function to create status messages.
#define MSG_EVT_STATUS MSG_MK_TYPE(GROUP_CLB, 1) static int statusTask; static void statusFull(DataBuffer * eventData) { // outside of scope of example, fills the event with the required fields. } // called every second void statusPeridoic() { if (!_statusRecValid) return; // no valid sender address, cancel. uint8_t eventBuf[MAX_SIZE]; DataBuffer event = DB_BUF_INIT(eventBuf, MAX_SIZE); statusFill(&eventBuf); msgTxEvent(MSG_EVT_STATUS, &statusRecv, &event); } bool statusInit() { if (!schdRegister(statusPeriodic, false, &statusTask)) return false; if (!schdPeriodic(statusTask, 1000)) return false; }
Other remarks ============= Message Container Format is just what it is, a format. It does not imply any kind of protocol. The Msg module, however, uses it to parse and format messages. Still on the remote side it is expected that a command always yields a reply within a specified time. Care must be taken by the higher-level layer, that is, a command implementation, to always generate a reply. In its simplest form this is done by returning false in the stub function. Failure to do so will cause time-out's on the shore-station, and may hold-up processes.
void _msgRxCmd | ( | MsgId | id, | |
DataBuffer * | buf | |||
) |
Invoked when a command is received.
id | ||
buf |
void _msgTx | ( | SockAddr * | addr, | |
uint8_t * | message, | |||
int | len | |||
) |
Stub function, invoked by RMT when sending a MCF packet.
addr | Target address | |
data | Pointer to the data | |
len | Length of the message. |
void msgRx | ( | SockAddr * | addr, | |
uint8_t * | data, | |||
int | len | |||
) |
Receive a packet with data as a command.
addr | Source address | |
data | Pointer to the message data | |
len | Length of the message |
bool msgTxError | ( | MsgId * | id, | |
int | errCode, | |||
const char * | errMsg | |||
) |
Invoke to send an error response.
id | The message ID | |
errCode | The error code to reply. | |
errMsg | The error message to response, may be empty or NULL. |
bool msgTxReply | ( | MsgId * | id, | |
DataBuffer * | buf | |||
) |
Invoke to send a reply.
id | The message ID | |
buf | The content to send. |