Added initial Laser Ranger Finder simulator on port 17211 (as default) emulate digital pointer for in game laser designators

This commit is contained in:
2026-05-14 18:53:34 -03:00
parent 720f9da2df
commit d4dfd80cdf
11 changed files with 310 additions and 15 deletions

View File

@@ -41,7 +41,7 @@ impl DigitalPointerPayload {
link_uid: Some(self.link_uid.clone()),
remarker: None,
video_url: None,
stale_seconds: None,
stale_seconds: Some(7),
}
}
}

59
src/cot/lrf.rs Normal file
View File

@@ -0,0 +1,59 @@
use arma_rs::{FromArma, FromArmaError};
use chrono::{SecondsFormat, Utc};
pub struct LaserRangeFinderPayload {
pub uid: String,
pub distance_meters: f64,
pub azimuth_degrees: f64,
pub elevation_degrees: f64,
}
impl FromArma for LaserRangeFinderPayload {
fn from_arma(data: String) -> Result<LaserRangeFinderPayload, FromArmaError> {
let (uid, distance_meters, azimuth_degrees, elevation_degrees) =
<(String, f64, f64, f64)>::from_arma(data)?;
Ok(Self {
uid,
distance_meters,
azimuth_degrees,
elevation_degrees,
})
}
}
impl LaserRangeFinderPayload {
pub fn to_lrf_message(&self) -> String {
let timestamp = Utc::now().to_rfc3339_opts(SecondsFormat::Millis, true);
format!(
"1,{},{},{:.2},{:.2},{:.2}",
self.uid,
timestamp,
self.distance_meters.max(0.0),
normalize_degrees(self.azimuth_degrees),
self.elevation_degrees
)
}
}
pub struct LaserRangeFinderClearPayload {
pub uid: String,
}
impl FromArma for LaserRangeFinderClearPayload {
fn from_arma(data: String) -> Result<LaserRangeFinderClearPayload, FromArmaError> {
let uid = String::from_arma(data)?;
Ok(Self { uid })
}
}
impl LaserRangeFinderClearPayload {
pub fn to_lrf_message(&self) -> String {
let timestamp = Utc::now().to_rfc3339_opts(SecondsFormat::Millis, true);
format!("1,{},{},RANGE_ERROR", self.uid, timestamp)
}
}
fn normalize_degrees(degrees: f64) -> f64 {
degrees.rem_euclid(360.0)
}

View File

@@ -4,6 +4,7 @@ pub mod digital_pointer;
pub mod draws;
pub mod eud;
pub mod gps;
pub mod lrf;
pub mod message;
pub mod nato;
pub mod report_marker;

View File

