Control and I/O¶
This section lists PUSopen® public APIs that application developers may use to control the stack’s operations. Example code illustrates the APIs’ usage.
Note
All public APIs of PUSopen® used in the examples in this section are defined in the C header file “pusopen/pusopen.h.” The APIs are defined as convenience macros that expand into PUSopen® calls. This is because almost all calls use the default instance of the PUSopen® configuration—the convenience macros simplify the API usage.
Processing Overview¶
The following pseudo-code describes the basic pattern of the PUSopen® operations. This pattern is the most common usage of the PUSopen® stack and may be adjusted as necessary for a user application. Parts of the pseudo-code in bold represent calls to the stack’s APIs.
Stack initialization; /* 1. Initialize the stack */
Stack configuration; /* 2. Configure PUSopen */
while (1) {
if (new data received) /* 3. Accept incoming byte stream */
Accept received bytes;
Trigger stack; /* 4. Process received data */
Produce new packets; /* 5. Create/send new packets */
}
Before processing the received data may begin, the stack must be initialized and configured. Initialization and configuration of the PUSopen® is described in the Section 3.
The data processing is done in an endless (task) loop “while (1)”. In the first step of the loop, the received bytes (raw stream of received data) are accepted into the stack. See Section 4.2 for details of data acceptance.
In the next step, the stack is “triggered” (Section 4.3), which causes accepted data to be validated, processed, and pushed upwards in the stack (Section 4.3). Eventually, accepted data will be decoded, and the CCSDS/PUS packets will be recognized. In the top layer of the stack, the user application logic may process notifications of the received packets and their payloads.
The stack produces TM/TC packets as a response to received telecommands (TC) or upon a call of the stack’s public API by the user code. Created packets (or frames) are sent to the data link via user-defined functions.
Data Reception¶
Data received from a data bus, a raw received byte stream, is accepted byte-by-byte by the FESS layer of the PUSopen® stack via the API po_accept (). The user application may call po_accept () directly from the RX Interrupt Service Routine (ISR) or other data bus polling code. The following example shows the reception of a single byte from the UART link and accepting it into the stack.
#include <pusopen/pusopen.h>
void __interrupt__ ISR () {
/* Custom receive function */
uint8_t c = get_uart_rx ();
/* Insert received byte into PUSopen\ :supsub:`reg` stack */
po_accept (c);
}
Another way of receiving data is to use po_accept () from the user application code. Users may poll, for example, the Ethernet interface for received data. Once the data is received, the user may insert data byte-by-byte into the PUSopen® stack.
#include <pusopen/pusopen.h>
uint8_t buf[128] = { 0 };
void main() {
while (1) {
/* Custom code to retrieve received data */
uint8_t num = client_recv (&buf);
if ( num > 0 ) {
for ( i = 0; i < num; i++)
po_accept ( buf[i] );
}
/* The rest of the data processing goes here */
}
}
The size of the FESS reception buffer (Section 2.2) used by the po_accept () defined by the user in the PUSopen® configuration (see Section 3). The user application may want to check whether a space is available in the FESS reception buffer by calling po_available() before accepting the next received byte into the PUSopen® stack.
Sending Packets¶
The user application has complete control over the pace of processing the outgoing packets and frames. PUSopen® stack is driven by the calls to the APIs po_triggerPus3(), pus13_trigger (), po_frameVc (), and po_frame () on the sending side.
The sending sequence begins with the generation of the TM/TC in the top-most PUS layer. User either calls the PUS User (Section 5.10) to generate TM/TC or calls po_pus5tm(), po_triggerPus3() or po_triggerPus13 () to generate TM from those services. PUS service providers may also generate TM in response to a received TC (e.g. PUS 17). See Section 5 for description of the internal behavior of the PUS service providers.
PUS User and PUS service providers (Section 5) call directly Packet Service (PS) layer to wrap produced TM/TC into CCSDS packet, which in turn calls subnet_request () to handle produced CCSDS packet by the Subnetwork layer (Section 4.6).
User application shall call po_frameVc () only if Virtual Channel Services are used. A call po_frame () to produces the next serial frame (e.g., HDLC) and hands it to the user code.
The maximum size of the produced frame or packet by the PUSopen® stack (in its encoded/encrypted form, including ASM) is 61440 bytes (60 KB).
The following example shows the control sequence producing encoded frame.
#include <pusopen/pusopen.h>
uint8_t data[4] = { 0 };
uint8_t buf[256] = { 0 };
uint16_t len = 0 ;
uint16_t evtId = 3 ;
po_result_t res;
res = po_pus5tm(evtId, data, 4); /* Generate TM[5,x] */
if (res == PO_SUCCESS) {
/* Encode the packet into a serial frame (HDLC) */
res = po_frame( buf, len );
if (res == PO_SUCCESS) {
/* Custom "send" function, e.g., CAN bus or UART */
send( buf, len );
}
}
Receiving Packets¶
During reception, the data are accepted into the PUSopen® stack at the bottom FESS layer (see po_accept(), Section 4.2). Received packets and frames are processed in the FESS, VC and PS layers by calling po_triggerVc (), po_triggerPs (), po_triggerPus1 ().
Each call retrieves received data from its corresponding lower layer and process them (see Section 2.2 for overview of the PUSopen® layers). Each layer makes data available on-request to the upper layer of the stack.
First the po_triggerVc () is called, which retrieves the next received CCSDS Transfer Frame from the FESS layer and distributes the content of the CCSDS Transfer Frame - the CCSDS Packets into the reception queues of the defined Virtual Channels. The call to po_triggerVc() is optional, and shall be used only if Virtual Channel Services (VC) are used.
The po_triggerPs (), calls subnet_indication () API (Section 4.6) which typically calls VC/FESS layer to retrieve next received packet. The po_triggerPs () is called in the loop to retrieve and process all received CCSDS packets one-by-one.
Last called trigger is the po_triggerPus1 () which retrieves content of the last received CCSDS packet - the ECSS PUS TM/TC and distributes it to destination PUS service provider or user. PUS service then produces a response to the received PUS TM/TC.
#include <pusopen/pusopen.h>
while(po_triggerVc() == PO_SUCCESS) {
while(po_triggerPs() == PO_SUCCESS) {
res = po_triggerPus1();
}
}
Time Source¶
PUSopen® uses system time to timestamp created PUS telemetry packets. A custom callback function po_time () is called by the PUSopen® for reading the current system time. The implementation (body) of the po_time () is provided by the user.
If the function is not implemented by the user, PUSopen® provides default, empty, implementation of the po_time (), returning 0 as the time stamp.
In the following code example, the hook function po_time () returns actual system tick (usually total number of ms since boot), a way of representing system time in many embedded systems.
#include <pusopen/pusopen.h>
po_result_t po_time(uint8_t *buf, const uint8_t width) {
uint32_t tick = getTick();
if (width == 4) {
memcpy(buf, (uint8_t*)&tick, 4);
}
else if (width == 7) {
memcpy(&buf[3], (uint8_t*)&tick, 4);
}
}
PUSopen® provides the actual configured width (in bytes) of the time field through the parameter “width” of the po_time callback.
Subnetwork Layer¶
Subnetwork Layer is positioned between the Packet Services (PS) and FESS layer (or Virtual Channel Services if used) - see Figure 3. The Subnetwork Layer is customizable by the user and serves as point where packets are distributed between stack layers and external bus if required by the target system architecture.
There are two methods, which must be implemented for the Subnetwork Layer. The subnet_request () is called by the Packet Services (PS) layer and forwards created CCSDS packets either further down the stack (i.e. FESS) or to the on-board bus. The second method, subnet_indication () is also called from Packet Services (PS) layer and checks whether new packet is available from Virtual Channel Service or FESS or from an on-board data bus. Prototypes of both mentioned methods can be found in the header file “pusopen/pocallbacks.h”.
The following code shows an example implementation of the Subnetwork Layer which forwards created and received packets between the PS and FESS layer.
#include <pusopen/pusopen.h>
po_result_t subnet_request(
po_subnet_request_t *request,
void *poconf)
{
/* Forward requested data directly to FESS layer */
return fessChannelAccess_request(
conf->fess[0],
request->data,
request->len);
}
po_result_t subnet_indication (uint8_t * const data, uint16_t *len)
{
uint16_t io_index = 0U;
/* Call FESS indication API to retrieve received data */
return fessChannelAccess_indication(
conf, data, len, &io_index);
}
Virtual Channels¶
CCSDS Virtual Channels are mechanism for separation of the transmitted data into multiple logical channels transmitted over the same physical link. Physical transmission of the data from different Virtual Channels can be prioritized according to the user-defined scheme (MUX table).
PUSopen® implements the Virtual Channel Services layer (Section 2.2) which provides APIs po_triggerVc() and po_frameVc() to control reception and sending of the packets via Virtual Channels. User may define required number of Virtual Channels in the PUSopen® configuration.
Code examples Code 5 and Code 6 in the Sections 4.3 and 4.4 shown usage of the Virtual Channel Services APIs for sending and receiving data. Section 3 describes configuration and instantiation of Virtual Channels.
Observables¶
PUSopen® provides two mechanisms to observe its internal operations and diagnose potential unexpected behavior: monitoring observable parameters (this section) and observing PUSopen® internal debug outputs (Section 4.9).
Each module of the PUSopen® provides a set of observable parameters. All observable parameters are in the supplied header file “pusopen/poobs.h”
The following table summarizes all PUSopen® observable parameters:
Module |
Variable |
Data Type |
Description |
---|---|---|---|
PUS 1 |
routedReqCounter |
UINT32 |
Num. requests routed by PUS 1 service provider. |
PUS 1 |
failedRoutingCounter |
UINT32 |
The total number of requests for which routing has failed. |
PUS 3 |
pus3numGenReports |
UINT32 |
The total number of generated TM[3,x] telemetry reports. |
PUS 3 |
pus3triggers |
UINT16 |
The total number of calls to the po_triggerPus3() API. |
PUS 5 |
tm51evtCounter |
UINT32 |
The total number of generated TM[5,1] event reports. |
PUS 5 |
tm52evtCounter |
UINT32 |
The total number of generated TM[5,2] event reports. |
PUS 5 |
tm53evtCounter |
UINT32 |
The total number of generated TM[5,3] event reports. |
PUS 5 |
tm54evtCounter |
UINT32 |
The total number of generated TM[5,4] event reports. |
PUS 8 |
requestCounter |
UINT32 |
The total number of received TC[8,1] requests. |
PUS 13 |
numUplinks |
UINT32 |
The total number of ongoing uplink sequences. |
PUS 13 |
numDownlinks |
UINT32 |
The total number of ongoing downlink sequences. |
PUS 13 |
downlinkPktCounter |
UINT32 |
The total number of generated downlink packets. |
PUS 17 |
aliveCounter |
UINT32 |
The total number of received TC[17,1] alive requests. |
PUS 17 |
connCounter |
UINT32 |
The total number of received TC[17,3] connection requests. |
PUS 20 |
numValueReports |
UINT32 |
The total number of produced TM[20,2] reports. |
PUS 20 |
numSuccSetParamTC |
UINT32 |
The total number of successful TC[20, 3] commands. |
PUS 20 |
numFailedSetParamTC |
UINT32 |
The total number of failed TC[20, 3] commands. |
PUS 20 |
numParamDefReports |
UINT32 |
The total number of produced TM[20,7] reports. |
PUS Usr |
numTmRecv |
UINT32 |
The total number of received TMs. |
PUS Usr |
numTcRecv |
UINT32 |
The total number of received TCs. |
PUS Usr |
numTcGen |
UINT32 |
The total number of generated TCs. |
PUS Usr |
numTmGen |
UINT32 |
The total number of generated TMs. |
PS |
discSentPktCounter |
UINT32 |
The total number of CCSDS packets discarded when sending. |
PS |
discRecvPktCounter |
UINT32 |
The total number of CCSDS packets discarded on reception. |
PS |
psSequenceCounter |
UINT32 |
Sequence counter for generated CCSDS packets. |
PS |
pktRecvCounter |
UINT32 |
The total number of received CCSDS packets. |
VC |
insertedPktCounter |
UINT32 |
The total number of packets inserted into Virtual Channels. |
VC |
extractedPktCounter |
UINT32 |
The total number of packets inserted from Virtual Channels. |
VC |
dicardedSentPktCounter |
UINT32 |
The total number of discarded packets when inserting into VC. |
VC |
dicardedRecvPktCounter |
UINT32 |
The total number of discarded packets when extracting from VC. |
VC |
sendFrmCounter |
UINT32 |
The total number of created CCSDS Transfer Frames. |
VC |
recvFrmCounter |
UINT32 |
The total number of received CCSDS Transfer Frames. |
VC |
discardedSendFrmCounter |
UINT32 |
The total number of CCSDS frames discarded on sending. |
VC |
discardedRecvFrmCounter |
UINT32 |
The total number of CCSDS frames discarded on reception. |
FESS |
numFessRequestOk |
UINT32 |
The total number of successful fess_request calls. |
FESS |
numFessRequestErr |
UINT32 |
The total number of failed fess_request calls. |
FESS |
numProvidedSdu |
UINT32 |
The total number of SDU encoded by FESS. |
FESS |
numDiscBytes |
UINT32 |
The total number of discarded bytes by po_accept (). |
FESS |
numSduRecvOk |
UINT32 |
The total number of SDUs received correctly. |
The following code example shows an example use of the observable variable “numDiscBytes” (Table 8) of the FESS layer by user code. The variable indicates the total number of discarded bytes by the API po_accept () due to insufficient room to store the bytes in the internal buffers of the FESS instance.
#include <pusopen/pusopen.h>
/* retrieve value of the observable parameter */
uint32_t num = po_mdb_apid.fess->observables.numDiscBytes;
printf("Num. discarded bytes: %u\n", num);
Debug Functions¶
PUSopen® provides three mechanisms for troubleshooting unexpected outputs or behavior.
Return values - All public APIs of PUSopen® return a result code (po_result_t). In case of successful operation, the result code is PO_SUCCESS, otherwise an error code is returned. See the header file potypes.h for the list of return codes.
Observable parameters - Printing out or observing range of the PUSopen® internal observable parameters provides non-invasive way to see what is happening inside the layers of the stack. Section Observables provides the list of observable parameters. All C structures with observable parameters are in the header file poobs.h.
Debug messages - The header file podebug.h contains APIs for switching ON and OFF internal debug messages - po_debug_msg_on() and po_debug_msg_off(). Each debug message is sent to the custom callback function - API po_set_callback_debug(). The user application must first set the custom debug callback function, then call po_debug_msg_on() to enable internal debug outputs.
Note
Processing custom debug messages or printing out observable values on the console may have impact on the timing and performance. Adding too many debug printouts may delay processing of messages, leading to the reception buffer overrun and missed messages.
#include <pusopen/pusopen.h>
void po_dbg_print(po_debug_record_t *dbg)
{
po_debug_print_record(dbg);
}
...
po_set_callback_debug(po_dbg_print);
po_debug_msg_on(PO_DBG_PUS, PO_DBG_DEV);
po_debug_msg_on(PO_DBG_PS, PO_DBG_DEV);
po_debug_msg_on(PO_DBG_FESS, PO_DBG_DEV);
...