feat: modern HUD dashboard + telemetry expansion (#43)

ui/index.html — full dashboard rewrite:
- 3-column layout: LEFT telemetry gauges, CENTER 3D SaltyBot, RIGHT comms
- LEFT: artificial horizon (canvas, pitch/roll/ladder/roll-arc), yaw compass
  tape, pitch/roll/yaw readouts, bidirectional motor bar, battery bar, BME280
  environment section (auto-shows on data), MAG heading row
- CENTER: Three.js SaltyBot model (PR#41) with ground plane + animated
  wheel rolling proportional to motor_cmd
- RIGHT: USB tx/rx packet counters, mode badge (MANUAL/ASSISTED/AUTO),
  CRSF RSSI/LQ, dual RC stick overlay canvases (CH1–4), Jetson active dot
- BOTTOM: KP/KI/KD/SP/MAX sliders with APPLY + QUERY, collapsible log console
- Style: Tailwind CSS CDN, dark cyberpunk theme, neon cyan + orange accents

src/main.c — telemetry JSON additions:
- buf: 256 → 320 bytes (headroom for new fields)
- ja: Jetson active flag (0/1) via jetson_cmd_is_active()
- txc: TX telemetry frame counter (uint32, main-loop local)
- rxc: RX CDC packet counter (cdc_rx_count from usbd_cdc_if)
- ch1–ch4: CRSF channels mapped to µs (1000–2000) via crsf_to_range(),
  appended alongside rssi/lq when RC is alive

lib/USB_CDC/src/usbd_cdc_if.c:
- cdc_rx_count: volatile uint32_t, incremented in CDC_Receive on every
  packet; extern'd in main.c for telemetry

Closes #43.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
sl-firmware 2026-02-28 22:41:02 -05:00
parent 2a12aac86f
commit 6dc7aea32f
3 changed files with 774 additions and 330 deletions

View File

@ -7,6 +7,7 @@ static volatile uint8_t cdc_port_open = 0; /* set when host asserts DTR */
volatile uint8_t cdc_arm_request = 0; /* set by A command */ volatile uint8_t cdc_arm_request = 0; /* set by A command */
volatile uint8_t cdc_disarm_request = 0; /* set by D command */ volatile uint8_t cdc_disarm_request = 0; /* set by D command */
volatile uint8_t cdc_recal_request = 0; /* set by G command — gyro recalibration */ volatile uint8_t cdc_recal_request = 0; /* set by G command — gyro recalibration */
volatile uint32_t cdc_rx_count = 0; /* total CDC packets received from host */
/* /*
* PID tuning command buffer. * PID tuning command buffer.
@ -174,6 +175,7 @@ static int8_t CDC_Receive(uint8_t *buf, uint32_t *len) {
} }
done: done:
cdc_rx_count++;
USBD_CDC_SetRxBuffer(&hUsbDevice, UserRxBuffer); USBD_CDC_SetRxBuffer(&hUsbDevice, UserRxBuffer);
USBD_CDC_ReceivePacket(&hUsbDevice); USBD_CDC_ReceivePacket(&hUsbDevice);
return USBD_OK; return USBD_OK;

View File

@ -24,6 +24,7 @@ extern volatile uint8_t cdc_streaming; /* set by S command in CDC RX */
extern volatile uint8_t cdc_arm_request; /* set by A command */ extern volatile uint8_t cdc_arm_request; /* set by A command */
extern volatile uint8_t cdc_disarm_request; /* set by D command */ extern volatile uint8_t cdc_disarm_request; /* set by D command */
extern volatile uint8_t cdc_recal_request; /* set by G command */ extern volatile uint8_t cdc_recal_request; /* set by G command */
extern volatile uint32_t cdc_rx_count; /* total CDC packets received */
/* /*
* Apply a PID tuning command string from the USB terminal. * Apply a PID tuning command string from the USB terminal.
@ -156,8 +157,9 @@ int main(void) {
*/ */
safety_init(); safety_init();
char buf[256]; char buf[320];
int len; int len;
uint32_t tx_count = 0; /* telemetry frames sent to host */
IMUData imu; IMUData imu;
uint32_t send_tick = 0; uint32_t send_tick = 0;
@ -340,17 +342,27 @@ int main(void) {
} }
} }
if (safety_rc_alive(now)) { if (safety_rc_alive(now)) {
/* RSSI in dBm (negative), link quality 0-100% */ /* RSSI, link quality, and CH1CH4 mapped to µs (10002000) */
n = snprintf(p, rem, ",\"rssi\":%d,\"lq\":%d", n = snprintf(p, rem, ",\"rssi\":%d,\"lq\":%d"
(int)crsf_state.rssi_dbm, (int)crsf_state.link_quality); ",\"ch1\":%d,\"ch2\":%d,\"ch3\":%d,\"ch4\":%d",
(int)crsf_state.rssi_dbm, (int)crsf_state.link_quality,
(int)crsf_to_range(crsf_state.channels[0], 1000, 2000),
(int)crsf_to_range(crsf_state.channels[1], 1000, 2000),
(int)crsf_to_range(crsf_state.channels[2], 1000, 2000),
(int)crsf_to_range(crsf_state.channels[3], 1000, 2000));
p += n; rem -= n; p += n; rem -= n;
} }
n = snprintf(p, rem, "}\n"); /* Jetson active flag, USB TX/RX packet counters */
n = snprintf(p, rem, ",\"ja\":%d,\"txc\":%u,\"rxc\":%u}\n",
jetson_cmd_is_active(now) ? 1 : 0,
(unsigned)tx_count,
(unsigned)cdc_rx_count);
len = (int)(p + n - buf); len = (int)(p + n - buf);
} else { } else {
len = snprintf(buf, sizeof(buf), "{\"err\":%d}\n", imu_ret); len = snprintf(buf, sizeof(buf), "{\"err\":%d}\n", imu_ret);
} }
CDC_Transmit((uint8_t *)buf, len); CDC_Transmit((uint8_t *)buf, len);
tx_count++;
} }
status_update(now, (imu_ret == 0), status_update(now, (imu_ret == 0),

File diff suppressed because it is too large Load Diff