use super::build::{
broadcast_to_peers, build_attitude_status_data, build_gimbal_manager_status_data,
};
use crate::daemon::config::MavlinkConfig;
use crate::daemon::gimbal_handle::GimbalHandle;
use crate::daemon::mavlink_manager::Peers;
use crate::daemon::state::StateManager;
use mavlink::ardupilotmega::{
GimbalDeviceErrorFlags, MavAutopilot, MavMessage, MavModeFlag, MavState, MavType,
HEARTBEAT_DATA,
};
use std::sync::atomic::AtomicU8;
use std::sync::Arc;
use std::time::{Duration, Instant};
use tokio::net::UdpSocket;
use tokio::sync::Notify;
use tokio::time;
use tracing::{info, warn};
pub(in crate::daemon::mavlink_manager) async fn heartbeat_loop(
socket: Arc<UdpSocket>,
peers: Peers,
seq: Arc<AtomicU8>,
config: MavlinkConfig,
) {
let mut interval = time::interval(Duration::from_secs(1));
loop {
interval.tick().await;
let hb = HEARTBEAT_DATA {
custom_mode: 0,
mavtype: MavType::MAV_TYPE_GIMBAL,
autopilot: MavAutopilot::MAV_AUTOPILOT_INVALID,
base_mode: MavModeFlag::empty(),
system_status: MavState::MAV_STATE_ACTIVE,
mavlink_version: 3,
};
broadcast_to_peers(
&socket,
&peers,
&config,
&seq,
MavMessage::HEARTBEAT(hb),
"HEARTBEAT",
)
.await;
}
}
pub(in crate::daemon::mavlink_manager) async fn publish_status_loop(
socket: Arc<UdpSocket>,
peers: Peers,
seq: Arc<AtomicU8>,
state_manager: StateManager,
config: MavlinkConfig,
start_time: Instant,
) {
let mut interval = time::interval(Duration::from_millis(200));
loop {
interval.tick().await;
let msg = MavMessage::GIMBAL_MANAGER_STATUS(build_gimbal_manager_status_data(
&state_manager,
&config,
start_time,
));
broadcast_to_peers(&socket, &peers, &config, &seq, msg, "GIMBAL_MANAGER_STATUS").await;
}
}
#[allow(clippy::too_many_arguments)]
pub(in crate::daemon::mavlink_manager) async fn attitude_loop(
socket: Arc<UdpSocket>,
peers: Peers,
seq: Arc<AtomicU8>,
state_manager: StateManager,
config: MavlinkConfig,
gimbal: GimbalHandle,
start_time: Instant,
notify_lost: Arc<Notify>,
) {
const FAILURE_THRESHOLD: u32 = 8;
const COMMS_ERROR_LINGER: Duration = Duration::from_secs(1);
let mut interval = time::interval(Duration::from_millis(250));
interval.set_missed_tick_behavior(time::MissedTickBehavior::Skip);
interval.tick().await;
let mut consecutive_failures: u32 = 0;
let mut last_failure: Option<Instant> = None;
loop {
interval.tick().await;
let attitude = gimbal.get_attitude().await;
let attitude = match attitude {
Ok(a) => {
if consecutive_failures > 0 {
info!(
"Attitude readback recovered after {} consecutive failures",
consecutive_failures
);
}
consecutive_failures = 0;
a
}
Err(e) => {
consecutive_failures = consecutive_failures.wrapping_add(1);
last_failure = Some(Instant::now());
if consecutive_failures == 1 || consecutive_failures % 20 == 0 {
warn!(
"Attitude readback failed (#{}): {}",
consecutive_failures, e
);
}
if consecutive_failures == FAILURE_THRESHOLD {
warn!(
"Gimbal silent for {} consecutive polls; signaling reconnect",
FAILURE_THRESHOLD
);
notify_lost.notify_one();
}
continue;
}
};
state_manager.update_measured_angles(attitude.yaw, attitude.pitch, attitude.roll);
let failure_flags = match last_failure {
Some(t) if t.elapsed() < COMMS_ERROR_LINGER => {
GimbalDeviceErrorFlags::GIMBAL_DEVICE_ERROR_FLAGS_COMMS_ERROR
}
_ => GimbalDeviceErrorFlags::empty(),
};
let msg = MavMessage::GIMBAL_DEVICE_ATTITUDE_STATUS(build_attitude_status_data(
&attitude,
start_time,
failure_flags,
));
broadcast_to_peers(
&socket,
&peers,
&config,
&seq,
msg,
"GIMBAL_DEVICE_ATTITUDE_STATUS",
)
.await;
}
}