Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 13 additions & 7 deletions Drivers/Holybro.inf
Original file line number Diff line number Diff line change
Expand Up @@ -87,14 +87,18 @@ ServiceBinary=%12%\%DRIVERFILENAME%.sys
[DeviceList]
%DESCRIPTION47%=DriverInstall, USB\VID_3162&PID_0047
%DESCRIPTION49%=DriverInstall, USB\VID_3162&PID_0049
%DESCRIPTION4B%=DriverInstall, USB\VID_3162&PID_004B&MI_00
%DESCRIPTION4BSL%=DriverInstall, USB\VID_3162&PID_004B&MI_02
%DESCRIPTION4B%=DriverInstall, USB\VID_3162&PID_004B&MI_00
%DESCRIPTION4BSL%=DriverInstall, USB\VID_3162&PID_004B&MI_02
%DESCRIPTION53%=DriverInstall, USB\VID_3162&PID_0053&MI_00
%DESCRIPTION53SL%=DriverInstall, USB\VID_3162&PID_0053&MI_02

[DeviceList.NTamd64]
%DESCRIPTION47%=DriverInstall, USB\VID_3162&PID_0047
%DESCRIPTION49%=DriverInstall, USB\VID_3162&PID_0049
%DESCRIPTION4B%=DriverInstall, USB\VID_3162&PID_004B&MI_00
%DESCRIPTION4BSL%=DriverInstall, USB\VID_3162&PID_004B&MI_02
%DESCRIPTION4B%=DriverInstall, USB\VID_3162&PID_004B&MI_00
%DESCRIPTION4BSL%=DriverInstall, USB\VID_3162&PID_004B&MI_02
%DESCRIPTION53%=DriverInstall, USB\VID_3162&PID_0053&MI_00
%DESCRIPTION53SL%=DriverInstall, USB\VID_3162&PID_0053&MI_02

