// ============================================================================= // protocols/nmea2000_publisher.cpp -- NMEA 2000 publisher (Sprint 6) // ============================================================================= #include "nmea2000_publisher.h" #include #include #include #include // NMEA2000_CAN.h defines the global tNMEA2000& NMEA2000 in the header, // so it can only be included in one translation unit (nmea2000_consumer.cpp). // Declare the same object here via extern. extern tNMEA2000& NMEA2000; #include "../hal/rudder_sensor.h" #include "../modes/standby.h" #include "../pid/pid_outer_task.h" #include "../protocols/nmea2000_consumer.h" #include "../system/ar_log.h" #include "../system/task_config.h" namespace arautopilot::protocols::nmea2000 { namespace { constexpr const char* TAG = "AR/N2K/PUB"; static const double K_DEG2RAD = M_PI / 180.0; // ----- PGN 127245: Rudder angle (10 Hz) ------------------------------------- void publish_rudder() { const auto rdr = hal::rudder_sensor_latest(); if (!rdr.valid) return; tN2kMsg msg; SetN2kRudder(msg, (double)rdr.angle_deg * K_DEG2RAD, 0, N2kRDO_NoDirectionOrder, N2kDoubleNA); NMEA2000.SendMsg(msg); } // ----- PGN 127237: Heading Track Control (1 Hz) ----------------------------- void publish_heading_track_control() { const bool engaged = !modes::is_standby(); const tN2kSteeringMode steering_mode = engaged ? N2kSM_HeadingControl : N2kSM_MainSteering; double heading_to_steer = N2kDoubleNA; double vessel_heading = N2kDoubleNA; // Current heading from consumer snapshot for VesselHeading field. { const auto n = nmea2000_latest(); if (n.heading_valid) { vessel_heading = (double)n.heading_deg * K_DEG2RAD; } } if (engaged) { const modes::Mode mode = modes::current_mode(); if (mode == modes::Mode::HEADING_HOLD) { heading_to_steer = (double)pid::pid_outer_heading_setpoint_deg() * K_DEG2RAD; } else if (mode == modes::Mode::TRUE_COURSE || mode == modes::Mode::TRACK_KEEPING) { heading_to_steer = (double)pid::pid_outer_cog_setpoint_deg() * K_DEG2RAD; } } tN2kMsg msg; SetN2kHeadingTrackControl(msg, N2kOnOff_Unavailable, // RudderLimitExceeded N2kOnOff_Unavailable, // OffHeadingLimitExceeded N2kOnOff_Unavailable, // OffTrackLimitExceeded N2kOnOff_Off, // Override steering_mode, // SteeringMode N2kTM_RudderLimitControlled,// TurnMode N2khr_true, // HeadingReference N2kRDO_NoDirectionOrder, // CommandedRudderDirection N2kDoubleNA, // CommandedRudderAngle heading_to_steer, // HeadingToSteerCourse N2kDoubleNA, // Track N2kDoubleNA, // RudderLimit N2kDoubleNA, // OffHeadingLimit N2kDoubleNA, // RadiusOfTurnOrder N2kDoubleNA, // RateOfTurnOrder N2kDoubleNA, // OffTrackLimit vessel_heading); // VesselHeading NMEA2000.SendMsg(msg); } void PublisherTask(void* /*pv*/) { AR_LOGI(TAG, "nmea2000_publisher task started on core %d", xPortGetCoreID()); TickType_t last_wake = xTaskGetTickCount(); uint8_t slow_tick = 0; for (;;) { publish_rudder(); if (++slow_tick >= 10) { slow_tick = 0; publish_heading_track_control(); } vTaskDelayUntil(&last_wake, pdMS_TO_TICKS(100)); } } } // namespace void nmea2000_publisher_start_task() { xTaskCreatePinnedToCore(PublisherTask, "n2k_pub", AR_TASK_STACK_N2K_RX, nullptr, AR_TASK_PRIO_N2K_RX - 1, nullptr, AR_TASK_CORE_COMMS); } } // namespace arautopilot::protocols::nmea2000