saltylab-firmware/include/uart_protocol.h
sl-jetson 25f0711309 feat: Jetson Orin system monitor ROS2 node (Issue #631)
New package saltybot_system_monitor:
- jetson_stats.py: pure-Python data layer (JetsonStats, CpuCore,
  TegrastatsParser, JtopReader, TegrastatsReader, MockReader,
  AlertChecker, AlertThresholds) — no ROS2 dependency
- system_monitor_node.py: ROS2 node publishing /saltybot/system/stats
  (JSON) and /saltybot/diagnostics (DiagnosticArray) at 1 Hz
- Alerts: CPU/GPU >85% WARN (+10% ERROR), temp >80°C, disk/RAM >90%,
  power >30 W; each alert produces a DiagnosticStatus entry
- Stats source priority: jtop > tegrastats > mock (auto-detected)
- config/system_monitor.yaml: all thresholds and rate tunable via params
- launch/system_monitor.launch.py: single-node launch with config arg
- test/test_system_monitor.py: 50+ pytest tests, ROS2-free

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-15 14:38:43 -04:00

130 lines
5.2 KiB
C

#ifndef UART_PROTOCOL_H
#define UART_PROTOCOL_H
#include <stdint.h>
#include <stdbool.h>
/*
* uart_protocol.h -- Structured UART command protocol for Jetson-STM32
* communication (Issue #629)
*
* Hardware: UART5, PC12=TX / PD2=RX, 115200 baud
* Note: spec references USART1 but USART1 is occupied by JLink (Issue #120)
* at 921600 baud. UART5 is the designated secondary/debug UART and is used
* here as the structured-command channel.
*
* Frame format (both directions):
* [STX=0x02][LEN][CMD][PAYLOAD...][CRC8][ETX=0x03]
*
* STX : frame start sentinel (0x02)
* LEN : CMD byte + payload bytes (1 + payload_len)
* CMD : command / response type byte
* PAYLOAD : 0..N bytes, CMD-dependent
* CRC8 : CRC8-SMBUS (poly 0x07, init 0x00) over CMD+PAYLOAD
* ETX : frame end sentinel (0x03)
*
* Host (Jetson) to STM32 commands:
* 0x01 SET_VELOCITY - int16 left_rpm, int16 right_rpm (4 bytes)
* 0x02 GET_STATUS - no payload; reply URESP_STATUS
* 0x03 SET_PID - float kp, float ki, float kd (12 bytes, LE)
* 0x04 ESTOP - no payload; engage emergency stop
* 0x05 CLEAR_ESTOP - no payload; clear emergency stop
*
* STM32 to host responses:
* 0x80 ACK - uint8 echoed_cmd (1 byte)
* 0x81 NACK - uint8 echoed_cmd, uint8 error_code (2 bytes)
* 0x82 STATUS - uart_prot_status_t (8 bytes)
*
* Heartbeat: if no valid frame is received within UART_PROT_HB_TIMEOUT_MS,
* uart_protocol_is_active() returns false and velocity commands are ignored.
*
* DMA: UART5_RX uses DMA1_Stream0_Channel4 in circular mode (256-byte ring).
* Parser runs in uart_protocol_process() called from the main loop.
*/
/* ---- Frame constants ---- */
#define UPROT_STX 0x02u
#define UPROT_ETX 0x03u
#define UPROT_RX_BUF_LEN 256u /* DMA ring buffer size (power of 2) */
#define UPROT_MAX_PAYLOAD 16u /* largest payload: SET_PID = 12 bytes */
/* ---- Command IDs (host to STM32) ---- */
#define UCMD_SET_VELOCITY 0x01u /* int16 left_rpm + int16 right_rpm */
#define UCMD_GET_STATUS 0x02u /* no payload */
#define UCMD_SET_PID 0x03u /* float kp, ki, kd (12 bytes LE) */
#define UCMD_ESTOP 0x04u /* no payload */
#define UCMD_CLEAR_ESTOP 0x05u /* no payload */
/* ---- Response IDs (STM32 to host) ---- */
#define URESP_ACK 0x80u /* 1-byte payload: echoed CMD */
#define URESP_NACK 0x81u /* 2-byte payload: CMD + error_code */
#define URESP_STATUS 0x82u /* 8-byte payload: uart_prot_status_t */
/* ---- NACK error codes ---- */
#define UERR_UNKNOWN_CMD 0x01u /* unrecognised CMD byte */
#define UERR_BAD_LENGTH 0x02u /* LEN does not match CMD expectation */
#define UERR_BAD_CRC 0x03u /* CRC8 mismatch */
#define UERR_REFUSED 0x04u /* command refused in current state */
/* ---- STATUS response payload (8 bytes, packed) ---- */
typedef struct __attribute__((packed)) {
int16_t pitch_x10; /* pitch angle, degrees x10 (0.1° resolution) */
int16_t motor_cmd; /* balance PID ESC output, -1000..+1000 */
uint16_t vbat_mv; /* battery voltage (mV) */
uint8_t balance_state; /* BalanceState: 0=DISARMED, 1=ARMED, 2=TILT */
uint8_t estop_active; /* 1 = estop currently engaged */
} uart_prot_status_t; /* 8 bytes */
/* ---- Volatile state (read from main loop) ---- */
typedef struct {
/* SET_VELOCITY: set by parser, cleared by main loop */
volatile uint8_t vel_updated; /* 1 = new velocity command pending */
volatile int16_t left_rpm; /* requested left wheel RPM */
volatile int16_t right_rpm; /* requested right wheel RPM */
/* SET_PID: set by parser, cleared by main loop */
volatile uint8_t pid_updated; /* 1 = new PID gains pending */
volatile float pid_kp;
volatile float pid_ki;
volatile float pid_kd;
/* ESTOP / CLEAR_ESTOP: set by parser, cleared by main loop */
volatile uint8_t estop_req;
volatile uint8_t estop_clear_req;
/* Heartbeat: updated on every valid frame */
volatile uint32_t last_rx_ms; /* HAL_GetTick() at last good frame; 0=none */
} UartProtState;
extern volatile UartProtState uart_prot_state;
/* ---- API ---- */
/*
* uart_protocol_init() -- configure UART5 + DMA1_Stream0 and start reception.
* Call once during system init, before the main loop.
*/
void uart_protocol_init(void);
/*
* uart_protocol_process() -- parse new bytes from DMA ring buffer, dispatch
* commands, and send ACK/NACK/STATUS responses.
* Call every main loop tick (non-blocking, < 5 µs when idle).
*/
void uart_protocol_process(void);
/*
* uart_protocol_is_active(now_ms) -- returns true if a valid frame was
* received within UART_PROT_HB_TIMEOUT_MS. Main loop uses this to gate
* SET_VELOCITY commands when the host is disconnected.
*/
bool uart_protocol_is_active(uint32_t now_ms);
/*
* uart_protocol_send_status(s) -- transmit a URESP_STATUS (0x82) frame.
* Called from main loop in response to GET_STATUS or periodically.
*/
void uart_protocol_send_status(const uart_prot_status_t *s);
#endif /* UART_PROTOCOL_H */