SITL: add NoopLoop beacon simulator

Simulates a NoopLoop (LinkTrack) UWB positioning beacon on a serial port, emitting SETTING_FRAME0 (anchor positions) and NODE_FRAME2 (tag position and per-anchor ranges) frames for AP_Beacon_Nooploop to parse.  Attach with "--serial5=sim:nooploop".

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Peter Barker
2026-06-30 10:28:56 +10:00
committed by Peter Barker
co-authored by Claude Opus 4.8
parent 836df79ce6
commit cf0ca15c5e
5 changed files with 335 additions and 0 deletions
+146
View File
@@ -0,0 +1,146 @@
/*
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/*
Simulator for the NoopLoop (LinkTrack) UWB beacon system
*/
#include "SIM_Beacon_NoopLoop.h"
#if AP_SIM_NOOPLOOP_ENABLED
#include <AP_HAL/AP_HAL.h>
using namespace SITL;
// NODE_FRAME2 layout (tag position + anchor ranges); offsets and
// scalings must match AP_Beacon/AP_Beacon_Nooploop.cpp:
#define NOOPLOOP_HEADER 0x55
#define NOOPLOOP_FUNCTION_MARK_NODE_FRAME2 0x04
#define NOOPLOOP_NF2_SYSTIME 6 // 4 bytes, system time (ms)
#define NOOPLOOP_NF2_PRECISION_X 10 // 1 byte each x,y,z, m*100
#define NOOPLOOP_NF2_POSX 13 // 3 bytes, ENU x (East) m*1000
#define NOOPLOOP_NF2_POSY 16 // 3 bytes, ENU y (North) m*1000
#define NOOPLOOP_NF2_POSZ 19 // 3 bytes, ENU z (Up) m*1000
#define NOOPLOOP_NF2_VALID_NODES 118 // 1 byte, number of node blocks
#define NOOPLOOP_NF2_NODE_BLOCK 119 // start of first node block
#define NOOPLOOP_NF2_NODE_BLOCK_LEN 13 // bytes per node block
// SETTING_FRAME0 layout (anchor positions):
#define NOOPLOOP_HEADER2 0x54
#define NOOPLOOP_FUNCTION_MARK_SETTING_FRAME0 0x00
#define NOOPLOOP_SF0_SZ 128 // total frame length
#define NOOPLOOP_SF0_A0 37 // offset of first anchor
#define NOOPLOOP_SF0_ANCHOR_LEN 9 // 3 x 3-byte position
// anchors arranged in a rectangle around the beacon origin (NED metres):
Vector3f Beacon_NoopLoop::anchor_position(uint8_t i) const
{
switch (i) {
case 0: return Vector3f{ 25, 25, 0}; // NE
case 1: return Vector3f{-25, 25, 0}; // SE
case 2: return Vector3f{-25, -25, 0}; // SW
case 3: return Vector3f{ 25, -25, 0}; // NW
}
return Vector3f{};
}
void Beacon_NoopLoop::put_int24_le(uint8_t *buf, int32_t value_mm)
{
buf[0] = value_mm & 0xff;
buf[1] = (value_mm >> 8) & 0xff;
buf[2] = (value_mm >> 16) & 0xff;
}
void Beacon_NoopLoop::send_data(const Vector3f &pos_ned)
{
// the driver discards NODE_FRAME2 frames until it has the anchor
// positions, so (re)send those each cycle ahead of the position:
send_setting_frame0();
send_node_frame2(pos_ned);
}
void Beacon_NoopLoop::send_setting_frame0()
{
uint8_t buf[NOOPLOOP_SF0_SZ] {};
buf[0] = NOOPLOOP_HEADER2;
buf[1] = NOOPLOOP_FUNCTION_MARK_SETTING_FRAME0;
for (uint8_t i=0; i<num_anchors; i++) {
const Vector3f pos = anchor_position(i);
uint8_t *p = &buf[NOOPLOOP_SF0_A0 + i*NOOPLOOP_SF0_ANCHOR_LEN];
// anchor position is carried as ENU millimetres:
put_int24_le(&p[0], int32_t(pos.y * 1000)); // East
put_int24_le(&p[3], int32_t(pos.x * 1000)); // North
put_int24_le(&p[6], int32_t(-pos.z * 1000)); // Up
}
// the checksum is the modulo-256 sum of all preceding bytes:
uint8_t crc = 0;
for (uint16_t i=0; i<NOOPLOOP_SF0_SZ-1; i++) {
crc += buf[i];
}
buf[NOOPLOOP_SF0_SZ-1] = crc;
write_to_autopilot((const char*)buf, sizeof(buf));
}
void Beacon_NoopLoop::send_node_frame2(const Vector3f &pos_ned)
{
const uint16_t frame_len = NOOPLOOP_NF2_NODE_BLOCK + num_anchors*NOOPLOOP_NF2_NODE_BLOCK_LEN + 1;
uint8_t buf[NOOPLOOP_NF2_NODE_BLOCK + num_anchors*NOOPLOOP_NF2_NODE_BLOCK_LEN + 1] {};
buf[0] = NOOPLOOP_HEADER;
buf[1] = NOOPLOOP_FUNCTION_MARK_NODE_FRAME2;
buf[2] = frame_len & 0xff;
buf[3] = (frame_len >> 8) & 0xff;
// system time (ms):
const uint32_t now_ms = AP_HAL::millis();
buf[NOOPLOOP_NF2_SYSTIME+0] = now_ms & 0xff;
buf[NOOPLOOP_NF2_SYSTIME+1] = (now_ms >> 8) & 0xff;
buf[NOOPLOOP_NF2_SYSTIME+2] = (now_ms >> 16) & 0xff;
buf[NOOPLOOP_NF2_SYSTIME+3] = (now_ms >> 24) & 0xff;
// position precision in each axis (m*100); report 0.1m:
buf[NOOPLOOP_NF2_PRECISION_X+0] = 10;
buf[NOOPLOOP_NF2_PRECISION_X+1] = 10;
buf[NOOPLOOP_NF2_PRECISION_X+2] = 10;
// tag (vehicle) position, ENU millimetres:
put_int24_le(&buf[NOOPLOOP_NF2_POSX], int32_t(pos_ned.y * 1000)); // East
put_int24_le(&buf[NOOPLOOP_NF2_POSY], int32_t(pos_ned.x * 1000)); // North
put_int24_le(&buf[NOOPLOOP_NF2_POSZ], int32_t(-pos_ned.z * 1000)); // Up
// distance from the tag to each anchor:
buf[NOOPLOOP_NF2_VALID_NODES] = num_anchors;
for (uint8_t i=0; i<num_anchors; i++) {
uint8_t *block = &buf[NOOPLOOP_NF2_NODE_BLOCK + i*NOOPLOOP_NF2_NODE_BLOCK_LEN];
const float dist = (pos_ned - anchor_position(i)).length();
block[1] = i; // node id
put_int24_le(&block[2], int32_t(dist * 1000)); // distance (mm)
}
// the checksum is the modulo-256 sum of all preceding bytes:
uint8_t crc = 0;
for (uint16_t i=0; i<frame_len-1U; i++) {
crc += buf[i];
}
buf[frame_len-1] = crc;
write_to_autopilot((const char*)buf, frame_len);
}
#endif // AP_SIM_NOOPLOOP_ENABLED
+69
View File
@@ -0,0 +1,69 @@
/*
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/*
Simulator for the NoopLoop (LinkTrack) UWB beacon system
Driver: AP_Beacon/AP_Beacon_Nooploop.cpp
Use:
./Tools/autotest/sim_vehicle.py -v ArduCopter -A "--serial5=sim:nooploop" --map --console
param set SERIAL5_PROTOCOL 13 # beacon
param set BCN_TYPE 3 # nooploop
param set GPS1_TYPE 0
param set AHRS_EKF_TYPE 3
param set EK3_SRC1_POSXY 4 # beacon
param set BCN_LATITUDE -35.363262
param set BCN_LONGITUDE 149.165237
param set BCN_ALT 584.0
reboot
*/
#pragma once
#include "SIM_SerialBeacon.h"
#if AP_SIM_NOOPLOOP_ENABLED
namespace SITL {
class Beacon_NoopLoop : public SerialBeacon {
public:
using SerialBeacon::SerialBeacon;
protected:
void send_data(const Vector3f &pos_ned) override;
private:
static constexpr uint8_t num_anchors = 4;
// position of anchor i, in NED metres relative to the beacon origin:
Vector3f anchor_position(uint8_t i) const;
// build and emit the two frames the driver consumes:
void send_setting_frame0(); // anchor positions
void send_node_frame2(const Vector3f &pos_ned); // tag position + ranges
// store value (millimetres) as a signed 24-bit little-endian integer:
static void put_int24_le(uint8_t *buf, int32_t value_mm);
};
}
#endif // AP_SIM_NOOPLOOP_ENABLED
+61
View File
@@ -0,0 +1,61 @@
/*
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/*
Base class for serially-attached simulated positioning beacons
*/
#include "SIM_SerialBeacon.h"
#if AP_SIM_NOOPLOOP_ENABLED
#include <AP_HAL/AP_HAL.h>
#include <SITL/SITL.h>
using namespace SITL;
void SerialBeacon::update()
{
if (!init_sitl_pointer()) {
return;
}
// send data out at the beacon's reading rate:
const uint32_t now_ms = AP_HAL::millis();
if (now_ms - last_sent_ms < reading_interval_ms()) {
return;
}
last_sent_ms = now_ms;
// the beacon system origin is the same as the vehicle/EKF origin:
const Location &origin = _sitl->state.home;
if (!origin.initialised()) {
return;
}
// truth location of the simulated vehicle:
const Location veh_loc {
int32_t(_sitl->state.latitude * 1.0e7),
int32_t(_sitl->state.longitude * 1.0e7),
int32_t(_sitl->state.altitude * 1.0e2),
Location::AltFrame::ABSOLUTE
};
// vehicle position in NED metres relative to the beacon origin:
const Vector3f pos_ned = origin.get_distance_NED(veh_loc);
send_data(pos_ned);
}
#endif // AP_SIM_NOOPLOOP_ENABLED
+55
View File
@@ -0,0 +1,55 @@
/*
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/*
Base class for serially-attached simulated positioning beacons
*/
#pragma once
#include "SIM_config.h"
#if AP_SIM_NOOPLOOP_ENABLED // currently the only serial beacon
#include "SIM_SerialDevice.h"
#include <AP_Math/AP_Math.h>
namespace SITL {
class SerialBeacon : public SerialDevice {
public:
SerialBeacon() {}
// update; works out the vehicle's position relative to the beacon
// origin and sends beacon data to the autopilot:
void update();
protected:
// send beacon data describing the supplied vehicle position (in NED
// metres relative to the beacon origin); implemented per beacon type:
virtual void send_data(const Vector3f &pos_ned) = 0;
virtual uint16_t reading_interval_ms() const { return 200; } // 5Hz default
private:
uint32_t last_sent_ms;
};
}
#endif // AP_SIM_NOOPLOOP_ENABLED
+4
View File
@@ -13,6 +13,10 @@
#define AP_SIM_AIS_ENABLED (CONFIG_HAL_BOARD == HAL_BOARD_SITL)
#endif // AP_SIM_AIS_ENABLED
#ifndef AP_SIM_NOOPLOOP_ENABLED
#define AP_SIM_NOOPLOOP_ENABLED (CONFIG_HAL_BOARD == HAL_BOARD_SITL)
#endif // AP_SIM_NOOPLOOP_ENABLED
/*
* Simulated proximity sensor configuration:
*/