Skip to content
Merged
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
4 changes: 2 additions & 2 deletions Doxyfile
Original file line number Diff line number Diff line change
Expand Up @@ -48,13 +48,13 @@ PROJECT_NAME = "HELIA"
# could be handy for archiving the generated documentation or if some version
# control system is used.

PROJECT_NUMBER = v0.1.0
PROJECT_NUMBER = v0.1.1

# Using the PROJECT_BRIEF tag one can provide an optional one line description
# for a project that appears at the top of each page and should give viewer a
# quick idea about the purpose of the project. Keep the description short.

PROJECT_BRIEF = "REXUS/BEXUS CubeSat payload flight software"
PROJECT_BRIEF = "REXUS/BEXUS Experiment On Board Flight Software documentation"

# With the PROJECT_LOGO tag one can specify a logo or an icon that is included
# in the documentation. The maximum height of the logo should not exceed 55
Expand Down
5 changes: 3 additions & 2 deletions src/Master/HAL/esp32/eth_w5500.c
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
/* W5500 Ethernet bring-up. This is a thin driver shim, same kind of thing
/** @brief W5500 Ethernet thingymabob!
* This is a thin driver shim, same kind of thing
* as can_bus_twai.c: it brings up a peripheral and reports link/IP state,
* owns no task and no queue of its own. Lives in Master's HAL because only
* Master has Ethernet - it isn't a reusable lib. */
* Master has Ethernet so it isn't reusable */
#include "eth_w5500.h"

#include <string.h>
Expand Down
7 changes: 3 additions & 4 deletions src/Master/HAL/esp32/eth_w5500.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,13 @@
#include "esp_err.h"
#include "freertos/FreeRTOS.h"

/* Bring up SPI + W5500 + esp_netif and start the driver. Non-blocking:
* link/IP arrive later via events. */
/* Bring up SPI + W5500 + esp_netif and start the driver */
esp_err_t ttc_eth_init(void);

/* True once the cable is up AND the interface has an IP. */
/* True once the cable is up AND the interface has an IP */
bool ttc_eth_ready(void);

/* Block until ready or timeout. Returns ttc_eth_ready(). */
/* Block until ready or timeout. Returns ttc_eth_ready() */
bool ttc_eth_wait_ready(TickType_t timeout);

#endif
5 changes: 4 additions & 1 deletion src/Master/HAL/esp32/pins.h
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
/** @file pins.h @brief Master board wiring. Only the ESP32 HAL includes this. */
/** @file pins.h
* @brief Master board wiring
*
*/
#ifndef HELIA_MASTER_PINS_H
#define HELIA_MASTER_PINS_H

Expand Down
3 changes: 1 addition & 2 deletions src/Master/HAL/sim/hal_sim.c
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
/* Master HAL for running on a laptop. */
/* Master HAL for running on ma laptop */
#include "hal.h"
#include <stdio.h>
#include <stdlib.h>

bool hal_init(void) {
/* The simulated CAN bus is added with the simulator step. */
return true;
}

Expand Down
14 changes: 4 additions & 10 deletions src/Master/control.c
Original file line number Diff line number Diff line change
@@ -1,16 +1,13 @@
/* Master control core: owns the flight-phase state machine (flight_phase.h
/** @brief Master control core: owns the flight-phase state machine (flight_phase.h
* does the actual FSM logic; this file just synchronises access to one
* instance across cores and hooks it up to CAN + logging).
*
* Two callers touch the flight state from two different cores:
* - master_control_task (this file, core 1): periodic no-motion tick.
* - master_control_task (this file, core 1): periodic no-motion tick
* - master_control_set_phase (called from ttc.c's uplink handler, core
* 0): ground-commanded phase changes.
* 0): ground-commanded phase changes
* Hence the mutex - see master_control_init()'s doc comment in control.h
* for why it's created eagerly from app_main() rather than lazily here.
*
* Guarded by ESP_PLATFORM so this compiles to nothing on the host (same
* as before) - flight_phase.c underneath it is what's host-testable.
*/
#include "control.h"

Expand All @@ -34,10 +31,7 @@ static uint32_t now_ms(void) {
return (uint32_t)pdTICKS_TO_MS(xTaskGetTickCount());
}