;------------------------------------------------------------------------------
; String Definitions
Expand All @@ -108,6 +112,8 @@ MFGNAME="Holybro"
INSTDISK="Holybro Installer"
DESCRIPTION47="Pixhawk4"
DESCRIPTION49="Pixhawk4-mini"
DESCRIPTION4B="Durandal"
DESCRIPTION4BSL="Durandal SLCAN"
SERVICE="USB RS-232 Emulation Driver"
DESCRIPTION4B="Durandal"
DESCRIPTION4BSL="Durandal SLCAN"
SERVICE="USB RS-232 Emulation Driver"
DESCRIPTION53="Pixhawk6C-MAVLink"
DESCRIPTION53SL="Pixhawk6C-SLCAN"
35 changes: 28 additions & 7 deletions ExtLibs/ArduPilot/Mavlink/MAVLinkInterface.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1699,8 +1699,9 @@ public async Task<bool> setParamAsync(byte sysid, byte compid, string paramname,

Array.Resize(ref temp, 16);
req.param_id = temp.ToByteArray();
if ((MAVlist[sysid, compid].cs.capabilities & (uint) MAV_PROTOCOL_CAPABILITY.PARAM_FLOAT) > 0 ||
MAVlist[sysid, compid].apname == MAV_AUTOPILOT.ARDUPILOTMEGA)
if (!UsesBytewiseParameterEncoding(
MAVlist[sysid, compid].cs.capabilities,
MAVlist[sysid, compid].apname))
{
req.param_value = new MAVLinkParam(paramname, value, (MAV_PARAM_TYPE.REAL32)).float_value;
}
Expand Down Expand Up @@ -2090,8 +2091,9 @@ public async Task<MAVLinkParamList> getParamListAsync(byte sysid, byte compid)
//Console.WriteLine(DateTime.Now.Millisecond + " gp2a ");

// item uses float based param system
if ((MAVlist[sysid, compid].cs.capabilities & (uint) MAV_PROTOCOL_CAPABILITY.PARAM_FLOAT) > 0 ||
MAVlist[sysid, compid].apname == MAV_AUTOPILOT.ARDUPILOTMEGA)
if (!UsesBytewiseParameterEncoding(
MAVlist[sysid, compid].cs.capabilities,
MAVlist[sysid, compid].apname))
{
var offset = Marshal.OffsetOf(typeof(mavlink_param_value_t), "param_value");
newparamlist[paramID] = new MAVLinkParam(paramID, BitConverter.GetBytes(par.param_value),
Expand Down Expand Up @@ -2319,9 +2321,28 @@ public float GetParam(string name = "", short index = -1, bool requireresponce =
public float GetParam(byte sysid, byte compid, string name = "", short index = -1, bool requireresponce = true)
{
return GetParamAsync(sysid, compid, name, index, requireresponce).AwaitSync();
}

/// <summary>
}

internal static bool UsesBytewiseParameterEncoding(
uint capabilities, MAV_AUTOPILOT autopilot)
{
// The current flags are authoritative. In particular, a peripheral may identify its
// firmware as ArduPilot while still using the byte-wise protocol required to preserve
// UINT32/INT32 bits in the float-shaped PARAM_VALUE and PARAM_SET fields.
if ((capabilities & (uint) MAV_PROTOCOL_CAPABILITY.PARAM_ENCODE_BYTEWISE) != 0)
return true;
if ((capabilities & (uint) MAV_PROTOCOL_CAPABILITY.PARAM_ENCODE_C_CAST) != 0)
return false;
#pragma warning disable CS0612 // Legacy devices advertised PARAM_FLOAT before the encoding flags.
if ((capabilities & (uint) MAV_PROTOCOL_CAPABILITY.PARAM_FLOAT) != 0)
return false;
#pragma warning restore CS0612

// ArduPilot historically used C-cast encoding without advertising a capability.
return autopilot != MAV_AUTOPILOT.ARDUPILOTMEGA;
}

/// <summary>
/// Get param by either index or name
/// </summary>
/// <param name="index"></param>
Expand Down
5 changes: 4 additions & 1 deletion GCSViews/FlightPlannerView.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@

<Grid x:Name="WaypointPanel" Grid.Row="2" Grid.Column="0" RowDefinitions="Auto,*">
<WrapPanel Grid.Row="0" Margin="6,4" VerticalAlignment="Center">
<ctl:FormField Label="{Binding WpRadiusLabel}" LabelWidth="92" Margin="0,2,12,2">
<ctl:FormField Label="{Binding WpRadiusLabel}" LabelWidth="92" Margin="0,2,12,2"
IsVisible="{Binding ShowWpRadius}">
<NumericUpDown Classes="sm" Value="{Binding WpRadius}" />
</ctl:FormField>
<ctl:FormField Label="{Binding LoiterRadiusLabel}" LabelWidth="104" Margin="0,2,12,2"
Expand Down Expand Up @@ -83,6 +84,8 @@
<ComboBox
ItemsSource="{x:Static vm:WpRow.CommandList}"
SelectedItem="{Binding CommandName, Mode=TwoWay}"
KeyDown="OnMissionCommandKeyDown"
PointerWheelChanged="OnMissionCommandPointerWheelChanged"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Stretch"
Width="180"
Expand Down
18 changes: 18 additions & 0 deletions GCSViews/FlightPlannerView.axaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,24 @@ public FlightPlannerView() {

internal void SwitchDocking() => ApplyDockingLayout(!_actionDockBottom, persist: true);

internal static bool BlocksClosedMissionCommandKey(
bool isDropDownOpen, Key key, KeyModifiers modifiers) =>
!isDropDownOpen && modifiers == KeyModifiers.None
&& key is Key.Up or Key.Down or Key.PageUp or Key.PageDown or Key.Home or Key.End;

private void OnMissionCommandKeyDown(object? sender, KeyEventArgs e) {
if (sender is ComboBox combo
&& BlocksClosedMissionCommandKey(combo.IsDropDownOpen, e.Key, e.KeyModifiers)) {
e.Handled = true;
}
}

private void OnMissionCommandPointerWheelChanged(object? sender, PointerWheelEventArgs e) {
if (sender is ComboBox { IsDropDownOpen: false }) {
e.Handled = true;
}
}

internal void ApplyDockingLayout(bool actionBottom, bool persist) {
_actionDockBottom = actionBottom;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,29 @@ public void Parameter_export_is_sorted_and_uses_mission_planner_format() {
File.Delete(path);
}
}

[Fact]
public void Parameter_history_keeps_every_change_and_computes_final_values() {
string path = Path.Combine(Path.GetTempPath(), $"mp_param_history_{Guid.NewGuid():N}.log");
try {
File.WriteAllLines(path, [
"FMT, 128, 89, FMT, BBnNZ, Type,Length,Name,Format,Columns",
"FMT, 130, 40, PARM, QNff, TimeUS,Name,Value,Default",
"PARM, 1000000, ATC_STR_RAT_FF, 0.5, 0.2",
"PARM, 2000000, ARMING_CHECK, 1, 1",
"PARM, 3000000, ATC_STR_RAT_FF, 0.8, 0.2",
]);

DataFlashParameterHistory history = DataFlashLog.ReadParameterHistory(path);

Assert.Equal(3, history.Changes.Count);
Assert.Equal(new[] { 1d, 2d, 3d },
history.Changes.Select(change => change.TimeSeconds));
Assert.Equal("0.8", history.FinalValues.Single(
parameter => parameter.Name == "ATC_STR_RAT_FF").Value);
Assert.Equal(2, history.FinalValues.Count);
} finally {
File.Delete(path);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -57,4 +57,23 @@ public void DroneCan_value_conversion_preserves_string_type_and_rejects_bad_numb
Assert.False(ConfigDroneCanViewModel.TryConvertParameterValue(
numericParameter, "not-a-number", out _));
}

[Theory]
[InlineData("-1", false)]
[InlineData("0", true)]
[InlineData("5.5", true)]
[InlineData("10", true)]
[InlineData("11", false)]
public void DroneCan_numeric_values_respect_reported_node_limits(
string text, bool expected) {
var parameter = new DroneCanParam {
Name = "RATE",
IsString = false,
Min = "0",
Max = "10",
};

Assert.Equal(expected,
ConfigDroneCanViewModel.TryConvertParameterValue(parameter, text, out _));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,30 @@ public void Sbs_decoder_merges_callsign_and_velocity_before_emitting_position()
Assert.Equal(0x7700, plane.Squawk);
}

[Fact]
public void Sbs_decoder_keeps_recent_nonempty_squawk_and_expires_it() {
DateTime now = new(2026, 8, 24, 12, 0, 0, DateTimeKind.Utc);
var decoder = new ExternalAdsbDecoder(() => now);

Assert.False(decoder.TryDecodeLine(Sbs("6", "ABC123", fields => {
fields[17] = "7700";
}), out _));
Assert.True(decoder.TryDecodeLine(Sbs("3", "ABC123", fields => {
fields[11] = "10000";
fields[14] = "34.5";
fields[15] = "33.25";
}), out var recent));
Assert.Equal(0x7700, recent.Squawk);

now = now.AddSeconds(31);
Assert.True(decoder.TryDecodeLine(Sbs("3", "ABC123", fields => {
fields[11] = "10000";
fields[14] = "34.5";
fields[15] = "33.25";
}), out var expired));
Assert.Equal(0, expired.Squawk);
}

[Fact]
public void Mode_s_decoder_accepts_valid_cpr_pair_in_avr_and_beast_framing() {
const string even = "*8D75804B580FF2CF7E9BA6F701D0;";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@ public void Destructive_action_prompts_explain_the_consequence() {
FlightDataViewModel.ActionConfirmationText("Terminate_Flight"));
Assert.Contains("permanently erased",
FlightDataViewModel.ActionConfirmationText("Format_SD_Card"));
Assert.Contains("Disable automatic parachute release",
Assert.Contains("cannot be undone",
FlightDataViewModel.ActionConfirmationText("Do_Parachute"));
Assert.Equal(MAVLink.PARACHUTE_ACTION.PARACHUTE_DISABLE,
Assert.Equal(MAVLink.PARACHUTE_ACTION.PARACHUTE_RELEASE,
FlightDataViewModel.ParachuteCommandAction);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
namespace MissionPlanner.Tests;

public sealed class MavlinkParameterEncodingTests {
[Fact]
public void Explicit_bytewise_capability_overrides_firmware_identity() {
uint bytewise = (uint)MAVLink.MAV_PROTOCOL_CAPABILITY.PARAM_ENCODE_BYTEWISE;

Assert.True(MAVLinkInterface.UsesBytewiseParameterEncoding(
bytewise, MAVLink.MAV_AUTOPILOT.ARDUPILOTMEGA));
Assert.False(MAVLinkInterface.UsesBytewiseParameterEncoding(
(uint)MAVLink.MAV_PROTOCOL_CAPABILITY.PARAM_ENCODE_C_CAST,
MAVLink.MAV_AUTOPILOT.INVALID));
}

[Fact]
public void Bytewise_uint32_round_trip_preserves_all_bits() {
const uint expected = 60180513;
var encoded = new MAVLink.MAVLinkParam(
"DEVICE_CODE", expected, MAVLink.MAV_PARAM_TYPE.UINT32);
var decoded = new MAVLink.MAVLinkParam(
"DEVICE_CODE", BitConverter.GetBytes(encoded.float_value),
MAVLink.MAV_PARAM_TYPE.UINT32, MAVLink.MAV_PARAM_TYPE.UINT32);

Assert.Equal(expected, decoded.Value);
Assert.NotEqual(expected, (double)(float)expected);
}
}
57 changes: 57 additions & 0 deletions MissionPlannerTests/Avalonia/MissionPlanner.Tests/NvModemTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,63 @@ public void Supports_every_current_gtu_identity_mode_without_id_ranges() {
item.Label.Contains("NV5 88:222", StringComparison.Ordinal));
}

[Theory]
[InlineData((byte)0, (byte)4)]
[InlineData((byte)1, (byte)1)]
public void Nv5_unlocked_receiver_displays_current_channel_signal_only(
byte modulation, byte radioChip) {
var transport = new FakeTransport();
using var viewModel = new NvModemViewModel(transport, () => DateTime.UtcNow,
startTimer: false);
var source = new NvModemLink(new MAVLinkInterface(), "shared UDP");

viewModel.HandlePacket(source, Packet(NvModemMessageIds.Nv5LinkStatus,
new Nv5LinkStatusMessage {
SampleMs = 1000,
Channel = 1,
RadioChip = radioChip,
Role = 0,
Modulation = modulation,
Flags = 0xfb,
PacketRssiDbmX10 = -395,
PacketSnrDbX10 = 112,
ChannelRssiDbmX10 = -970,
}, 5, 68));

Assert.Contains("L no", viewModel.RadioStatuses[0].Link, StringComparison.Ordinal);
Assert.Contains("R -97.0", viewModel.RadioStatuses[0].Link, StringComparison.Ordinal);
Assert.Contains("S —", viewModel.RadioStatuses[0].Link, StringComparison.Ordinal);
Assert.DoesNotContain("-39.5", viewModel.RadioStatuses[0].Link, StringComparison.Ordinal);
}

[Fact]
public void Nv5_locked_receiver_prefers_packet_signal_and_allows_channel_fallback() {
var transport = new FakeTransport();
using var viewModel = new NvModemViewModel(transport, () => DateTime.UtcNow,
startTimer: false);
var source = new NvModemLink(new MAVLinkInterface(), "shared UDP");
var status = new Nv5LinkStatusMessage {
SampleMs = 1000,
Channel = 1,
RadioChip = 0,
Role = 0,
Flags = 1 << 2,
PacketRssiDbmX10 = -395,
PacketSnrDbX10 = 112,
ChannelRssiDbmX10 = -970,
};

viewModel.HandlePacket(source,
Packet(NvModemMessageIds.Nv5LinkStatus, status, 5, 68));
Assert.Contains("R -39.5", viewModel.RadioStatuses[0].Link, StringComparison.Ordinal);
Assert.Contains("S 11.2", viewModel.RadioStatuses[0].Link, StringComparison.Ordinal);

status.PacketRssiDbmX10 = short.MinValue;
viewModel.HandlePacket(source,
Packet(NvModemMessageIds.Nv5LinkStatus, status, 5, 68));
Assert.Contains("R -97.0", viewModel.RadioStatuses[0].Link, StringComparison.Ordinal);
}

[Fact]
public void Invalid_passport_and_unscoped_nv4_parameter_do_not_create_devices() {
var transport = new FakeTransport();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,25 @@ public void Make_package_targets_select_their_platform_rid() {
StringComparison.Ordinal);
}

[Fact]
public void Windows_driver_catalog_recognizes_both_Pixhawk6C_interfaces() {
string inf = File.ReadAllText(Path.Combine(FindRepoRoot(), "Drivers", "Holybro.inf"));

Assert.Equal(2, Count(inf, @"USB\VID_3162&PID_0053&MI_00"));
Assert.Equal(2, Count(inf, @"USB\VID_3162&PID_0053&MI_02"));
Assert.Contains("DESCRIPTION53=\"Pixhawk6C-MAVLink\"", inf, StringComparison.Ordinal);
Assert.Contains("DESCRIPTION53SL=\"Pixhawk6C-SLCAN\"", inf, StringComparison.Ordinal);
}

private static int Count(string text, string value) {
int count = 0;
for (int index = 0; (index = text.IndexOf(value, index, StringComparison.Ordinal)) >= 0;
index += value.Length) {
count++;
}
return count;
}

private static void AssertPayload(
XElement feature, XNamespace wix, string id, string expectedName) {
XElement file = Assert.Single(feature.Descendants(wix + "File"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -602,8 +602,12 @@ public void Loiter_turns_radius_respects_copter_panorama_semantics(
}

[Fact]
public void Global_loiter_radius_is_not_exposed_for_copter() {
public void Global_waypoint_and_loiter_radii_are_not_exposed_for_copter_or_rover() {
Assert.False(FlightPlannerViewModel.SupportsGlobalWaypointRadius(Firmwares.ArduCopter2));
Assert.False(FlightPlannerViewModel.SupportsGlobalWaypointRadius(Firmwares.ArduRover));
Assert.True(FlightPlannerViewModel.SupportsGlobalWaypointRadius(Firmwares.ArduPlane));
Assert.False(FlightPlannerViewModel.SupportsGlobalLoiterRadius(Firmwares.ArduCopter2));
Assert.False(FlightPlannerViewModel.SupportsGlobalLoiterRadius(Firmwares.ArduRover));
Assert.True(FlightPlannerViewModel.SupportsGlobalLoiterRadius(Firmwares.ArduPlane));
}

Expand Down
Loading
Loading