Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
CC ?= cc
CFLAGS ?= -O2 -std=c11 \
CFLAGS ?= -O2 -std=c99 \
-Iinclude -Itests \
-Wall -Wextra -Wpedantic -Wconversion -Wshadow \
-Wcast-align -Wcast-qual -Wpointer-arith -Wformat=2 \
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ Minimal ECSS Packet Utilisation Standard (PUS) implementation in C, designed for

| Service | Name | Subtypes |
|---------|------|----------|
| ST[01] | Request Verification | Acceptance, Start, Progress, Completion (success & failure), Routing failure |
| ST[01] | Request Verification | Acceptance, Start, Completion (success & failure), Routing failure; Progress reports must be emitted manually by the handler |
| ST[03] | Housekeeping | TM[3,25] HK report, TM[3,26] diagnostic report |
| ST[05] | Event Reporting | Info, Low/Medium/High severity |
| ST[17] | Test | TC[17,1] are-you-alive, TC[17,3] on-board connection test |
Expand Down Expand Up @@ -115,10 +115,10 @@ void app_on_tc_received(const uint8_t *raw, uint16_t len)
void app_periodic(void)
{
/* 7. Emit periodic housekeeping */
pus_service_3_emit_hk(&g_pus, &g_s3, 0x0001);
pus_service_3_emit_hk(&g_pus, &g_s3, 0x0001u);

/* 8. Emit an event */
pus_service_5_emit(&g_pus, PUS_SUBTYPE_EVENT_INFO, 0x0101, NULL, 0);
pus_service_5_emit(&g_pus, PUS_SUBTYPE_EVENT_INFO, 0x0101u, NULL, 0u);
}
```

Expand Down
2 changes: 1 addition & 1 deletion example/obc_example.c
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ static pus_status_t setter_batt_alert(uint16_t pid, const uint8_t *buf,
uint16_t len, void *ud)
{
(void)pid; (void)len; (void)ud;
g_batt_alert_mv = (uint16_t)((uint16_t)(buf[0] << 8u) | buf[1]);
g_batt_alert_mv = (uint16_t)(((uint16_t)buf[0] << 8u) | (uint16_t)buf[1]);
return PUS_STATUS_OK;
}

Expand Down
6 changes: 5 additions & 1 deletion include/pus.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,11 @@ pus_status_t pus_tc_decode(

/**
* @brief Decode, route, and process a raw TC buffer.
* Emits ST[01] verification reports according to the TC ack_flags.
*
* Automatically emits ST[01] verification reports for the acceptance, start,
* and completion ACK flags. Progress reports (PUS_ACK_PROGRESS) are NOT
* emitted automatically — handlers that need them must call
* pus_service_1_emit_success() / pus_service_1_emit_failure() directly.
* Unrecognised commands receive a TM[1,10] routing failure report.
*
* @param[in,out] ctx Active PUS context.
Expand Down
30 changes: 0 additions & 30 deletions include/pus_codec.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,36 +5,6 @@
#include <stdint.h>
#include "pus_config.h"
#include "pus_types.h"
#include "pus_context.h"

/**
* @brief Populate a TM secondary header with fields common to every outgoing packet.
* @note msg_type_counter is read from ctx->tm_counter but NOT incremented here;
* callers must increment after all error paths are cleared.
*
* @param[in] ctx Active PUS context (provides counter and time source).
* @param[out] hdr Header struct to fill.
* @param[in] service Service type identifier.
* @param[in] subtype Service subtype identifier.
* @param[in] dest_id Destination APID.
*/
static inline void pus_tm_hdr_fill(
pus_context_t *ctx,
pus_tm_sec_header_t *hdr,
pus_service_t service,
pus_subtype_t subtype,
uint16_t dest_id)
{
hdr->version = PUS_VERSION;
hdr->time_ref_status = 0u;
hdr->service_type_id = service;
hdr->subtype_id = subtype;
hdr->msg_type_counter = ctx->tm_counter;
hdr->destination_id = dest_id;
hdr->time = (ctx->time_source != NULL)
? ctx->time_source(ctx->time_source_user_data) : 0u;
hdr->spare = 0u;
}

/**
* @brief Decode a raw TC secondary header from a byte buffer.
Expand Down
5 changes: 4 additions & 1 deletion include/pus_context.h
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,10 @@ typedef struct {

/**
* @brief Runtime state for one EmbeddedPUS instance.
* Allocate statically or on the stack; do not share across threads without a mutex.
* Allocate statically or on the stack.
* @warning Not thread-safe. If the context is accessed from more than one task
* or interrupt, all calls must be serialised with a mutex or critical
* section.
*/
struct pus_context {
pus_handler_entry_t handler_table[PUS_MAX_TC_HANDLERS]; /**< TC dispatch table. */
Expand Down
37 changes: 25 additions & 12 deletions include/pus_handler.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,19 @@
* @brief Register or update a TC handler for a (service, subtype) pair.
* If the pair is already registered, the handler and user_data are replaced.
*
* Passing NULL for @p handler deregisters the entry for that (service, subtype)
* pair. If no entry is found for that pair, PUS_STATUS_NO_HANDLER is returned.
*
* @param[in,out] ctx Active PUS context.
* @param[in] service Service type identifier.
* @param[in] subtype Service subtype identifier.
* @param[in] handler Callback invoked when a matching TC is received.
* @param[in] user_data Opaque pointer forwarded to the handler.
* @param[in] handler Callback to register, or NULL to deregister.
* @param[in] user_data Opaque pointer forwarded to the handler (ignored when deregistering).
*
* @return PUS_STATUS_NULL, PUS_STATUS_TABLE_FULL, or PUS_STATUS_OK.
* @return PUS_STATUS_NULL if ctx is NULL.
* @return PUS_STATUS_NO_HANDLER if handler is NULL and no matching entry exists.
* @return PUS_STATUS_TABLE_FULL if the table is full and no matching entry exists.
* @return PUS_STATUS_OK on success.
*/
pus_status_t pus_handler_register(
pus_context_t *ctx,
Expand All @@ -23,17 +29,24 @@ pus_status_t pus_handler_register(
void *user_data);

/**
* @brief Return the handler table index for a (service, subtype) pair.
* @brief Invoke the registered handler for a (service, subtype) pair.
*
* Calls the handler with @p ctx and @p tc, passing through the stored
* user_data.
*
* @param[in] ctx Active PUS context.
* @param[in] service Service type identifier.
* @param[in] subtype Service subtype identifier.
* @param[in,out] ctx Active PUS context.
* @param[in] service Service type identifier.
* @param[in] subtype Service subtype identifier.
* @param[in] tc TC packet to pass to the handler.
*
* @return Table index (>= 0) if found, -1 if not registered.
* @return PUS_STATUS_NULL if ctx or tc is NULL.
* @return PUS_STATUS_NO_HANDLER if no handler is registered for the pair.
* @return Whatever the handler returns otherwise.
*/
int pus_handler_find(
const pus_context_t *ctx,
pus_service_t service,
pus_subtype_t subtype);
pus_status_t pus_handler_invoke(
pus_context_t *ctx,
pus_service_t service,
pus_subtype_t subtype,
const pus_tc_packet_t *tc);

#endif /* PUS_HANDLER_H */
23 changes: 18 additions & 5 deletions include/pus_service_1.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,20 @@
/** @{ */
#define PUS_ACK_ACCEPTANCE 0x08u /**< Request acceptance verification report. */
#define PUS_ACK_START 0x04u /**< Request start of execution verification report. */
#define PUS_ACK_PROGRESS 0x02u /**< Request progress verification report. */
/**
* @brief Request progress verification report.
* @note pus_tc_process() does NOT emit progress reports automatically.
* Handlers that need them must call pus_service_1_emit_success() with
* PUS_SUBTYPE_VERIFICATION_PROGRESS_SUCCESS (or _FAILURE) at suitable
* points during their own execution, checking this flag themselves:
* @code
* if (tc->sec_header.ack_flags & PUS_ACK_PROGRESS) {
* pus_service_1_emit_success(ctx, tc,
* PUS_SUBTYPE_VERIFICATION_PROGRESS_SUCCESS);
* }
* @endcode
*/
#define PUS_ACK_PROGRESS 0x02u
#define PUS_ACK_COMPLETION 0x01u /**< Request completion verification report. */
/** @} */

Expand Down Expand Up @@ -56,8 +69,8 @@ pus_status_t pus_service_1_build_failure(
uint16_t *out_len);

/**
* @brief Build and forward a success report to ctx->tm_sink.
* Returns PUS_STATUS_OK silently when no sink is configured.
* @brief Build a success report and forward it to ctx->tm_sink.
* Increments ctx->tm_counter regardless of whether a TM sink is configured.
*
* @param[in,out] ctx Active PUS context.
* @param[in] tc The TC being verified.
Expand All @@ -71,8 +84,8 @@ pus_status_t pus_service_1_emit_success(
pus_subtype_t subtype);

/**
* @brief Build and forward a failure report to ctx->tm_sink.
* Returns PUS_STATUS_OK silently when no sink is configured.
* @brief Build a failure report and forward it to ctx->tm_sink.
* Increments ctx->tm_counter regardless of whether a TM sink is configured.
*
* @param[in,out] ctx Active PUS context.
* @param[in] tc The TC being verified.
Expand Down
1 change: 1 addition & 0 deletions src/pus.c
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#include "pus.h"
#include "pus_codec.h"
#include "pus_internal.h"
#include "pus_handler.h"
#include "pus_service_1.h"
#include <stddef.h>
Expand Down
73 changes: 55 additions & 18 deletions src/pus_handler.c
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#include "pus_handler.h"
#include "pus_internal.h"
#include <stddef.h>

pus_status_t pus_handler_register(
Expand All @@ -8,32 +9,47 @@ pus_status_t pus_handler_register(
pus_tc_handler_t handler,
void *user_data)
{
if (ctx == NULL || handler == NULL) {
if (ctx == NULL) {
return PUS_STATUS_NULL;
}

for (uint16_t i = 0u; i < PUS_MAX_TC_HANDLERS; i++) {
if (ctx->handler_table[i].is_used &&
ctx->handler_table[i].service == service &&
ctx->handler_table[i].subtype == subtype) {
ctx->handler_table[i].handler = handler;
ctx->handler_table[i].user_data = user_data;
return PUS_STATUS_OK;
/* NULL handler means deregister: find and clear the existing entry. */
if (handler == NULL) {
for (uint16_t i = 0u; i < PUS_MAX_TC_HANDLERS; i++) {
if (ctx->handler_table[i].is_used &&
ctx->handler_table[i].service == service &&
ctx->handler_table[i].subtype == subtype) {
ctx->handler_table[i].is_used = 0u;
return PUS_STATUS_OK;
}
}
return PUS_STATUS_NO_HANDLER;
}

for (uint16_t i = 0u; i < PUS_MAX_TC_HANDLERS; i++) {
if (!ctx->handler_table[i].is_used) {
ctx->handler_table[i].service = service;
ctx->handler_table[i].subtype = subtype;
ctx->handler_table[i].handler = handler;
ctx->handler_table[i].user_data = user_data;
ctx->handler_table[i].is_used = 1u;
return PUS_STATUS_OK;
{
int32_t first_free = -1;
for (uint16_t i = 0u; i < PUS_MAX_TC_HANDLERS; i++) {
if (ctx->handler_table[i].is_used) {
if (ctx->handler_table[i].service == service &&
ctx->handler_table[i].subtype == subtype) {
ctx->handler_table[i].handler = handler;
ctx->handler_table[i].user_data = user_data;
return PUS_STATUS_OK;
}
} else if (first_free < 0) {
first_free = (int32_t)i;
}
}
if (first_free < 0) {
return PUS_STATUS_TABLE_FULL;
}
ctx->handler_table[first_free].service = service;
ctx->handler_table[first_free].subtype = subtype;
ctx->handler_table[first_free].handler = handler;
ctx->handler_table[first_free].user_data = user_data;
ctx->handler_table[first_free].is_used = 1u;
return PUS_STATUS_OK;
}

return PUS_STATUS_TABLE_FULL;
}

int pus_handler_find(
Expand All @@ -55,3 +71,24 @@ int pus_handler_find(

return -1;
}

pus_status_t pus_handler_invoke(
pus_context_t *ctx,
pus_service_t service,
pus_subtype_t subtype,
const pus_tc_packet_t *tc)
{
int idx;

if (ctx == NULL || tc == NULL) {
return PUS_STATUS_NULL;
}

idx = pus_handler_find(ctx, service, subtype);
if (idx < 0) {
return PUS_STATUS_NO_HANDLER;
}

return ctx->handler_table[idx].handler(ctx, tc,
ctx->handler_table[idx].user_data);
}
41 changes: 41 additions & 0 deletions src/pus_internal.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#ifndef PUS_INTERNAL_H
#define PUS_INTERNAL_H

#include <stddef.h>
#include "pus_config.h"
#include "pus_context.h"
#include "pus_types.h"

/*
* Fill the fields common to every outgoing TM secondary header.
* msg_type_counter is copied from ctx->tm_counter but NOT incremented here;
* callers must increment after all error paths are cleared.
*/
static inline void pus_tm_hdr_fill(
pus_context_t *ctx,
pus_tm_sec_header_t *hdr,
pus_service_t service,
pus_subtype_t subtype,
uint16_t dest_id)
{
hdr->version = PUS_VERSION;
hdr->time_ref_status = 0u;
hdr->service_type_id = service;
hdr->subtype_id = subtype;
hdr->msg_type_counter = ctx->tm_counter;
hdr->destination_id = dest_id;
hdr->time = (ctx->time_source != NULL)
? ctx->time_source(ctx->time_source_user_data) : 0u;
hdr->spare = 0u;
}

/*
* Returns the table index of the handler for (service, subtype), or -1 if
* not found. Internal use only — callers must not store or expose the index.
*/
int pus_handler_find(
const pus_context_t *ctx,
pus_service_t service,
pus_subtype_t subtype);

#endif /* PUS_INTERNAL_H */
Loading