@@ -59,8 +59,17 @@ pub fn init() -> Extension {
"udp_socket",
Group::new()
.command("start", udp_socket::start)
.command("start_lrf", udp_socket::start_lrf)
.command("start_cot", udp_socket::start_cot)
.command("send_payload", udp_socket::send_payload)
.command("send_gps_cot", udp_socket::send_gps_cot)
.command("send_eud_cot", udp_socket::send_eud_cot)
.command("send_lrf", udp_socket::send_lrf)
.command("clear_lrf", udp_socket::clear_lrf)
.command(
"send_digital_pointer_cot",
udp_socket::send_digital_pointer_cot,
)
.command("stop", udp_socket::stop),
)
.group(

View File

@@ -93,6 +93,8 @@ impl UdpClient {
lazy_static! {
static ref UDP_CLIENT: Arc<Mutex<Option<UdpClient>>> = Arc::new(Mutex::new(None));
static ref LRF_CLIENT: Arc<Mutex<Option<UdpClient>>> = Arc::new(Mutex::new(None));
static ref COT_CLIENT: Arc<Mutex<Option<UdpClient>>> = Arc::new(Mutex::new(None));
}
pub fn start(ctx: Context, address: String) -> &'static str {
@@ -125,13 +127,22 @@ pub fn start(ctx: Context, address: String) -> &'static str {
"Starting UDP Client"
}
pub fn send_payload(ctx: Context, payload: String) -> &'static str {
if let Some(ref client) = *UDP_CLIENT.lock().unwrap() {
fn send_with_client(
client_slot: &Arc<Mutex<Option<UdpClient>>>,
ctx: Context,
payload: String,
missing_message: &'static str,
) {
if let Some(ref client) = *client_slot.lock().unwrap() {
client.send_payload(ctx, payload);
} else {
let _ = ctx.callback_null("UDP SOCKET ERROR", "UDP Socket is not running");
info!("UDP send requested while socket was not running");
let _ = ctx.callback_null("UDP SOCKET ERROR", missing_message);
info!("UDP send requested while target socket was not running");
}
}
pub fn send_payload(ctx: Context, payload: String) -> &'static str {
send_with_client(&UDP_CLIENT, ctx, payload, "UDP Socket is not running");
"Sending payload to UDP server"
}
@@ -146,6 +157,116 @@ pub fn send_gps_cot(
"Sending GPS Cursor Over Time to UDP server"
}
pub fn send_eud_cot(
ctx: Context,
cursor_over_time: cot::gps::ExternalPositionPayload,
) -> &'static str {
send_with_client(
&COT_CLIENT,
ctx,
cursor_over_time.to_cot().convert_to_xml(),
"CoT UDP Socket is not running",
);
"Sending EUD Cursor Over Time to CoT UDP server"
}
pub fn start_lrf(ctx: Context, address: String) -> &'static str {
info!("LRF UDP socket start requested for {}", address);
let (tx, rx): (Sender<UdpCommand>, Receiver<UdpCommand>) = mpsc::channel();
let client = UdpClient {
tx,
address: address.clone(),
};
{
let mut client_guard = LRF_CLIENT.lock().unwrap();
if let Some(ref existing_client) = *client_guard {
info!(
"Stopping previous LRF UDP client {} before starting {}",
existing_client.address, address
);
existing_client.stop();
}
*client_guard = Some(UdpClient {
tx: client.tx.clone(),
address: client.address.clone(),
});
}
client.start(address, rx, ctx);
"Starting LRF UDP Client"
}
pub fn start_cot(ctx: Context, address: String) -> &'static str {
info!("CoT UDP socket start requested for {}", address);
let (tx, rx): (Sender<UdpCommand>, Receiver<UdpCommand>) = mpsc::channel();
let client = UdpClient {
tx,
address: address.clone(),
};
{
let mut client_guard = COT_CLIENT.lock().unwrap();
if let Some(ref existing_client) = *client_guard {
info!(
"Stopping previous CoT UDP client {} before starting {}",
existing_client.address, address
);
existing_client.stop();
}
*client_guard = Some(UdpClient {
tx: client.tx.clone(),
address: client.address.clone(),
});
}
client.start(address, rx, ctx);
"Starting CoT UDP Client"
}
pub fn send_lrf(ctx: Context, payload: cot::lrf::LaserRangeFinderPayload) -> &'static str {
send_with_client(
&LRF_CLIENT,
ctx,
payload.to_lrf_message(),
"LRF UDP Socket is not running",
);
"Sending Laser Range Finder payload to UDP server"
}
pub fn clear_lrf(ctx: Context, payload: cot::lrf::LaserRangeFinderClearPayload) -> &'static str {
send_with_client(
&LRF_CLIENT,
ctx,
payload.to_lrf_message(),
"LRF UDP Socket is not running",
);
"Clearing Laser Range Finder payload on UDP server"
}
pub fn send_digital_pointer_cot(
ctx: Context,
payload: cot::digital_pointer::DigitalPointerPayload,
) -> &'static str {
send_with_client(
&COT_CLIENT,
ctx,
payload.to_cot().convert_to_xml(),
"CoT UDP Socket is not running",
);
"Sending Digital Pointer CoT to UDP server"
}
pub fn stop(ctx: Context) -> &'static str {
if let Some(ref client) = *UDP_CLIENT.lock().unwrap() {
info!("UDP socket stop requested for {}", client.address);
@@ -156,5 +277,15 @@ pub fn stop(ctx: Context) -> &'static str {
info!("UDP stop requested while socket was not running");
}
if let Some(ref client) = *LRF_CLIENT.lock().unwrap() {
info!("LRF UDP socket stop requested for {}", client.address);
client.stop();
}
if let Some(ref client) = *COT_CLIENT.lock().unwrap() {
info!("CoT UDP socket stop requested for {}", client.address);
client.stop();
}
"Stopping UDP Client"
}