#ifndef VESC_CAN_H
#define VESC_CAN_H

#include <stdint.h>
#include <stdbool.h>

/*
 * vesc_can — VESC CAN protocol driver for FSESC 6.7 Pro Mini Dual (Issue #674).
 *
 * VESC uses 29-bit extended CAN IDs:
 *   arbitration_id = (packet_type << 8) | vesc_node_id
 *
 * Wire format is big-endian throughout (matches VESC FW 6.x).
 *
 * Physical layer: CAN2 on PB12 (RX, AF9) / PB13 (TX, AF9) at 500 kbps.
 *
 * NOTE ON PA11/PA12 vs PB12/PB13:
 *   PA11/PA12 carry CAN1_RX/TX (AF9) BUT are also USB_OTG_FS DM/DP (AF10).
 *   USB CDC is active on this board, so PA11/PA12 are occupied.
 *   PB8/PB9 (CAN1 alternate) are occupied by I2C1 (barometer).
 *   CAN2 on PB12/PB13 is the only conflict-free choice.
 *   If the SN65HVD230 is wired to the pads labelled RX6/TX6 on the Mamba
 *   silkscreen, those pads connect to PB12/PB13 (SPI2/OSD, repurposed).
 *
 * VESC frames arrive in FIFO1 (extended-ID filter, bank 15).
 * Orin standard frames arrive in FIFO0 (standard-ID filter, bank 14).
 */

/* ---- VESC packet type IDs (upper byte of 29-bit arb ID) ---- */
#define VESC_PKT_SET_DUTY           0u   /* int32 duty × 100000           */
#define VESC_PKT_SET_CURRENT        1u   /* int32 current (mA)            */
#define VESC_PKT_SET_CURRENT_BRAKE  2u   /* int32 brake current (mA)      */
#define VESC_PKT_SET_RPM            3u   /* int32 target RPM              */
#define VESC_PKT_STATUS             9u   /* int32 RPM, int16 I×10, int16 duty×1000 */
#define VESC_PKT_STATUS_4           16u  /* int16 T_fet×10, T_mot×10, I_in×10 */
#define VESC_PKT_STATUS_5           27u  /* int32 tacho, int16 V_in×10   */

/* ---- Default VESC node IDs (configurable via vesc_can_init) ---- */
#define VESC_CAN_ID_LEFT            56u
#define VESC_CAN_ID_RIGHT           68u

/* ---- Alive timeout ---- */
#define VESC_ALIVE_TIMEOUT_MS       1000u  /* node offline if no STATUS for 1 s */

/* ---- JLink telemetry rate ---- */
#define VESC_TLM_HZ                 1u

/* ---- Fault codes (VESC FW 6.6) ---- */
#define VESC_FAULT_NONE                      0u
#define VESC_FAULT_OVER_VOLTAGE              1u
#define VESC_FAULT_UNDER_VOLTAGE             2u
#define VESC_FAULT_DRV                       3u
#define VESC_FAULT_ABS_OVER_CURRENT          4u
#define VESC_FAULT_OVER_TEMP_FET             5u
#define VESC_FAULT_OVER_TEMP_MOTOR           6u
#define VESC_FAULT_GATE_DRIVER_OVER_VOLTAGE  7u
#define VESC_FAULT_GATE_DRIVER_UNDER_VOLTAGE 8u
#define VESC_FAULT_MCU_UNDER_VOLTAGE         9u
#define VESC_FAULT_WATCHDOG_RESET            10u

/* ---- Telemetry state per VESC node ---- */
typedef struct {
    int32_t  rpm;             /* actual RPM (STATUS pkt, int32 BE)           */
    int16_t  current_x10;    /* phase current (A × 10; STATUS pkt)          */
    int16_t  duty_x1000;     /* duty cycle (× 1000; –1000..+1000)           */
    int16_t  temp_fet_x10;   /* FET temperature (°C × 10; STATUS_4)         */
    int16_t  temp_motor_x10; /* motor temperature (°C × 10; STATUS_4)       */
    int16_t  current_in_x10; /* input (battery) current (A × 10; STATUS_4)  */
    int16_t  voltage_x10;    /* input voltage (V × 10; STATUS_5)            */
    uint8_t  fault_code;     /* VESC fault code (0 = none)                  */
    uint8_t  _pad;
    uint32_t last_rx_ms;     /* HAL_GetTick() of last received STATUS frame  */
} vesc_state_t;

/* ---- API ---- */

/*
 * vesc_can_init(id_left, id_right) — store VESC node IDs and register the
 * extended-frame callback with can_driver.
 * Call after can_driver_init().
 */
void vesc_can_init(uint8_t id_left, uint8_t id_right);

/*
 * vesc_can_send_rpm(vesc_id, rpm) — transmit VESC_PKT_SET_RPM (3) to the
 * target VESC.  arb_id = (3 << 8) | vesc_id.  Payload: int32 big-endian.
 */
void vesc_can_send_rpm(uint8_t vesc_id, int32_t rpm);

/*
 * vesc_can_on_frame(ext_id, data, len) — called by can_driver when an
 * extended-ID frame arrives (registered via can_driver_set_ext_cb).
 * Parses STATUS / STATUS_4 / STATUS_5 into the matching vesc_state_t.
 */
void vesc_can_on_frame(uint32_t ext_id, const uint8_t *data, uint8_t len);

/*
 * vesc_can_get_state(vesc_id, out) — copy latest telemetry snapshot.
 * vesc_id must match id_left or id_right passed to vesc_can_init.
 * Returns false if vesc_id unknown or no frame has arrived yet.
 */
bool vesc_can_get_state(uint8_t vesc_id, vesc_state_t *out);

/*
 * vesc_can_is_alive(vesc_id, now_ms) — true if a STATUS frame arrived
 * within VESC_ALIVE_TIMEOUT_MS of now_ms.
 */
bool vesc_can_is_alive(uint8_t vesc_id, uint32_t now_ms);

/*
 * vesc_can_send_tlm(now_ms) — rate-limited JLINK_TLM_VESC_STATE (0x8E)
 * telemetry to Orin over JLink.  Safe to call every ms; internally
 * rate-limited to VESC_TLM_HZ (1 Hz).
 */
void vesc_can_send_tlm(uint32_t now_ms);

#endif /* VESC_CAN_H */
