refactor: move firmware workspace under firmware
This commit is contained in:
1 parent
153b0bf262
commit
f03fc119a4
222 files changed
+56
-13
No files matched your search
@@ -0,0 +1,253 @@
|
||||
use std::{fs, path::PathBuf};
|
||||
|
||||
use kaptan_board::{
|
||||
BoardConfig, BoardProfile, DataChannelRates, ImuDevice, ImuMount, MavlinkEndpoint, MavlinkPort,
|
||||
TelemetryProfile, VerticalSensingKind,
|
||||
};
|
||||
use kaptan_log::{ErrorAdditionalInformation, ErrorPath, LogFlags, LogRecord, LogText, Severity};
|
||||
use kaptan_log_mavlink::{DiagnosticPayload, encode_diagnostic};
|
||||
use kaptan_mavlink::encoder::MavlinkEncoder;
|
||||
use platform_stm32f7::{
|
||||
BoardError, F7BoardBuilder, Mavlink, Mpu6000, Pa4, VerticalSensor, validate_board_config,
|
||||
};
|
||||
|
||||
fn platform_source(relative: &str) -> String {
|
||||
let root = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
||||
fs::read_to_string(root.join(relative)).expect("platform source must exist")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn former_motor_pins_are_inhibited_before_all_other_initialization() {
|
||||
let module = platform_source("src/f722/mod.rs");
|
||||
let interlock = module
|
||||
.find("motor_inhibit::inhibit_motor_pins")
|
||||
.expect("F722 initialization must claim the former motor pins first");
|
||||
let validation = module
|
||||
.find("validate_board_config")
|
||||
.expect("F722 initialization must validate its board configuration");
|
||||
let board_initialization = module
|
||||
.find("board::initialize")
|
||||
.expect("F722 initialization must subsequently claim telemetry I/O");
|
||||
|
||||
assert!(interlock < validation);
|
||||
assert!(validation < board_initialization);
|
||||
|
||||
let interlock = platform_source("src/f722/motor_inhibit.rs");
|
||||
let reset = interlock
|
||||
.find("port.bsrr()")
|
||||
.expect("interlock must reset the former motor-pin output latches");
|
||||
let output_mode = interlock
|
||||
.find("port.moder()")
|
||||
.expect("interlock must configure the former motor pins as GPIO outputs");
|
||||
assert!(
|
||||
reset < output_mode,
|
||||
"output latches must be low before GPIO output mode is selected"
|
||||
);
|
||||
for pin in ["br0", "br1", "br4", "br5"] {
|
||||
assert!(interlock.contains(pin), "interlock must reset PB pin {pin}");
|
||||
}
|
||||
for pin in ["moder0", "moder1", "moder4", "moder5"] {
|
||||
assert!(
|
||||
interlock.contains(pin),
|
||||
"interlock must configure PB pin {pin}"
|
||||
);
|
||||
}
|
||||
for pin in ["ot0", "ot1", "ot4", "ot5"] {
|
||||
assert!(
|
||||
interlock.contains(pin),
|
||||
"interlock must set PB pin {pin} push-pull"
|
||||
);
|
||||
}
|
||||
for pin in ["ospeedr0", "ospeedr1", "ospeedr4", "ospeedr5"] {
|
||||
assert!(
|
||||
interlock.contains(pin),
|
||||
"interlock must set PB pin {pin} low speed"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn mpu_config() -> BoardConfig {
|
||||
BoardConfig {
|
||||
name: "test",
|
||||
mavlink: MavlinkEndpoint {
|
||||
system_id: 1,
|
||||
component_id: 1,
|
||||
port: MavlinkPort::Serial1,
|
||||
baud: 115_200,
|
||||
},
|
||||
profile: BoardProfile::TelemetryOnly(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,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn immutable_board_policy_must_match_the_executed_route_graph() {
|
||||
let routes = F7BoardBuilder::new()
|
||||
.imu(Mpu6000::on_spi1().with_drdy(Pa4))
|
||||
.mavlink(Mavlink::on_usart1())
|
||||
.vertical_sensor(VerticalSensor::mtf01_on_uart4())
|
||||
.build()
|
||||
.unwrap();
|
||||
assert_eq!(validate_board_config(mpu_config(), routes), Ok(()));
|
||||
|
||||
let mut wrong_imu = mpu_config();
|
||||
let BoardProfile::TelemetryOnly(profile) = &mut wrong_imu.profile else {
|
||||
unreachable!();
|
||||
};
|
||||
profile.imu = ImuDevice::Icm42688;
|
||||
assert_eq!(
|
||||
validate_board_config(wrong_imu, routes),
|
||||
Err(BoardError::ImuRouteMismatch)
|
||||
);
|
||||
|
||||
let mut wrong_mount = mpu_config();
|
||||
let BoardProfile::TelemetryOnly(profile) = &mut wrong_mount.profile else {
|
||||
unreachable!();
|
||||
};
|
||||
profile.imu_mount = ImuMount::Yaw180;
|
||||
assert_eq!(
|
||||
validate_board_config(wrong_mount, routes),
|
||||
Err(BoardError::ImuMountMismatch)
|
||||
);
|
||||
|
||||
let mut wrong_vertical = mpu_config();
|
||||
let BoardProfile::TelemetryOnly(profile) = &mut wrong_vertical.profile else {
|
||||
unreachable!();
|
||||
};
|
||||
profile.vertical_sensing = VerticalSensingKind::Range;
|
||||
profile.data_channel.optical_flow_hz = None;
|
||||
assert_eq!(
|
||||
validate_board_config(wrong_vertical, routes),
|
||||
Err(BoardError::VerticalSensorCapabilityMismatch)
|
||||
);
|
||||
|
||||
let mut wrong_port = mpu_config();
|
||||
wrong_port.mavlink.port = MavlinkPort::Serial2;
|
||||
assert_eq!(
|
||||
validate_board_config(wrong_port, routes),
|
||||
Err(BoardError::MavlinkRouteMismatch)
|
||||
);
|
||||
|
||||
let mut overcommitted_link = mpu_config();
|
||||
overcommitted_link.mavlink.baud = 9_600;
|
||||
assert_eq!(
|
||||
validate_board_config(overcommitted_link, routes),
|
||||
Err(BoardError::TelemetryLinkOvercommitted)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn telemetry_platform_has_no_physical_actuator_peripheral_path() {
|
||||
let sources = [
|
||||
platform_source("src/lib.rs"),
|
||||
platform_source("src/f722/mod.rs"),
|
||||
platform_source("src/f722/board.rs"),
|
||||
platform_source("src/f722/firmware.rs"),
|
||||
platform_source("src/f722/interrupts.rs"),
|
||||
]
|
||||
.join("\n");
|
||||
|
||||
for forbidden in [
|
||||
"mod actuator",
|
||||
"MotorOutput",
|
||||
"MotorRoute",
|
||||
"MotorOutputs",
|
||||
"TIM3",
|
||||
"TIM5",
|
||||
"Dshot",
|
||||
"Pwm",
|
||||
"device.TIM3",
|
||||
"device.TIM5",
|
||||
] {
|
||||
assert!(
|
||||
!sources.contains(forbidden),
|
||||
"telemetry platform must not expose actuator path {forbidden}"
|
||||
);
|
||||
}
|
||||
|
||||
let manifest = platform_source("Cargo.toml");
|
||||
assert!(
|
||||
!manifest.contains("kaptan-actuator"),
|
||||
"telemetry platform must not link the physical actuator crate"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn telemetry_has_no_inbound_command_authority() {
|
||||
let board = platform_source("src/f722/board.rs");
|
||||
let firmware = platform_source("src/f722/firmware.rs");
|
||||
|
||||
assert!(
|
||||
!board.contains("MavlinkReceiver"),
|
||||
"telemetry must not retain an inbound MAVLink dispatch path"
|
||||
);
|
||||
assert!(
|
||||
!board.contains("ArmRequest"),
|
||||
"telemetry must not decode arm commands"
|
||||
);
|
||||
assert!(
|
||||
!board.contains("MAV_RESULT_ACCEPTED"),
|
||||
"telemetry must not accept an arm command"
|
||||
);
|
||||
assert!(
|
||||
!firmware.contains("shared = [mavlink, motors]"),
|
||||
"the MAVLink worker must not share a motor resource"
|
||||
);
|
||||
assert!(
|
||||
!firmware.contains("MAVLINK_EVENT_RECEIVED"),
|
||||
"telemetry must not schedule a worker from an inbound command"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn f722_link_budget_uses_both_maximum_generated_diagnostic_frames() {
|
||||
let text = LogText::from_message(&"x".repeat(160));
|
||||
let log_record = LogRecord::new(Severity::Info, LogFlags::NONE, text);
|
||||
let error_record = LogRecord::error(
|
||||
LogFlags::NONE,
|
||||
ErrorPath::from_message(&"p".repeat(96)),
|
||||
ErrorAdditionalInformation::from_message(&"i".repeat(64)),
|
||||
);
|
||||
let mut encoder = MavlinkEncoder::new(1, 1);
|
||||
let DiagnosticPayload::Log(log_payload) = encode_diagnostic(&log_record) else {
|
||||
panic!("info record must use KAPTAN_LOG");
|
||||
};
|
||||
let log_frame = encoder.custom(&log_payload);
|
||||
let DiagnosticPayload::Error(error_payload) = encode_diagnostic(&error_record) else {
|
||||
panic!("error record must use KAPTAN_ERROR");
|
||||
};
|
||||
let error_frame = encoder.custom(&error_payload);
|
||||
|
||||
assert_eq!(log_frame.raw_bytes().len(), 175);
|
||||
assert_eq!(
|
||||
kaptan_mavlink::message::kaptan::KAPTAN_LOG_DATA::ENCODED_LEN,
|
||||
163
|
||||
);
|
||||
assert_eq!(error_frame.raw_bytes().len(), 175);
|
||||
assert_eq!(
|
||||
kaptan_mavlink::message::kaptan::KAPTAN_ERROR_DATA::ENCODED_LEN,
|
||||
163
|
||||
);
|
||||
assert_eq!(error_payload.path_length, 96);
|
||||
assert_eq!(error_payload.additional_information_length, 64);
|
||||
assert_eq!(error_payload.flags.bits(), LogFlags::NONE.bits());
|
||||
assert_eq!(
|
||||
62 * 48 + 10 * error_frame.raw_bytes().len() + 5 * log_frame.raw_bytes().len(),
|
||||
5_601
|
||||
);
|
||||
let board = platform_source("src/f722/board.rs");
|
||||
assert!(board.contains("KAPTAN_ERROR_FRAME_MAXIMUM_BYTES"));
|
||||
assert!(board.contains("KAPTAN_LOG_FRAME_MAXIMUM_BYTES"));
|
||||
assert!(board.contains("5,601 bytes/s"));
|
||||
}
|
||||
Reference in new issue
Block a user