/* Tells every other node the phase changed - CAN_CMD_FLIGHT_PHASE, per the
* SED's Broadcast CAN Message Dictionary (source BROADCAST, type 0x03).
* Payload: 1 byte, the new master_phase_t value. Called with s_flight_mutex
* already held so the logged phase name matches what was just applied. */
/** @brief Tells every other node the phase changed */
static void broadcast_phase(master_phase_t phase, master_flight_result_t result) {
can_frame_t f = {
.id = can_make_id(CAN_PRIO_IMPORTANT, CAN_SRC_BROADCAST, CAN_CMD_FLIGHT_PHASE),
Expand Down
26 changes: 4 additions & 22 deletions src/Master/control.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,38 +11,20 @@ extern "C" {
* @brief Create the flight-phase state and its mutex. MUST be called once
* from app_main(), before any task that might call
* master_control_set_phase()/master_control_get_phase() is
* created (i.e. before ttc_uplink_task) - this avoids a boot-time
* race where a ground command could arrive before the mutex
* exists. master_control_task() itself only ticks; it doesn't
* create state, so its own creation order relative to this
* doesn't matter, only relative to ttc_uplink_task's.
* created (i.e. before ttc_uplink_task) other we will have a race condition
*/
void master_control_init(void);

/** @brief Control core task: periodically checks the DESCENDING -> LANDED
* no-motion timeout. Pinned to core 1 by setup.c. ESP32-only (see
* control.c); declared unconditionally here since only ESP32-only
* callers reference it. Requires master_control_init() to have
* run first. */
* no-motion timeout */
void master_control_task(void *arg);

/**
* @brief Thread-safe: apply a ground-commanded phase change. Called from
* ttc.c's uplink handler (core 0) when a CAN_CMD_FLIGHT_PHASE
* uplink command arrives - this is the flight manager's gate
* between ground commands and the rest of the system for that
* command. Broadcasts CAN_CMD_FLIGHT_PHASE to the bus and logs the
* result internally; the caller just needs the result to ACK
* ground correctly.
*
* Safe to call even before master_control_task's first loop
* iteration, but NOT before it has been created (the mutex it uses
* is created at task start) - setup.c creates control before ttc's
* uplink task, so this is naturally satisfied.
* @brief Thread-safe: apply a ground-commanded phase chang
*/
master_flight_result_t master_control_set_phase(master_phase_t next);

/** @brief Thread-safe read of the current flight phase. */
/** @brief Thread-safe read of the current flight phase */
master_phase_t master_control_get_phase(void);

#ifdef __cplusplus
Expand Down
2 changes: 1 addition & 1 deletion src/Master/flight_phase.c
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ static const char *PHASE_NAMES[MASTER_PHASE_COUNT] = {
[MASTER_PHASE_ASCENDING] = "ASCENDING",
[MASTER_PHASE_FLOAT] = "FLOAT",
[MASTER_PHASE_DESCENDING] = "DESCENDING",
[MASTER_PHASE_LANDED] = "LANDED",
[MASTER_PHASE_LANDED] = "LANDED", // This probably won't ever be sent over TT&C because radio connection will be lost
};

void master_flight_init(master_flight_t *f, uint32_t now_ms) {
Expand Down
30 changes: 8 additions & 22 deletions src/Master/flight_phase.h
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,9 @@ typedef enum {
} master_flight_result_t;

/** @brief Auto-transition DESCENDING -> LANDED after this long with no
* motion noted via master_flight_note_motion(). Placeholder value
* (5 minutes) - nothing feeds real motion data yet (see ttc.h/.c
* and control.c), so tune this once it does. */
* motion noted via master_flight_note_motion()
* Currently this is set to 5 minutes... It might be a little bit longer lmao (5 mins for testing)
* */
#define MASTER_LANDING_NO_MOTION_MS (5u * 60u * 1000u)

typedef struct {
Expand All @@ -64,35 +64,21 @@ typedef struct {
uint32_t last_motion_ms; /**< now_ms() at last noted motion (or phase entry) */
} master_flight_t;

/** @brief Starts in MASTER_PHASE_TESTING. */
/** @brief Starts in MASTER_PHASE_TESTING */
void master_flight_init(master_flight_t *f, uint32_t now_ms);

master_phase_t master_flight_phase(const master_flight_t *f);

/** @brief Human-readable phase name for logging. Never NULL. */
/** @brief Human-readable phase name for logging */
const char *master_phase_name(master_phase_t p);

/**
* @brief Apply a ground-commanded (or otherwise externally requested)
* phase change. See the "override" note above for how out-of-order
* requests are handled.
*/
master_flight_result_t master_flight_set_phase(master_flight_t *f, master_phase_t next, uint32_t now_ms);

/**
* @brief Call periodically (e.g. once per control-loop tick). Checks the
* DESCENDING -> LANDED no-motion timeout and applies it internally
* if due. No-op in any other phase.
*/
master_flight_result_t master_flight_set_phase(master_flight_t *f, master_phase_t next, uint32_t now_ms);
void master_flight_tick(master_flight_t *f, uint32_t now_ms);

/**
* @brief Call whenever motion is detected, to feed the no-motion timeout.
* TODO: nothing calls this yet - wire it to real Instrumentation
* CAN data (accel/altitude-rate) once that message exists. Until
* then DESCENDING always times out to LANDED
* MASTER_LANDING_NO_MOTION_MS after entry, regardless of actual
* motion.
* @brief Feeds the motion time out for landing detection.
* TODO: Nothing calls this, need to use CAN frames from instrumentation to update this (blocked)
*/
void master_flight_note_motion(master_flight_t *f, uint32_t now_ms);

Expand Down
47 changes: 11 additions & 36 deletions src/Master/setup.c
Original file line number Diff line number Diff line change
@@ -1,19 +1,10 @@
/* Master boot and task loop.
*
* Two cores, matching EPS/Instrumentation's comms/control split:
* core 0 (comms_task + ttc_uplink_task): CAN bus and the TT&C link.
* core 1 (master_control_task): flight manager. Placeholder for now.
* core 0 (comms_task + ttc_uplink_task): CAN bus and the TT&C link
* core 1 (master_control_task): flight manager (will be extended in the future)
*
* TT&C is deliberately minimal right now - the goal is proving Master and
* a ground script can talk at all, not the full flight design:
* - comms_task mirrors every CAN frame it sees down to ground over UDP -
* both what Master itself sends (its heartbeat, ACKs, etc, from
* master_comms_on_tick()/on_frame()) and every frame it receives from
* other nodes on the bus (rx, via can_bus_recv()). No separate task,
* no queue, no filtering - just a socket call next to the existing CAN
* send/recv, so ground sees the whole bus, not just Master's traffic.
* - ttc_uplink_task (in ttc.c) logs and ACKs whatever ground sends over
* TCP. Nothing is forwarded to CAN yet.
* TT&C is deliberately minimal right now, just enough to mirror CAN traffic down to ground for bench testing
*/
#include <stdint.h>
#include "freertos/FreeRTOS.h"
Expand Down Expand Up @@ -46,12 +37,12 @@ static void send_all(const can_frame_t *tx, size_t n) {
if (!can_bus_send(&tx[i], TX_TIMEOUT_MS)) {
ESP_LOGW(TAG, "send failed, id=0x%03lX", (unsigned long)tx[i].id);
} else if (can_id_type(tx[i].id) != CAN_MSG_HEARTBEAT || can_id_source(tx[i].id) != CAN_SRC_MASTER) {
ESP_LOGI(TAG, "sent: %s", master_describe_frame(&tx[i], text, sizeof text)); /* don't log our own heartbeat */
ESP_LOGI(TAG, "sent: %s", master_describe_frame(&tx[i], text, sizeof text)); /* don't log our own heartbeat ;-; */
}
}
}

/* "EPS online" / "EPS LOST" whenever a node appears or goes silent */
/* "EPS online" / "EPS LOST" whenever a node appears or goes silent (good for debugging restarting) */
static void log_presence_changes(uint8_t before, uint8_t after) {
for (unsigned i = 0; i < MASTER_NODE_SLOTS; i++) {
uint8_t bit = (uint8_t)(1u << i);
Expand All @@ -63,11 +54,7 @@ static void log_presence_changes(uint8_t before, uint8_t after) {
}
}

/* Packs and mirrors one CAN frame down to ground over UDP. No-ops quietly
* if the socket isn't up or the link isn't ready yet - same "just drop it,
* same as ground not being switched on" philosophy as before. Pulled out
* to a helper because it's now called for every frame Master sees on the
* bus, not just its own outgoing ones - see comms_task. */
/* Packs and mirrors one CAN frame down to ground over UDP. This will fail silently for now (TODO) */
static void downlink_frame(int sock, const struct sockaddr_in *ground, uint8_t *seq, const can_frame_t *f) {
if (sock < 0 || !ttc_eth_ready())
return;
Expand All @@ -85,33 +72,23 @@ static void comms_task(void *arg) {
ESP_LOGI(TAG, "state %s, auto-unlock %s", fsm_state_name(comms.state), MASTER_AUTO_UNLOCK ? "ON" : "OFF");

/* One UDP socket, opened once, used to mirror CAN traffic down to
* ground - both frames Master itself sends AND every frame it
* receives from other nodes, which is what gives ground full-bus
* visibility rather than just Master's own heartbeat. No retries
* beyond what sendto() does on its own - if the link is down the
* packet is just dropped, same as the ground station not being
* switched on. */
* ground */
int dl_sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP);
struct sockaddr_in ground = {
.sin_family = AF_INET,
.sin_port = htons(CONFIG_TTC_UDP_DOWNLINK_PORT),
.sin_addr.s_addr = inet_addr(CONFIG_TTC_GROUND_IP),
};
uint8_t dl_seq = 0; /* one running counter across everything mirrored down,
* so ground can spot drops in the combined stream */
uint8_t dl_seq = 0;

for (;;) {
while (1) {
can_frame_t rx, tx[MASTER_COMMS_MAX_TX];
char text[96];
uint8_t alive_before = comms.alive_mask;

if (can_bus_recv(&rx, RX_TIMEOUT_MS)) {
ESP_LOGI(TAG, "rx: %s", master_describe_frame(&rx, text, sizeof text));
/* CAN controllers don't loop a node's own transmitted frames
* back to its own RX, so this is the ONLY path that captures
* traffic from every other node - EPS/Instrumentation/
* Photonics/etc - Master's own tx frames are mirrored
* separately below. */

downlink_frame(dl_sock, &ground, &dl_seq, &rx);
send_all(tx, master_comms_on_frame(&comms, now_ms(), &rx, tx));
}
Expand All @@ -137,9 +114,7 @@ void app_main(void) {
if (!hal_init())
ESP_LOGE(TAG, "CAN init failed");

/* Must run before ttc_uplink_task is created: that task can call
* master_control_set_phase() as soon as a ground command arrives, and
* this is what creates the mutex it needs. See control.h. */
/* Must run before ttc_uplink_task is created */
master_control_init();

xTaskCreatePinnedToCore(comms_task, "master_comms", 4096, NULL, 10, NULL, 0);
Expand Down
24 changes: 7 additions & 17 deletions src/Master/ttc.c
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@

#include <string.h>

/* Wire format constants (SED "Downlink (UDP)" proposal). Needed on host too,
* since ttc_pack_downlink() is pure byte-packing and is built there. */
#define TTC_DL_HDR_LEN 4
#define TTC_DL_FRAME_LEN 11

Expand All @@ -24,10 +22,10 @@ size_t ttc_pack_downlink(uint8_t *pkt, size_t pkt_len, uint8_t seq, const can_fr
return TTC_DL_HDR_LEN + TTC_DL_FRAME_LEN;
}

/* Everything below owns a task and sockets, so it's ESP-IDF-only - guarded
* the same way as control.c, so the host node_Master target (linked against
* HAL/sim/) builds this file down to just ttc_pack_downlink() above,
* host-testable like any other pure helper. */
/* Everything below owns a task and sockets, so it's ESP-IDF-only
* This is very close to what the old ttc_uplink_task() did, but now it's a separate file
* I'm very close to putting this in the HAL layer (TODO?)
*/
#ifdef ESP_PLATFORM

#include <errno.h>
Expand All @@ -45,7 +43,6 @@ static const char *TAG = "master_ttc";

#define RX_BUF_LEN 64

/* Wire format constants (SED "Uplink (TCP)" proposal - see ttc_ground.py). */
#define TTC_UL_SYNC 0xA5
#define TTC_UL_HDR_LEN 4
#define TTC_UL_MAX_PAYLOAD 8
Expand Down Expand Up @@ -82,16 +79,9 @@ static void send_ack(int sock, uint8_t cmd, uint8_t flags, uint8_t status,
send_all(sock, ack, TTC_ACK_HDR_LEN + len);
}

/* No general CAN forwarding yet - see ttc.h. Two commands are handled for
* real so far:
* - TTC_TEST_PING (payload echoed back) so a round-trip time can be
* measured with nothing else in the loop.
* - CAN_CMD_FLIGHT_PHASE: the first command actually gated through the
* flight manager (control.c) before anything happens, rather than
* just being logged and ACKed. This is the pattern every other
* command will eventually follow.
* Everything else is still just logged and ACKed OK, which is enough to
* prove ground's command reached the board. */
/* No general CAN forwarding yet - see ttc.h
* This is just a proof of concept for the EAR demo, full TT&C still need to be tested
*/
static void handle_command(int sock, uint8_t cmd, uint8_t flags, const uint8_t *p, uint8_t len) {
if ((flags & TTC_UL_FLAG_TEST) && cmd == TTC_TEST_PING) {
send_ack(sock, cmd, flags, TTC_ACK_OK, p, len);
Expand Down
Loading
Loading