| name | add-topic |
| description | Add or modify a topic in the Rexio topic bus. Ensures the enum, struct, metadata table, architecture docs, and tests stay in sync. Use when modifying topic_data.h, adding topic publish/subscribe calls, or when the user says /add-topic. |
Add a new topic to the Rexio topic bus or modify an existing one. This skill ensures all 5 locations that define a topic stay in sync.
Input
Gather from the user:
- Topic name — e.g.,
ALTITUDE_SETPOINT (becomes TOPIC_ALTITUDE_SETPOINT in enum, topic_altitude_setpoint_t as struct)
- Struct fields — C type, name, and unit for each field
- Publisher — which task/component publishes this topic
- Rate — publish frequency (e.g.,
500 Hz, ~20 Hz)
- Consumers — which tasks read this topic (for documentation)
Validation
Before writing ANY code, validate the struct definition against these rules. Reject and ask the user to fix violations:
- First field: MUST be
uint64_t timestamp_us;
- SI units: All physical quantities use SI units. Verify field names use correct suffixes (see
references/topic_conventions.md):
- Meters:
_m (e.g., altitude_m, range_bottom_m)
- Radians:
_rad or bare name when context is obvious (roll, pitch, yaw)
- m/s^2: comment on line (e.g.,
float accel_x; // m/s^2)
- Pascals:
_pa (e.g., pressure_pa)
- Microseconds:
_us (e.g., loop_time_us)
- Volts:
_v (e.g., vbat_v)
- Amps:
_a (e.g., current[4])
- Percent:
_pct (e.g., cpu_load_pct)
- Exceptions:
temperature (degC), mag_x/y/z (uT) — unit in comment
- Quaternions:
float q[4] in Hamilton convention [w, x, y, z] — add comment // quaternion [w,x,y,z]
- Per-motor arrays:
type field[4] with index order RR=0, FR=1, RL=2, FL=3 — add comment // index: RR,FR,RL,FL
- No pointers: Topic structs are copied via memcpy. Pointers would create dangling references.
- No bitfields: Use
uint8_t masks with #define constants instead.
- Keep it small: Topic bus uses memcpy on every publish/copy. Prefer flat structs. If you need more than ~128 bytes, question whether it should be one topic.
Step 1: Add Enum Entry
In components/rexio_topics/include/topic_data.h, add the new entry to topic_id_t enum before TOPIC_COUNT:
typedef enum {
TOPIC_IMU_RAW = 0,
TOPIC_{NAME},
TOPIC_COUNT,
} topic_id_t;
Step 2: Add Struct Typedef
In the same file (topic_data.h), add the struct definition. Place it near related topics (sensor topics together, control topics together, etc.):
typedef struct {
uint64_t timestamp_us;
} topic_{name_lower}_t;
Every field that has a physical unit MUST have a comment documenting it:
float altitude_m;
float pressure_pa;
float gyro_x, gyro_y, gyro_z;
Step 3: Add Metadata Entry
In components/rexio_topics/src/topics.c, find the topic size metadata table and add:
static const size_t topic_sizes[TOPIC_COUNT] = {
[TOPIC_IMU_RAW] = sizeof(topic_imu_raw_t),
[TOPIC_{NAME}] = sizeof(topic_{name_lower}_t),
};
This MUST match the enum entry exactly. A mismatch causes silent memcpy corruption.
Step 4: Update Architecture Documentation
In docs/architecture.md section 5.3 (Topic Registry table), add a row:
| `TOPIC_{NAME}` | `topic_{name_lower}_t` | {publisher} | {rate} |
If the struct is new, also add its definition to section 5.4 (Topic Data Structures).
Step 5: Verify Test Coverage
The test_all_topic_sizes test in test/test_topics.c iterates over all entries up to TOPIC_COUNT and verifies each has a non-zero size in the metadata table. This should automatically cover the new topic IF steps 1-3 are correct.
Verify by running make test. If the test fails with a zero-size entry, step 3 was missed.
If test_all_topic_sizes doesn't exist yet, add it:
void test_all_topic_sizes(void) {
for (int i = 0; i < TOPIC_COUNT; i++) {
TEST_ASSERT_NOT_EQUAL_MESSAGE(0, topic_get_size(i),
"Topic has zero size in metadata table");
}
}
Modifying an Existing Topic
When changing an existing topic's struct:
- Make the struct changes in
topic_data.h
- CRITICAL: Search for all consumers. Run:
grep -rn "TOPIC_{NAME}" components/ main/ test/
- List every file that reads or writes this topic
- If a field was renamed or its type changed, each consumer MUST be updated
- If a field was removed, each consumer that read it MUST be updated
- If only a field was added, existing consumers continue to work (memcpy copies the full struct)
Warn the user about each consumer that needs updating.
Scope Boundaries
- Topic data contract ONLY. Do NOT implement publishers or subscribers.
- Do NOT create components (use
/new-component).
- Do NOT modify the topic bus implementation (
topics_init, topic_publish, topic_copy).
- Do NOT create FreeRTOS tasks (use
/wire-task).
Reference
See references/topic_conventions.md for the full field naming and unit convention reference.