Skip to content

Commit 40e7448

Browse files
committed
ArduPilotPlugin: auto-detect 16/32 servo packet via magic
1 parent 65937b7 commit 40e7448

3 files changed

Lines changed: 127 additions & 53 deletions

File tree

README.md

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,48 @@ echo 'export GZ_SIM_RESOURCE_PATH=$HOME/ardupilot_gazebo/models:$HOME/ardupilot_
125125

126126
Reload your terminal with `source ~/.bashrc` (or `source ~/.zshrc` on macOS).
127127

128+
### Servo Channels (16/32)
129+
130+
`ArduPilotPlugin` auto-detects 16 vs 32 output channels from the SITL packet
131+
magic value (`18458` for 16 channels, `29569` for 32 channels).
132+
133+
The SDF parameter `<have_32_channels>` is retained for compatibility and debug
134+
purposes, but no longer needs to match `SERVO_32_ENABLE` exactly at startup.
135+
If a mismatch is detected at runtime, the plugin logs a warning and overrides
136+
the value from incoming packets.
137+
138+
#### Tested
139+
140+
Manual verification was run with Gazebo logs captured to a file:
141+
142+
```bash
143+
gz sim -v4 -r iris_runway.sdf 2>&1 | tee /tmp/gz.log
144+
```
145+
146+
Then, in MAVProxy:
147+
148+
```text
149+
param show SERVO_32_ENABLE
150+
param set SERVO_32_ENABLE 1
151+
reboot
152+
param show SERVO_32_ENABLE
153+
param set SERVO_32_ENABLE 0
154+
reboot
155+
param show SERVO_32_ENABLE
156+
```
157+
158+
And in a shell:
159+
160+
```bash
161+
rg -n "ArduPilot|magic|Overriding|Connected" /tmp/gz.log
162+
```
163+
164+
Expected behavior:
165+
166+
- with `0 -> 1`, Gazebo logs a mismatch warning and overrides to 32 channels.
167+
- with `1 -> 0`, Gazebo logs a mismatch warning and overrides to 16 channels.
168+
- during SITL reboot, transient `ArduPilot controller has reset` warnings can appear.
169+
128170
## Usage
129171

130172
### 1. Iris quad-copter

