Files
ardupilot/libraries/SITL/SIM_Beacon_NoopLoop.cpp
T
Peter BarkerandClaude Opus 4.8 cf0ca15c5e 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>
2026-06-30 10:28:56 +10:00

147 lines
5.5 KiB
C++

/*
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