boards/kakute-f7-mini-v3: add selectable motor outputs

Signed-off-by: Onur Özkan <work@onurozkan.dev>
This commit is contained in:
Onur Özkan committed 2026-07-25 21:55:07 +03:00
1 parent 4a5d0313ca
commit 117e1abe97
12 files changed
+987 -88

No files matched your search

+102
View File
@@ -0,0 +1,102 @@
//! Narrow MAVLink arm/disarm command decoding.
//!
//! This module deliberately accepts no actuator-value or motor-test command.
//! A board must pass accepted requests to its single actuator-state owner; this
//! decoder has no access to output hardware.
use mavlink::Message;
use crate::{MavlinkFrame, message};
/// A requested change to the vehicle's arming state.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ArmRequest {
/// Request the armed-idle state.
Arm,
/// Request electrically inactive outputs.
Disarm,
}
/// Decodes a targeted MAVLink `MAV_CMD_COMPONENT_ARM_DISARM` command.
///
/// Only `param1` values exactly equal to `0.0` (disarm) or `1.0` (arm) are
/// accepted. Broadcast targets are intentionally rejected: an actuator command
/// must name this vehicle and its component explicitly.
pub fn decode_arm_request(
frame: &MavlinkFrame,
system_id: u8,
component_id: u8,
) -> Option<ArmRequest> {
let message = message::MavMessage::parse(
mavlink::MavlinkVersion::V2,
frame.message_id(),
frame.payload(),
)
.ok()?;
let message::MavMessage::COMMAND_LONG(command) = message else {
return None;
};
if command.command != message::MavCmd::MAV_CMD_COMPONENT_ARM_DISARM
|| command.target_system != system_id
|| command.target_component != component_id
{
return None;
}
match command.param1 {
0.0 => Some(ArmRequest::Disarm),
1.0 => Some(ArmRequest::Arm),
_ => None,
}
}
#[cfg(test)]
mod tests {
use super::{ArmRequest, decode_arm_request};
use crate::{MavlinkFrame, message};
fn command(target_system: u8, target_component: u8, value: f32) -> MavlinkFrame {
let mut frame = MavlinkFrame::new();
frame.serialize_message_data(
message::MavHeader {
sequence: 0,
system_id: 42,
component_id: 7,
},
&message::COMMAND_LONG_DATA {
param1: value,
param2: 0.0,
param3: 0.0,
param4: 0.0,
param5: 0.0,
param6: 0.0,
param7: 0.0,
command: message::MavCmd::MAV_CMD_COMPONENT_ARM_DISARM,
target_system,
target_component,
confirmation: 0,
},
);
frame
}
#[test]
fn accepts_explicit_targeted_arm_and_disarm() {
assert_eq!(
decode_arm_request(&command(1, 1, 1.0), 1, 1),
Some(ArmRequest::Arm)
);
assert_eq!(
decode_arm_request(&command(1, 1, 0.0), 1, 1),
Some(ArmRequest::Disarm)
);
}
#[test]
fn rejects_broadcast_wrong_target_and_invalid_parameter() {
assert_eq!(decode_arm_request(&command(0, 0, 1.0), 1, 1), None);
assert_eq!(decode_arm_request(&command(2, 1, 1.0), 1, 1), None);
assert_eq!(decode_arm_request(&command(1, 1, 0.5), 1, 1), None);
}
}
+26 -5
View File
@@ -37,13 +37,17 @@ impl MavlinkEncoder {
raw
}
/// Encodes a Kaptan heartbeat.
pub fn heartbeat(&mut self, system_status: message::MavState) -> MavlinkFrame {
/// Encodes a Kaptan heartbeat with its current arming state.
pub fn heartbeat(&mut self, system_status: message::MavState, armed: bool) -> MavlinkFrame {
let heartbeat = message::HEARTBEAT_DATA {
custom_mode: 0,
mavtype: message::MavType::MAV_TYPE_QUADROTOR,
autopilot: message::MavAutopilot::MAV_AUTOPILOT_GENERIC,
base_mode: message::MavModeFlag::empty(),
base_mode: if armed {
message::MavModeFlag::MAV_MODE_FLAG_SAFETY_ARMED
} else {
message::MavModeFlag::empty()
},
system_status,
mavlink_version: 3,
};
@@ -51,6 +55,15 @@ impl MavlinkEncoder {
self.serialize(&heartbeat)
}
/// Acknowledges a supported MAVLink command.
pub fn command_ack(
&mut self,
command: message::MavCmd,
result: message::MavResult,
) -> MavlinkFrame {
self.serialize(&message::COMMAND_ACK_DATA { command, result })
}
/// Encodes a standard `RAW_IMU` frame.
pub fn raw_imu_snapshot(&mut self, snapshot: &ImuSnapshot) -> MavlinkFrame {
let raw_imu = message::RAW_IMU_DATA {
@@ -80,7 +93,7 @@ mod tests {
#[test]
fn heartbeat_has_the_configured_sender_and_sequence() {
let mut encoder = MavlinkEncoder::new(42, 7);
let frame = encoder.heartbeat(MavState::MAV_STATE_STANDBY);
let frame = encoder.heartbeat(MavState::MAV_STATE_STANDBY, false);
let bytes = frame.raw_bytes();
assert_eq!(bytes[0], 0xFD);
@@ -95,7 +108,7 @@ mod tests {
#[test]
fn heartbeat_preserves_the_requested_health_state() {
let mut encoder = MavlinkEncoder::new(1, 1);
let frame = encoder.heartbeat(MavState::MAV_STATE_CRITICAL);
let frame = encoder.heartbeat(MavState::MAV_STATE_CRITICAL, false);
assert_eq!(frame.raw_bytes()[17], MavState::MAV_STATE_CRITICAL as u8);
}
@@ -114,4 +127,12 @@ mod tests {
assert_eq!(&frame.raw_bytes()[7..10], &[27, 0, 0]);
}
#[test]
fn armed_heartbeat_sets_the_standard_armed_flag() {
let mut encoder = MavlinkEncoder::new(1, 1);
let frame = encoder.heartbeat(MavState::MAV_STATE_ACTIVE, true);
assert_ne!(frame.raw_bytes()[16] & 0x80, 0);
}
}
+2
View File
@@ -2,6 +2,8 @@
#![no_std]
/// Narrow MAVLink arm/disarm command decoding.
pub mod arm;
/// MAVLink frame encoding API for boards.
pub mod encoder;
/// Serial-byte MAVLink receive API for boards.
+2 -2
View File
@@ -117,13 +117,13 @@ mod tests {
transmitter
.send_frame(
FrameClass::BestEffort,
encoder.heartbeat(MavState::MAV_STATE_STANDBY),
encoder.heartbeat(MavState::MAV_STATE_STANDBY, false),
)
.unwrap();
transmitter
.send_frame(
FrameClass::BestEffort,
encoder.heartbeat(MavState::MAV_STATE_STANDBY),
encoder.heartbeat(MavState::MAV_STATE_STANDBY, false),
)
.unwrap();
assert!(transmitter.transmit.is_active());