feat(firmware): BNO085 node, concentrador y listener PGN 127237
AR_electronics — AR-Autopilot Project Tarea #3 — firmware/ar_bno085_node_v1/ Nodo compacto ESP32+CAN lee heading y yaw rate del BNO085 via I2C y los publica en el backbone NMEA 2000 como PGN 127250+127251 a 10 Hz. Tarea #4 — firmware/ar_concentrador_v1/ Gateway NMEA2000<->NMEA0183 con 4 puertos OUT (UART1 TX broadcast) y 4 puertos IN (UART2 RX comandos). Parsea sentencias $PARP, gestiona autoridad de mando entre estaciones con timeout 10s y override del puente. Reenvía comandos autopilot como PGN 127237 al backbone. Tarea #5 — nmea2000_consumer (ar_autopilot_v1) Listener PGN 127237 entrante desde concentrador: HeadingControlSnapshot, HandleHeadingControl() con filtro source address, nmea2000_htc() publico, ventana stale 3s para comandos externos.
This commit is contained in:
@@ -51,6 +51,17 @@ NavDataSnapshot g_nav_data{
|
||||
.valid = false,
|
||||
};
|
||||
|
||||
HeadingControlSnapshot g_htc{
|
||||
.commanded_heading_deg = NAN,
|
||||
.mode = HtcMode::UNKNOWN,
|
||||
.source_addr = 0xFF,
|
||||
.age_ms = 0,
|
||||
.valid = false,
|
||||
};
|
||||
|
||||
// Our own NMEA2000 source address — filter out self-echoes.
|
||||
constexpr uint8_t OWN_SOURCE_ADDR = 25;
|
||||
|
||||
float rad_to_deg_pos(float rad) {
|
||||
float d = rad * (180.0f / (float)M_PI);
|
||||
// Normalise to 0..360.
|
||||
@@ -155,12 +166,67 @@ void HandleNavData(const tN2kMsg& msg) {
|
||||
AR_LOGV(TAG, "PGN 129284 XTE=%.2f m DTW=%.0f m", xte_m, dtw_m);
|
||||
}
|
||||
|
||||
void HandleHeadingControl(const tN2kMsg& msg) {
|
||||
// Ignore our own broadcasts — concentrador uses a different source address.
|
||||
if (msg.Source == OWN_SOURCE_ADDR) return;
|
||||
|
||||
tN2kOnOff rud_lim, off_hdg, off_trk, override_st;
|
||||
tN2kSteeringMode steering_mode;
|
||||
tN2kTurnMode turn_mode;
|
||||
tN2kHeadingReference hdg_ref;
|
||||
tN2kRudderDirectionOrder cmd_rud_dir;
|
||||
double cmd_rud_angle = N2kDoubleNA;
|
||||
double hdg_to_steer = N2kDoubleNA;
|
||||
double track = N2kDoubleNA;
|
||||
double rud_limit = N2kDoubleNA;
|
||||
double off_hdg_limit = N2kDoubleNA;
|
||||
double radius = N2kDoubleNA;
|
||||
double rot_order = N2kDoubleNA;
|
||||
double xte_lim = N2kDoubleNA;
|
||||
double vessel_hdg = N2kDoubleNA;
|
||||
|
||||
if (!ParseN2kHeadingTrackControl(msg,
|
||||
rud_lim, off_hdg, off_trk, override_st,
|
||||
steering_mode, turn_mode, hdg_ref,
|
||||
cmd_rud_dir, cmd_rud_angle,
|
||||
hdg_to_steer, track,
|
||||
rud_limit, off_hdg_limit, radius, rot_order,
|
||||
xte_lim, vessel_hdg)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Map NMEA2000 steering mode to our internal enum.
|
||||
HtcMode our_mode;
|
||||
switch (steering_mode) {
|
||||
case N2kSM_HeadingControl: our_mode = HtcMode::HEADING_HOLD; break;
|
||||
case N2kSM_MainSteering: our_mode = HtcMode::STANDBY; break;
|
||||
default: return; // ignore unsupported modes
|
||||
}
|
||||
|
||||
const float hdg_deg = (hdg_to_steer < 1e8)
|
||||
? rad_to_deg_pos((float)hdg_to_steer)
|
||||
: NAN;
|
||||
|
||||
const uint32_t now = millis();
|
||||
portENTER_CRITICAL(&g_mux);
|
||||
g_htc.commanded_heading_deg = hdg_deg;
|
||||
g_htc.mode = our_mode;
|
||||
g_htc.source_addr = msg.Source;
|
||||
g_htc.age_ms = now;
|
||||
g_htc.valid = true;
|
||||
portEXIT_CRITICAL(&g_mux);
|
||||
|
||||
AR_LOGI(TAG, "PGN 127237 src=%d mode=%d hdg=%.2f deg",
|
||||
msg.Source, (int)our_mode, hdg_deg);
|
||||
}
|
||||
|
||||
void MessageHandler(const tN2kMsg& msg) {
|
||||
switch (msg.PGN) {
|
||||
case 127250L: HandleHeading(msg); break;
|
||||
case 127251L: HandleROT(msg); break;
|
||||
case 129026L: HandleCogSog(msg); break;
|
||||
case 129284L: HandleNavData(msg); break;
|
||||
case 127237L: HandleHeadingControl(msg); break;
|
||||
case 127250L: HandleHeading(msg); break;
|
||||
case 127251L: HandleROT(msg); break;
|
||||
case 129026L: HandleCogSog(msg); break;
|
||||
case 129284L: HandleNavData(msg); break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
@@ -186,6 +252,11 @@ void RxTask(void* /*pv*/) {
|
||||
if (g_nav_data.valid && (now - g_nav_data.age_ms) > STALE_THRESHOLD_MS) {
|
||||
g_nav_data.valid = false;
|
||||
}
|
||||
// HTC commands have a shorter stale window (3 s) — if the remote stops
|
||||
// sending, we don't want to keep acting on an old command.
|
||||
if (g_htc.valid && (now - g_htc.age_ms) > 3000U) {
|
||||
g_htc.valid = false;
|
||||
}
|
||||
portEXIT_CRITICAL(&g_mux);
|
||||
// 100 Hz polling is plenty -- the CAN driver buffers incoming frames.
|
||||
vTaskDelay(pdMS_TO_TICKS(10));
|
||||
@@ -262,4 +333,16 @@ bool nmea2000_cog_is_stale() {
|
||||
return !nmea2000_cog_sog().valid;
|
||||
}
|
||||
|
||||
HeadingControlSnapshot nmea2000_htc() {
|
||||
HeadingControlSnapshot copy;
|
||||
portENTER_CRITICAL(&g_mux);
|
||||
copy = g_htc;
|
||||
portEXIT_CRITICAL(&g_mux);
|
||||
return copy;
|
||||
}
|
||||
|
||||
bool nmea2000_htc_is_stale() {
|
||||
return !nmea2000_htc().valid;
|
||||
}
|
||||
|
||||
} // namespace arautopilot::protocols::nmea2000
|
||||
|
||||
@@ -53,6 +53,25 @@ struct NavDataSnapshot {
|
||||
bool valid; ///< fresh (<5 s) and non-NaN
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// PGN 127237 -- Heading Track Control (incoming command from concentrador)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
enum class HtcMode : uint8_t {
|
||||
STANDBY = 0, ///< Autopilot off / manual steering
|
||||
HEADING_HOLD = 1, ///< Hold commanded heading
|
||||
TRACK = 2, ///< Track to waypoint (NMEA route)
|
||||
UNKNOWN = 0xFF,
|
||||
};
|
||||
|
||||
struct HeadingControlSnapshot {
|
||||
float commanded_heading_deg; ///< 0..360 (NAN if not set)
|
||||
HtcMode mode; ///< desired autopilot mode
|
||||
uint8_t source_addr; ///< NMEA2000 source address of sender
|
||||
uint32_t age_ms; ///< millis() at last 127237 update
|
||||
bool valid; ///< fresh (<3 s) and parsed correctly
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Public API
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -78,4 +97,11 @@ bool nmea2000_is_stale();
|
||||
/// True if COG/SOG age exceeds 5 s or data was never received.
|
||||
bool nmea2000_cog_is_stale();
|
||||
|
||||
/// Thread-safe read of the latest Heading Track Control command (PGN 127237).
|
||||
/// Returns the last command received from an external concentrador.
|
||||
HeadingControlSnapshot nmea2000_htc();
|
||||
|
||||
/// True if no PGN 127237 has been received in the last 3 s.
|
||||
bool nmea2000_htc_is_stale();
|
||||
|
||||
} // namespace arautopilot::protocols::nmea2000
|
||||
|
||||
Reference in New Issue
Block a user