include/ArduPilotPlugin.hh

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,8 @@ namespace sim
2929
{
3030
namespace systems
3131
{
32-
/// \todo(srmainwaring) handle 16 or 32 based on magic
33-
3432
// The servo packet received from ArduPilot SITL. Defined in SIM_JSON.h.
33+
// Channel count (16 vs 32) is auto-detected at runtime from the magic field.
3534
struct servo_packet_16 {
3635
uint16_t magic; // 18458 expected magic value
3736
uint16_t frame_rate;
@@ -87,7 +86,8 @@ class ArduPilotPluginPrivate;
8786
/// <lock_step> set true to enforce lock-step simulation
8887
/// <no_time_sync> set true to prevent SITL from trying to sync
8988
/// with wall-time
90-
/// <have_32_channels> set true if 32 channels are enabled
89+
/// <have_32_channels> optional compatibility/debug hint; runtime value is
90+
/// auto-detected from packet magic and may be overridden
9191
///
9292
class GZ_SIM_VISIBLE ArduPilotPlugin:
9393
public gz::sim::System,

src/ArduPilotPlugin.cc

Lines changed: 82 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1437,7 +1437,7 @@ ssize_t getServoPacket(
14371437
int counter = 0;
14381438
while (true)
14391439
{
1440-
TServoPacket last_pkt;
1440+
TServoPacket last_pkt{};
14411441
auto recvSize_last = _sock.recv(&last_pkt, sizeof(TServoPacket), 0ul);
14421442
if (recvSize_last == -1)
14431443
{
@@ -1482,42 +1482,17 @@ bool gz::sim::systems::ArduPilotPlugin::ReceiveServoPacket()
14821482
waitMs = 1;
14831483
}
14841484

1485-
// 16 / 32 channel compatibility
1486-
uint16_t pkt_magic{0};
1487-
uint16_t pkt_frame_rate{0};
1488-
uint16_t pkt_frame_count{0};
1489-
std::array<uint16_t, 32> pkt_pwm;
1490-
ssize_t recvSize{-1};
1491-
if (this->dataPtr->have32Channels)
1492-
{
1493-
servo_packet_32 pkt;
1494-
recvSize = getServoPacket(
1495-
this->dataPtr->sock,
1496-
this->dataPtr->fcu_address,
1497-
this->dataPtr->fcu_port_out,
1498-
waitMs,
1499-
this->dataPtr->modelName,
1500-
pkt);
1501-
pkt_magic = pkt.magic;
1502-
pkt_frame_rate = pkt.frame_rate;
1503-
pkt_frame_count = pkt.frame_count;
1504-
std::copy(std::begin(pkt.pwm), std::end(pkt.pwm), std::begin(pkt_pwm));
1505-
}
1506-
else
1507-
{
1508-
servo_packet_16 pkt;
1509-
recvSize = getServoPacket(
1510-
this->dataPtr->sock,
1511-
this->dataPtr->fcu_address,
1512-
this->dataPtr->fcu_port_out,
1513-
waitMs,
1514-
this->dataPtr->modelName,
1515-
pkt);
1516-
pkt_magic = pkt.magic;
1517-
pkt_frame_rate = pkt.frame_rate;
1518-
pkt_frame_count = pkt.frame_count;
1519-
std::copy(std::begin(pkt.pwm), std::end(pkt.pwm), std::begin(pkt_pwm));
1520-
}
1485+
// Always receive into the largest packet buffer (servo_packet_32, 72 bytes).
1486+
// The channel count is determined afterward from the magic field; this removes
1487+
// the need to keep <have_32_channels> in sync with the SITL configuration.
1488+
servo_packet_32 pkt{};
1489+
ssize_t recvSize = getServoPacket(
1490+
this->dataPtr->sock,
1491+
this->dataPtr->fcu_address,
1492+
this->dataPtr->fcu_port_out,
1493+
waitMs,
1494+
this->dataPtr->modelName,
1495+
pkt);
15211496

15221497
// didn't receive a packet, increment timeout count if online, then return
15231498
if (recvSize == -1)
@@ -1547,6 +1522,75 @@ bool gz::sim::systems::ArduPilotPlugin::ReceiveServoPacket()
15471522
return false;
15481523
}
15491524

1525+
// Infer 16 vs 32 channels directly from the magic value in the packet.
1526+
// Values come from SIM_JSON.h in the ArduPilot SITL library.
1527+
constexpr uint16_t magic_16 = 18458;
1528+
constexpr uint16_t magic_32 = 29569;
1529+
const size_t recvSizeBytes = static_cast<size_t>(recvSize);
1530+
bool detected32Channels;
1531+
if (pkt.magic == magic_32)
1532+
{
1533+
detected32Channels = true;
1534+
}
1535+
else if (pkt.magic == magic_16)
1536+
{
1537+
detected32Channels = false;
1538+
}
1539+
else
1540+
{
1541+
gzwarn << "Incorrect protocol magic "
1542+
<< pkt.magic << " (expected "
1543+
<< magic_16 << " or " << magic_32 << "), dropping packet.\n";
1544+
return false;
1545+
}
1546+
1547+
if (detected32Channels && recvSizeBytes != sizeof(servo_packet_32))
1548+
{
1549+
gzwarn << "[" << this->dataPtr->modelName << "] "
1550+
<< "Got 32-channel magic with unexpected packet size "
1551+
<< recvSize << " (expected " << sizeof(servo_packet_32)
1552+
<< "), dropping packet.\n";
1553+
return false;
1554+
}
1555+
1556+
if (!detected32Channels && recvSizeBytes != sizeof(servo_packet_16))
1557+
{
1558+
gzwarn << "[" << this->dataPtr->modelName << "] "
1559+
<< "Got 16-channel magic with unexpected packet size "
1560+
<< recvSize << " (expected " << sizeof(servo_packet_16)
1561+
<< "), dropping packet.\n";
1562+
return false;
1563+
}
1564+
1565+
// If the SDF-configured value disagrees with the auto-detected one, override
1566+
// it and log a warning. <have_32_channels> is retained for backward
1567+
// compatibility and debug purposes only.
1568+
if (detected32Channels != this->dataPtr->have32Channels)
1569+
{
1570+
gzwarn << "[" << this->dataPtr->modelName << "] "
1571+
<< "<have_32_channels>=" << this->dataPtr->have32Channels
1572+
<< " does not match packet magic"
1573+
<< " (detected " << (detected32Channels ? 32 : 16) << " channels)."
1574+
<< " Overriding from packet.\n";
1575+
this->dataPtr->have32Channels = detected32Channels;
1576+
}
1577+
1578+
const uint16_t pkt_frame_rate = pkt.frame_rate;
1579+
const uint32_t pkt_frame_count = pkt.frame_count;
1580+
1581+
// Build a unified 32-element PWM array, zero-initialised.
1582+
// For a 16-channel packet the upper 16 slots remain at zero.
1583+
std::array<uint16_t, 32> pkt_pwm{};
1584+
if (detected32Channels)
1585+
{
1586+
std::copy(std::begin(pkt.pwm), std::end(pkt.pwm), std::begin(pkt_pwm));
1587+
}
1588+
else
1589+
{
1590+
std::copy(std::begin(pkt.pwm), std::begin(pkt.pwm) + 16,
1591+
std::begin(pkt_pwm));
1592+
}
1593+
15501594
#if DEBUG_JSON_IO
15511595
int max_servo_channels = this->dataPtr->have32Channels ? 32 : 16;
15521596

@@ -1555,7 +1599,7 @@ bool gz::sim::systems::ArduPilotPlugin::ReceiveServoPacket()
15551599
oss << "recv " << recvSize << " bytes from "
15561600
<< this->dataPtr->fcu_address << ":"
15571601
<< this->dataPtr->fcu_port_out << "\n";
1558-
// oss << "magic: " << pkt_magic << "\n";
1602+
// oss << "magic: " << pkt.magic << "\n";
15591603
// oss << "frame_rate: " << pkt_frame_rate << "\n";
15601604
oss << "frame_count: " << pkt_frame_count << "\n";
15611605
// oss << "pwm: [";
@@ -1566,18 +1610,6 @@ bool gz::sim::systems::ArduPilotPlugin::ReceiveServoPacket()
15661610
gzdbg << "\n" << oss.str();
15671611
#endif
15681612

1569-
// check magic, return if invalid
1570-
constexpr uint16_t magic_16 = 18458;
1571-
constexpr uint16_t magic_32 = 29569;
1572-
uint16_t magic = this->dataPtr->have32Channels ? magic_32 : magic_16;
1573-
if (magic != pkt_magic)
1574-
{
1575-
gzwarn << "Incorrect protocol magic "
1576-
<< pkt_magic << " should be "
1577-
<< magic << "\n";
1578-
return false;
1579-
}
1580-
15811613
// the controller is online
15821614
if (!this->dataPtr->arduPilotOnline)
15831615
{

0 commit comments

Comments
 (0)