refactor(firmware): centralize configuration and diagnostics
This commit is contained in:
1 parent
7048ed89d0
commit
387fdaad23
140 files changed
+6891
-4541
No files matched your search
@@ -9,7 +9,6 @@ version.workspace = true
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
kaptan-board = { path = "../kaptan-board" }
|
||||
kaptan-log = { path = "../kaptan-log" }
|
||||
kaptan-dma = { path = "../kaptan-dma" }
|
||||
kaptan-mavlink = { path = "../kaptan-mavlink" }
|
||||
|
||||
@@ -7,16 +7,14 @@
|
||||
|
||||
#![no_std]
|
||||
|
||||
use kaptan_board::{BoardConfig, DataChannelRates};
|
||||
use kaptan_dma::TxCompletion;
|
||||
use kaptan_log::KaptanError;
|
||||
use kaptan_mavlink::message::MessageData;
|
||||
use kaptan_mavlink::{
|
||||
MavlinkFrame,
|
||||
encoder::{DistanceSensorConfig, MavlinkEncoder},
|
||||
message::{MavCmd, MavResult, MavState},
|
||||
transmitter::FrameClass,
|
||||
transmitter::TransmitFaultKind,
|
||||
message::MavState,
|
||||
transmitter::{EnqueueResult, FrameClass},
|
||||
};
|
||||
use kaptan_sensor::{optical_flow::OpticalFlowSnapshot, range::RangeSnapshot};
|
||||
|
||||
@@ -24,84 +22,36 @@ mod mailbox;
|
||||
|
||||
pub use mailbox::{LatestImuMailbox, LatestOpticalFlowMailbox, LatestRangeMailbox};
|
||||
|
||||
/// Validated publication schedule consumed by the outbound data channel.
|
||||
///
|
||||
/// This is a derived runtime input, not a persisted configuration root. Board
|
||||
/// packages own the factory values and the platform constructs this view only
|
||||
/// after validating the active user configuration against immutable hardware.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub struct DataChannelRates {
|
||||
/// Frequency of the platform service tick.
|
||||
pub service_hz: usize,
|
||||
/// Raw-IMU publication rate.
|
||||
pub raw_imu_hz: usize,
|
||||
/// Range publication rate; `None` disables the stream.
|
||||
pub range_hz: Option<usize>,
|
||||
/// Optical-flow publication rate; `None` disables the stream.
|
||||
pub optical_flow_hz: Option<usize>,
|
||||
/// Heartbeat publication rate.
|
||||
pub heartbeat_hz: usize,
|
||||
}
|
||||
|
||||
/// Platform transport used by a data channel.
|
||||
///
|
||||
/// The channel decides exactly which encoded frame is admitted and with which
|
||||
/// delivery class. The platform retains only DMA endpoint ownership.
|
||||
pub trait FrameSink {
|
||||
/// Admits one fully encoded frame to the platform's bounded transport.
|
||||
fn send(&mut self, class: FrameClass, frame: MavlinkFrame) -> FrameAdmission;
|
||||
fn send(&mut self, class: FrameClass, frame: MavlinkFrame) -> EnqueueResult;
|
||||
|
||||
/// Releases or faults the active DMA frame after a terminal interrupt.
|
||||
fn on_dma_complete(&mut self, completion: TxCompletion) -> DmaCompletion;
|
||||
|
||||
/// Returns unconfigurable sole-link loss counters.
|
||||
fn health(&self) -> TxHealth;
|
||||
|
||||
/// Removes one exact terminal transport-fault category for the central
|
||||
/// observability owner. It is never encoded from an ISR or transport
|
||||
/// driver directly.
|
||||
fn take_transport_fault(&mut self) -> Option<TransmitFaultKind>;
|
||||
}
|
||||
|
||||
/// Outcome returned to the one platform telemetry owner for each attempted
|
||||
/// frame admission. It never triggers logging recursively.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub enum FrameAdmission {
|
||||
/// The queue retained this encoded frame, possibly after replacing a
|
||||
/// pending lower-priority frame.
|
||||
Retained(kaptan_mavlink::transmitter::EnqueueResult),
|
||||
}
|
||||
|
||||
impl FrameAdmission {
|
||||
/// Whether the frame is represented by the lower transport and the log
|
||||
/// hub may acknowledge its source record.
|
||||
pub const fn is_retained(self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
Self::Retained(
|
||||
kaptan_mavlink::transmitter::EnqueueResult::Enqueued
|
||||
| kaptan_mavlink::transmitter::EnqueueResult::ReplacedBestEffort
|
||||
| kaptan_mavlink::transmitter::EnqueueResult::ReplacedCritical
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/// Whether no pending queue slot accepted the source frame.
|
||||
pub const fn is_queue_full(self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
Self::Retained(
|
||||
kaptan_mavlink::transmitter::EnqueueResult::VitalQueueFull
|
||||
| kaptan_mavlink::transmitter::EnqueueResult::CriticalQueueFull
|
||||
| kaptan_mavlink::transmitter::EnqueueResult::BestEffortDropped
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Terminal outcome from the sole DMA owner.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub enum DmaCompletion {
|
||||
/// No new transport fault was observed.
|
||||
Complete,
|
||||
/// A DMA terminal or restart fault was counted by the platform.
|
||||
Fault,
|
||||
}
|
||||
|
||||
/// Unconfigurable loss counters from sole-link admission/DMA ownership.
|
||||
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
|
||||
pub struct TxHealth {
|
||||
/// Dropped or displaced pending frames, including vital admission loss.
|
||||
pub admission_loss_count: u32,
|
||||
/// DMA preparation/start/terminal transfer faults.
|
||||
pub dma_fault_count: u32,
|
||||
/// DMA-visible memory preparation faults.
|
||||
pub memory_fault_count: u32,
|
||||
/// DMA-engine transfer-start faults.
|
||||
pub engine_fault_count: u32,
|
||||
/// Active-DMA terminal faults or halts.
|
||||
pub terminal_fault_count: u32,
|
||||
/// Applies one terminal DMA interrupt and starts the next retained frame
|
||||
/// when possible.
|
||||
fn on_dma_complete(&mut self, completion: TxCompletion);
|
||||
}
|
||||
|
||||
/// Mailboxes read by one data-channel worker.
|
||||
@@ -129,8 +79,6 @@ pub struct VerticalDataConfig {
|
||||
/// Data-channel construction failure.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq, KaptanError)]
|
||||
pub enum DataChannelError {
|
||||
/// Rejected profiles must not construct a physical telemetry channel.
|
||||
NotTelemetryProfile,
|
||||
/// A configured channel must be due at least once per second.
|
||||
ZeroRate,
|
||||
/// A configured channel exceeds the service frequency.
|
||||
@@ -159,20 +107,19 @@ impl<'a, Sink, const SERVICE_HZ: usize> DataChannel<'a, Sink, SERVICE_HZ>
|
||||
where
|
||||
Sink: FrameSink,
|
||||
{
|
||||
/// Creates an outbound data flow from validated portable board metadata.
|
||||
/// Creates an outbound data flow from already-validated runtime inputs.
|
||||
pub fn new(
|
||||
sink: Sink,
|
||||
config: BoardConfig,
|
||||
system_id: u8,
|
||||
component_id: u8,
|
||||
rates: DataChannelRates,
|
||||
vertical: VerticalDataConfig,
|
||||
sources: DataSources<'a>,
|
||||
) -> Result<Self, DataChannelError> {
|
||||
let telemetry = config
|
||||
.telemetry()
|
||||
.ok_or(DataChannelError::NotTelemetryProfile)?;
|
||||
let schedule = Schedule::from_rates(telemetry.data_channel)?;
|
||||
let schedule = Schedule::from_rates(rates)?;
|
||||
Ok(Self {
|
||||
sink,
|
||||
encoder: MavlinkEncoder::new(config.mavlink.system_id, config.mavlink.component_id),
|
||||
encoder: MavlinkEncoder::new(system_id, component_id),
|
||||
schedule,
|
||||
sources,
|
||||
vertical,
|
||||
@@ -234,41 +181,16 @@ where
|
||||
}
|
||||
|
||||
/// Completes the active DMA lifecycle for this channel's transport.
|
||||
pub fn on_dma_complete(&mut self, completion: TxCompletion) -> DmaCompletion {
|
||||
pub fn on_dma_complete(&mut self, completion: TxCompletion) {
|
||||
self.sink.on_dma_complete(completion)
|
||||
}
|
||||
|
||||
/// Emits a critical acknowledgement for a received MAVLink command.
|
||||
pub fn acknowledge_command(&mut self, command: MavCmd, result: MavResult) {
|
||||
let _ = self.sink.send(
|
||||
FrameClass::Critical,
|
||||
self.encoder.command_ack(command, result),
|
||||
);
|
||||
}
|
||||
|
||||
/// Encodes an adapter-owned generated MAVLink data record through the
|
||||
/// channel's sole encoder and bounded transport sink.
|
||||
pub fn send_custom<M: MessageData>(
|
||||
&mut self,
|
||||
class: FrameClass,
|
||||
message: &M,
|
||||
) -> FrameAdmission {
|
||||
pub fn send_custom<M: MessageData>(&mut self, class: FrameClass, message: &M) -> EnqueueResult {
|
||||
self.sink.send(class, self.encoder.custom(message))
|
||||
}
|
||||
|
||||
/// Returns local sole-link accounting for platform diagnostics. It is not
|
||||
/// serialized through Kaptan's bounded diagnostic messages.
|
||||
pub fn transport_health(&self) -> TxHealth {
|
||||
self.sink.health()
|
||||
}
|
||||
|
||||
/// Removes one exact sole-link transport fault category after the platform
|
||||
/// has counted it. The platform maps it to a typed log record on its
|
||||
/// bounded telemetry worker.
|
||||
pub fn take_transport_fault(&mut self) -> Option<TransmitFaultKind> {
|
||||
self.sink.take_transport_fault()
|
||||
}
|
||||
|
||||
fn refresh_vertical_data(&mut self) {
|
||||
if let Some(range) = self.sources.range.take_newer(&mut self.observed_range) {
|
||||
self.last_range = Some(range);
|
||||
@@ -371,17 +293,15 @@ impl<const SERVICE_HZ: usize> Schedule<SERVICE_HZ> {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{
|
||||
DataChannel, DataSources, DmaCompletion, FrameAdmission, FrameSink, LatestImuMailbox,
|
||||
LatestOpticalFlowMailbox, LatestRangeMailbox, TransmitFaultKind, TxHealth,
|
||||
VerticalDataConfig,
|
||||
};
|
||||
use kaptan_board::{
|
||||
BoardConfig, DataChannelRates, ImuDevice, ImuMount, MavlinkEndpoint, MavlinkPort,
|
||||
VerticalSensingKind,
|
||||
DataChannel, DataChannelRates, DataSources, FrameSink, LatestImuMailbox,
|
||||
LatestOpticalFlowMailbox, LatestRangeMailbox, VerticalDataConfig,
|
||||
};
|
||||
use kaptan_dma::TxCompletion;
|
||||
use kaptan_log::Severity;
|
||||
use kaptan_mavlink::{MavlinkFrame, message::MavState, transmitter::FrameClass};
|
||||
use kaptan_mavlink::{
|
||||
MavlinkFrame,
|
||||
message::MavState,
|
||||
transmitter::{EnqueueResult, FrameClass},
|
||||
};
|
||||
use kaptan_sensor::{
|
||||
imu::ImuSnapshot, optical_flow::OpticalFlowSnapshot, range::RangeSnapshot,
|
||||
};
|
||||
@@ -394,50 +314,26 @@ mod tests {
|
||||
}
|
||||
|
||||
impl FrameSink for Sink {
|
||||
fn send(&mut self, class: FrameClass, _: MavlinkFrame) -> FrameAdmission {
|
||||
fn send(&mut self, class: FrameClass, _: MavlinkFrame) -> EnqueueResult {
|
||||
match class {
|
||||
FrameClass::Vital => self.critical += 1,
|
||||
FrameClass::BestEffort => self.best_effort += 1,
|
||||
FrameClass::Critical => self.critical += 1,
|
||||
}
|
||||
FrameAdmission::Retained(kaptan_mavlink::transmitter::EnqueueResult::Enqueued)
|
||||
EnqueueResult::Enqueued
|
||||
}
|
||||
|
||||
fn on_dma_complete(&mut self, _: TxCompletion) -> DmaCompletion {
|
||||
fn on_dma_complete(&mut self, _: TxCompletion) {
|
||||
self.completions += 1;
|
||||
DmaCompletion::Complete
|
||||
}
|
||||
|
||||
fn health(&self) -> TxHealth {
|
||||
TxHealth::default()
|
||||
}
|
||||
|
||||
fn take_transport_fault(&mut self) -> Option<TransmitFaultKind> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
const CONFIG: BoardConfig = BoardConfig {
|
||||
name: "test",
|
||||
mavlink: MavlinkEndpoint {
|
||||
system_id: 1,
|
||||
component_id: 1,
|
||||
port: MavlinkPort::Serial1,
|
||||
baud: 115_200,
|
||||
},
|
||||
profile: kaptan_board::BoardProfile::TelemetryOnly(kaptan_board::TelemetryProfile {
|
||||
imu: ImuDevice::Mpu6000,
|
||||
imu_mount: ImuMount::BodyAligned,
|
||||
vertical_sensing: VerticalSensingKind::RangeAndOpticalFlow,
|
||||
data_channel: DataChannelRates {
|
||||
service_hz: 100,
|
||||
raw_imu_hz: 20,
|
||||
range_hz: Some(20),
|
||||
optical_flow_hz: Some(20),
|
||||
heartbeat_hz: 2,
|
||||
},
|
||||
maximum_log_level: Severity::Info,
|
||||
}),
|
||||
const RATES: DataChannelRates = DataChannelRates {
|
||||
service_hz: 100,
|
||||
raw_imu_hz: 20,
|
||||
range_hz: Some(20),
|
||||
optical_flow_hz: Some(20),
|
||||
heartbeat_hz: 2,
|
||||
};
|
||||
|
||||
#[test]
|
||||
@@ -451,7 +347,9 @@ mod tests {
|
||||
critical: 0,
|
||||
completions: 0,
|
||||
},
|
||||
CONFIG,
|
||||
1,
|
||||
1,
|
||||
RATES,
|
||||
VerticalDataConfig {
|
||||
distance: Some(kaptan_mavlink::encoder::DistanceSensorConfig {
|
||||
sensor_id: 0,
|
||||
@@ -488,7 +386,6 @@ mod tests {
|
||||
channel.on_service_tick(MavState::MAV_STATE_STANDBY, false);
|
||||
}
|
||||
channel.on_dma_complete(TxCompletion::Complete);
|
||||
|
||||
let sink = channel.sink;
|
||||
assert_eq!(sink.best_effort, 60);
|
||||
assert_eq!(sink.critical, 2);
|
||||
|
||||
Reference in new issue
Block a user