mirror of
https://github.com/valmojr/armatak.git
synced 2026-06-13 21:03:29 +00:00
Compare commits
63 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9b05bcccc2 | |||
| 80320c0f2c | |||
| b43a6c9748 | |||
| dcc9e1d469 | |||
| 3c37185c1a | |||
| 671e7d5dc1 | |||
| 0ebd192487 | |||
| 3fc54a1fb5 | |||
| eaf38a4d06 | |||
| 6376b7acf0 | |||
| 52edf94b17 | |||
| 3e11dd9e16 | |||
| a9f09b6ce6 | |||
| a43aa60f45 | |||
| 9cba642e9b | |||
| c7494da901 | |||
| b9e848d66e | |||
| 99f8d991be | |||
| 753dcab26e | |||
| 2f53488ba8 | |||
| 323339e679 | |||
| 3f14a75e81 | |||
| 469a54c141 | |||
| 2ee9030c00 | |||
| 5b29a40990 | |||
| 708fe5e670 | |||
| e32aadda4e | |||
| c35b7f0268 | |||
| 876cf900c3 | |||
| 778ac0ac54 | |||
| b816144fb0 | |||
| 61ba9f6d63 | |||
| f88c02a7aa | |||
| 5ffc08e6f1 | |||
| 9392380c78 | |||
| a18343b81d | |||
|
|
13cd08c655 | ||
|
|
8fe14dc18d | ||
|
|
1bec26df8a | ||
|
|
c5d5da636f | ||
|
|
c2e137e67c | ||
|
|
de5ac9dbb5 | ||
|
|
ef3be1e768 | ||
|
|
9bda92d389 | ||
|
|
9763cb6697 | ||
|
|
5ac49e12f8 | ||
|
|
2108d20b01 | ||
|
|
524e7a0b3e | ||
|
|
7e4379ada4 | ||
|
|
572b2a360f | ||
|
|
083ccd2906 | ||
|
|
35a45d2cd4 | ||
|
|
2b241fbeaf | ||
|
|
ad9ba834cc | ||
|
|
469403d9b5 | ||
|
|
01ea754f57 | ||
|
|
fd8a25790e | ||
|
|
9ede7237b8 | ||
|
|
1242b1f79f | ||
|
|
9f8f326446 | ||
|
|
bf3b0cf0d4 | ||
|
|
42098401f2 | ||
|
|
ef787a6a09 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -3,6 +3,7 @@
|
|||||||
hemtt
|
hemtt
|
||||||
hemtt.exe
|
hemtt.exe
|
||||||
*.biprivatekey
|
*.biprivatekey
|
||||||
|
.hemttprivatekey
|
||||||
source/
|
source/
|
||||||
.vscode
|
.vscode
|
||||||
releases/
|
releases/
|
||||||
|
|||||||
@@ -38,28 +38,31 @@ preset = "Hemtt"
|
|||||||
[hemtt.launch.default]
|
[hemtt.launch.default]
|
||||||
workshop = [
|
workshop = [
|
||||||
"450814997", # CBA_A3
|
"450814997", # CBA_A3
|
||||||
"463939057", # ACE
|
"463939057", # ace
|
||||||
"751965892", # ACRE2
|
"751965892", # ACRE2
|
||||||
"2522638637", # ACE Extended Arsenal
|
"2522638637", # ACE3 Arsenal Extended - Core
|
||||||
"333310405", # Enhanced Movement
|
"333310405", # Enhanced Movement
|
||||||
"2034363662", # Enhanced Movement Rework
|
"2034363662", # Enhanced Movement Rework
|
||||||
"2941986336", # Hatchet Interaction Framework - Stable Version
|
|
||||||
"1745501605", # Hatchet H-60 pack - Stable Version
|
|
||||||
"843577117", # RHSUSAF
|
"843577117", # RHSUSAF
|
||||||
"843425103", # RHSAFRF
|
"3147473073", # TOTT Core
|
||||||
"843632231", # RHSSAF
|
|
||||||
"843593391", # RHSGREF
|
|
||||||
"1673456286", # 3CB Factions
|
|
||||||
"623475643", # 3den Enhanced
|
"623475643", # 3den Enhanced
|
||||||
"2257686620", # Blastcore Murr Edition
|
"520618345", # Jbad
|
||||||
|
"1779063631", # Zeus Enhanced
|
||||||
|
"2397360831", # USAF Mod - Main
|
||||||
|
"2397376046", # USAF Mod - Utility
|
||||||
|
"3147476552", # TOTT Optics
|
||||||
"583496184", # CUP Terrains - Core
|
"583496184", # CUP Terrains - Core
|
||||||
"3078351739", # Kunduz River
|
|
||||||
"1858075458", # LAMBS_Danger.fsm
|
|
||||||
"1808238502", # LAMBS_Suppression
|
|
||||||
"3425368881", # M4A1_URGI
|
|
||||||
"2268351256", # Tier One Weapons
|
|
||||||
"2560276469", # Restrict Markers
|
"2560276469", # Restrict Markers
|
||||||
"3493557838" # Ballad of the Green Berets
|
"1858075458", # LAMBS_Danger.fsm
|
||||||
|
"3407948300", # JSRS SOUNDMOD 2025
|
||||||
|
"2834576684", # ITN - Illuminate The Night
|
||||||
|
"3375788189", # Immersion Cigs - Rewrite
|
||||||
|
"2095827925", # Brighter Flares
|
||||||
|
"2257686620", # Blastcore Murr Edition
|
||||||
|
"2260572637", # BettIR NVG
|
||||||
|
"3493557838", # Broad Spectrum Warfare
|
||||||
|
"1291778160", # Hellanmaa
|
||||||
|
"3533734689", # FPV
|
||||||
]
|
]
|
||||||
|
|
||||||
parameters = [
|
parameters = [
|
||||||
|
|||||||
1074
Cargo.lock
generated
1074
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -9,7 +9,10 @@ chrono = "0.4.39"
|
|||||||
lazy_static = "1.5.0"
|
lazy_static = "1.5.0"
|
||||||
log = "0.4.22"
|
log = "0.4.22"
|
||||||
log4rs = "1.3.0"
|
log4rs = "1.3.0"
|
||||||
once_cell = "1.19.0"
|
reqwest = { version = "0.12.15", default-features = false, features = ["blocking", "json", "rustls-tls"] }
|
||||||
|
rcgen = { version = "0.13.2", default-features = false, features = ["crypto", "pem", "aws_lc_rs"] }
|
||||||
|
rustls = "0.23.23"
|
||||||
|
rustls-pemfile = "2.2.0"
|
||||||
serde = { version = "1.0.210", features = ["derive"] }
|
serde = { version = "1.0.210", features = ["derive"] }
|
||||||
|
|
||||||
[dependencies.uuid]
|
[dependencies.uuid]
|
||||||
|
|||||||
@@ -4,6 +4,10 @@
|
|||||||
|
|
||||||
ARMATAK is a server side Arma 3 addons for streaming unit positions to TAK Clients in sessions on real locations maps. It can be runned both as a clientside mod or a serverside mod, when runned serverside, it will create a TCP Socket connection between Arma 3 and the TAK Server, sending the game session information into it. When used clientside, Arma 3 will host a websocket server that you can connect to your phone and mock the phone's location to the player's in game location.
|
ARMATAK is a server side Arma 3 addons for streaming unit positions to TAK Clients in sessions on real locations maps. It can be runned both as a clientside mod or a serverside mod, when runned serverside, it will create a TCP Socket connection between Arma 3 and the TAK Server, sending the game session information into it. When used clientside, Arma 3 will host a websocket server that you can connect to your phone and mock the phone's location to the player's in game location.
|
||||||
|
|
||||||
|
The server-side CoT router supports two transports:
|
||||||
|
- Plain TCP, for legacy TAK ingress.
|
||||||
|
- Mutual TLS, using the TAK Server authentication API, so the Arma session can publish as an authenticated TAK device on port `8089`.
|
||||||
|
|
||||||
## Get in Touch
|
## Get in Touch
|
||||||
|
|
||||||
[Join the Discord Server for ARMATAK!](https://discord.gg/svK64PCycU)
|
[Join the Discord Server for ARMATAK!](https://discord.gg/svK64PCycU)
|
||||||
|
|||||||
@@ -10,44 +10,79 @@ class armatak_udp_socket_start_dialog {
|
|||||||
class armatak_gui_module_udp_socket_dialog_main_frame: RscBackground {
|
class armatak_gui_module_udp_socket_dialog_main_frame: RscBackground {
|
||||||
idc = 16960;
|
idc = 16960;
|
||||||
x = "0.386562 * safezoneW + safezoneX";
|
x = "0.386562 * safezoneW + safezoneX";
|
||||||
y = "0.401 * safezoneH + safezoneY";
|
y = "0.357 * safezoneH + safezoneY";
|
||||||
w = "0.216563 * safezoneW";
|
w = "0.216563 * safezoneW";
|
||||||
h = "0.242 * safezoneH";
|
h = "0.418 * safezoneH";
|
||||||
colorBackground[]={0,0,0,0.45};
|
colorBackground[]={0,0,0,0.45};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
class Controls {
|
class Controls {
|
||||||
class armatak_gui_module_udp_socket_dialog_address_edit: RscEdit {
|
class armatak_gui_module_udp_socket_dialog_address_edit: RscEdit {
|
||||||
idc = 16961;
|
idc = 16961;
|
||||||
text = "168.15.0.3";
|
text = "192.168.15.121";
|
||||||
x = "0.391719 * safezoneW + safezoneX";
|
x = "0.391719 * safezoneW + safezoneX";
|
||||||
y = "0.445 * safezoneH + safezoneY";
|
y = "0.401 * safezoneH + safezoneY";
|
||||||
w = "0.20625 * safezoneW";
|
w = "0.20625 * safezoneW";
|
||||||
h = "0.044 * safezoneH";
|
h = "0.044 * safezoneH";
|
||||||
colorBackground[]={0,0,0,0.5};
|
colorBackground[]={0,0,0,0.5};
|
||||||
};
|
};
|
||||||
class armatak_gui_module_udp_socket_dialog_address_port_edit: RscEdit {
|
class armatak_gui_module_udp_socket_dialog_gnss_port_edit: RscEdit {
|
||||||
idc = 16962;
|
idc = 16962;
|
||||||
text = "4349";
|
text = "4349";
|
||||||
x = "0.391719 * safezoneW + safezoneX";
|
x = "0.391719 * safezoneW + safezoneX";
|
||||||
y = "0.522 * safezoneH + safezoneY";
|
y = "0.478 * safezoneH + safezoneY";
|
||||||
w = "0.20625 * safezoneW";
|
w = "0.20625 * safezoneW";
|
||||||
h = "0.044 * safezoneH";
|
h = "0.044 * safezoneH";
|
||||||
colorBackground[]={0,0,0,0.5};
|
colorBackground[]={0,0,0,0.5};
|
||||||
};
|
};
|
||||||
|
class armatak_gui_module_udp_socket_dialog_mavlink_port_edit: RscEdit {
|
||||||
|
idc = 16967;
|
||||||
|
text = "14550";
|
||||||
|
x = "0.391719 * safezoneW + safezoneX";
|
||||||
|
y = "0.555 * safezoneH + safezoneY";
|
||||||
|
w = "0.20625 * safezoneW";
|
||||||
|
h = "0.044 * safezoneH";
|
||||||
|
colorBackground[]={0,0,0,0.5};
|
||||||
|
};
|
||||||
|
class armatak_gui_module_udp_socket_dialog_video_feed_url_edit: RscEdit {
|
||||||
|
idc = 16969;
|
||||||
|
text = "";
|
||||||
|
x = "0.391719 * safezoneW + safezoneX";
|
||||||
|
y = "0.632 * safezoneH + safezoneY";
|
||||||
|
w = "0.20625 * safezoneW";
|
||||||
|
h = "0.044 * safezoneH";
|
||||||
|
colorBackground[]={0,0,0,0.5};
|
||||||
|
tooltip = "Optional shared feed URL. If empty, the UAV 3DEN URL is used first, then a local RTP fallback.";
|
||||||
|
};
|
||||||
class armatak_gui_module_udp_socket_dialog_address_text: RscText {
|
class armatak_gui_module_udp_socket_dialog_address_text: RscText {
|
||||||
idc = 16963;
|
idc = 16963;
|
||||||
text = "Phone's Socket Local Address";
|
text = "EUD's Address";
|
||||||
x = "0.391719 * safezoneW + safezoneX";
|
x = "0.391719 * safezoneW + safezoneX";
|
||||||
y = "0.412 * safezoneH + safezoneY";
|
y = "0.368 * safezoneH + safezoneY";
|
||||||
w = "0.20625 * safezoneW";
|
w = "0.20625 * safezoneW";
|
||||||
h = "0.033 * safezoneH";
|
h = "0.033 * safezoneH";
|
||||||
};
|
};
|
||||||
class armatak_gui_module_udp_socket_dialog_address_port_text: RscText {
|
class armatak_gui_module_udp_socket_dialog_gnss_port_text: RscText {
|
||||||
idc = 16964;
|
idc = 16964;
|
||||||
text = "Phone's Socket Local Port";
|
text = "Network GNSS Port";
|
||||||
x = "0.391719 * safezoneW + safezoneX";
|
x = "0.391719 * safezoneW + safezoneX";
|
||||||
y = "0.489 * safezoneH + safezoneY";
|
y = "0.445 * safezoneH + safezoneY";
|
||||||
|
w = "0.20625 * safezoneW";
|
||||||
|
h = "0.033 * safezoneH";
|
||||||
|
};
|
||||||
|
class armatak_gui_module_udp_socket_dialog_mavlink_port_text: RscText {
|
||||||
|
idc = 16968;
|
||||||
|
text = "Mavlink Port";
|
||||||
|
x = "0.391719 * safezoneW + safezoneX";
|
||||||
|
y = "0.522 * safezoneH + safezoneY";
|
||||||
|
w = "0.20625 * safezoneW";
|
||||||
|
h = "0.033 * safezoneH";
|
||||||
|
};
|
||||||
|
class armatak_gui_module_udp_socket_dialog_video_feed_url_text: RscText {
|
||||||
|
idc = 16970;
|
||||||
|
text = "Video Feed URL (Optional)";
|
||||||
|
x = "0.391719 * safezoneW + safezoneX";
|
||||||
|
y = "0.599 * safezoneH + safezoneY";
|
||||||
w = "0.20625 * safezoneW";
|
w = "0.20625 * safezoneW";
|
||||||
h = "0.033 * safezoneH";
|
h = "0.033 * safezoneH";
|
||||||
};
|
};
|
||||||
@@ -56,7 +91,7 @@ class armatak_udp_socket_start_dialog {
|
|||||||
text = "Cancel";
|
text = "Cancel";
|
||||||
action = "closeDialog 2;";
|
action = "closeDialog 2;";
|
||||||
x = "0.551563 * safezoneW + safezoneX";
|
x = "0.551563 * safezoneW + safezoneX";
|
||||||
y = "0.577 * safezoneH + safezoneY";
|
y = "0.709 * safezoneH + safezoneY";
|
||||||
w = "0.0464063 * safezoneW";
|
w = "0.0464063 * safezoneW";
|
||||||
h = "0.055 * safezoneH";
|
h = "0.055 * safezoneH";
|
||||||
};
|
};
|
||||||
@@ -65,7 +100,7 @@ class armatak_udp_socket_start_dialog {
|
|||||||
text = "Ok";
|
text = "Ok";
|
||||||
action = QUOTE(call FUNC(startUDPSocket));
|
action = QUOTE(call FUNC(startUDPSocket));
|
||||||
x = "0.5 * safezoneW + safezoneX";
|
x = "0.5 * safezoneW + safezoneX";
|
||||||
y = "0.577 * safezoneH + safezoneY";
|
y = "0.709 * safezoneH + safezoneY";
|
||||||
w = "0.0464063 * safezoneW";
|
w = "0.0464063 * safezoneW";
|
||||||
h = "0.055 * safezoneH";
|
h = "0.055 * safezoneH";
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -38,6 +38,9 @@ switch (toLower worldName) do {
|
|||||||
case "vr": {
|
case "vr": {
|
||||||
_realLocation = _position call armatak_fnc_convert_to_vr;
|
_realLocation = _position call armatak_fnc_convert_to_vr;
|
||||||
};
|
};
|
||||||
|
case "lawn": {
|
||||||
|
_realLocation = _position call armatak_fnc_convert_to_lawn;
|
||||||
|
};
|
||||||
case "cucui": {
|
case "cucui": {
|
||||||
_realLocation = _position call armatak_fnc_convert_to_cucui;
|
_realLocation = _position call armatak_fnc_convert_to_cucui;
|
||||||
};
|
};
|
||||||
@@ -74,9 +77,42 @@ switch (toLower worldName) do {
|
|||||||
case "kunduz_valley": {
|
case "kunduz_valley": {
|
||||||
_realLocation = _position call armatak_fnc_convert_to_kunduz_valley;
|
_realLocation = _position call armatak_fnc_convert_to_kunduz_valley;
|
||||||
};
|
};
|
||||||
|
case "malvinasfalkands": {
|
||||||
|
_realLocation = _position call armatak_fnc_convert_to_malvinas_malvinasfalkands;
|
||||||
|
};
|
||||||
|
case "pebble_island_airfield": {
|
||||||
|
_realLocation = _position call armatak_fnc_convert_to_malvinas_pebble_island_airfield;
|
||||||
|
};
|
||||||
|
case "p_argentino_stanley": {
|
||||||
|
_realLocation = _position call armatak_fnc_convert_to_malvinas_p_argentino_stanley;
|
||||||
|
};
|
||||||
|
case "top_malo_house": {
|
||||||
|
_realLocation = _position call armatak_fnc_convert_to_malvinas_top_malo_house;
|
||||||
|
};
|
||||||
|
case "pradera_ganso": {
|
||||||
|
_realLocation = _position call armatak_fnc_convert_to_malvinas_pradera_ganso;
|
||||||
|
};
|
||||||
case "tanoa": {
|
case "tanoa": {
|
||||||
_realLocation = _position call armatak_fnc_convert_to_tanoa;
|
_realLocation = _position call armatak_fnc_convert_to_tanoa;
|
||||||
}
|
};
|
||||||
|
case "zagor_zagorsk_reserved_forest": {
|
||||||
|
_realLocation = _position call armatak_fnc_convert_to_zagor_zagorsk_reserved_forest;
|
||||||
|
};
|
||||||
|
case "umb_colombia": {
|
||||||
|
_realLocation = _position call armatak_fnc_convert_to_colombia;
|
||||||
|
};
|
||||||
|
case "clafghan": {
|
||||||
|
_realLocation = _position call armatak_fnc_convert_to_clafghan;
|
||||||
|
};
|
||||||
|
case "rut_mandol": {
|
||||||
|
_realLocation = _position call armatak_fnc_convert_to_rut_mandol;
|
||||||
|
};
|
||||||
|
case "hellanmaa": {
|
||||||
|
_realLocation = _position call armatak_fnc_convert_to_hellanmaa;
|
||||||
|
};
|
||||||
|
case "hellanmaaw": {
|
||||||
|
_realLocation = _position call armatak_fnc_convert_to_hellanmaa;
|
||||||
|
};
|
||||||
default {
|
default {
|
||||||
_warning = format ["<t color='#FF8021'>ARMATAK</t><br/> %1", "Unsupported Map"];
|
_warning = format ["<t color='#FF8021'>ARMATAK</t><br/> %1", "Unsupported Map"];
|
||||||
[[_warning, 1.5]] call CBA_fnc_notify;
|
[[_warning, 1.5]] call CBA_fnc_notify;
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
params ["_logic"];
|
params ["_logic"];
|
||||||
|
|
||||||
_socket_is_running = player getVariable [QGVAR(eudConnected), false];
|
private _socket_is_running = player getVariable [QGVAR(eudConnected), false];
|
||||||
|
|
||||||
if (_socket_is_running) exitWith {
|
if (_socket_is_running) exitWith {
|
||||||
["Socket is already running", "error", "UDP Socket"] call EFUNC(main,notify);
|
["Socket is already running", "error", "UDP Socket"] call EFUNC(main,notify);
|
||||||
@@ -11,20 +11,34 @@ if (_socket_is_running) exitWith {
|
|||||||
|
|
||||||
disableSerialization;
|
disableSerialization;
|
||||||
|
|
||||||
_udp_socket_instance_address = ctrlText 16961;
|
private _eud_address = ctrlText 16961;
|
||||||
_udp_socket_instance_port = ctrlText 16962;
|
private _gnss_port = ctrlText 16962;
|
||||||
|
private _mavlink_port = ctrlText 16967;
|
||||||
|
private _video_feed_url = ctrlText 16969;
|
||||||
|
|
||||||
_udp_socket_fulladdress = ((_udp_socket_instance_address) + ":" + (_udp_socket_instance_port));
|
private _udp_socket_fulladdress = _eud_address + ":" + _gnss_port;
|
||||||
|
private _mavlink_address = _eud_address + ":" + _mavlink_port;
|
||||||
|
|
||||||
player setVariable [QGVAR(udp_socket_address), _udp_socket_fulladdress];
|
player setVariable [QGVAR(udp_socket_address), _udp_socket_fulladdress];
|
||||||
|
player setVariable [QGVAR(mavlink_address), _mavlink_address];
|
||||||
|
player setVariable [QGVAR(video_feed_url), trim _video_feed_url];
|
||||||
player setVariable [QGVAR(eudConnected), true];
|
player setVariable [QGVAR(eudConnected), true];
|
||||||
|
|
||||||
|
private _advertised_video_uri = [objNull] call EFUNC(uav,resolveVideoUri);
|
||||||
|
|
||||||
"armatak" callExtension ["udp_socket:start", [_udp_socket_fulladdress]];
|
"armatak" callExtension ["udp_socket:start", [_udp_socket_fulladdress]];
|
||||||
|
"armatak" callExtension ["uas:start_endpoint", [parseNumber _mavlink_port]];
|
||||||
|
|
||||||
|
private _mdnsInstanceName = format ["ArmaTAK-%1", name player];
|
||||||
|
"armatak" callExtension ["mdns:start_uas_advertisement", [_mdnsInstanceName, parseNumber _mavlink_port, _advertised_video_uri]];
|
||||||
|
"armatak" callExtension ["log", [["info", format ["Client UDP socket started for %1, MAVLink target set to %2 and advertised video URI set to %3", _udp_socket_fulladdress, _mavlink_address, _advertised_video_uri]]]];
|
||||||
|
|
||||||
|
call EFUNC(uav,startMavlinkBroadcast);
|
||||||
|
|
||||||
[{
|
[{
|
||||||
if (player getVariable [QGVAR(eudConnected), false]) then {
|
if !(player getVariable [QGVAR(eudConnected), false]) exitWith {};
|
||||||
|
|
||||||
"armatak" callExtension ["udp_socket:send_gps_cot", [player call FUNC(extractClientPosition)]];
|
"armatak" callExtension ["udp_socket:send_gps_cot", [player call FUNC(extractClientPosition)]];
|
||||||
};
|
|
||||||
}, 0.5, []] call CBA_fnc_addPerFrameHandler;
|
}, 0.5, []] call CBA_fnc_addPerFrameHandler;
|
||||||
|
|
||||||
deleteVehicle _logic;
|
deleteVehicle _logic;
|
||||||
|
|||||||
@@ -78,6 +78,16 @@ class Cfg3den {
|
|||||||
condition = "objectVehicle";
|
condition = "objectVehicle";
|
||||||
typeName = "STRING";
|
typeName = "STRING";
|
||||||
};
|
};
|
||||||
|
class armatak_attribute_marker_video_url {
|
||||||
|
displayName = "Video Feed URL";
|
||||||
|
tooltip = "Shared UAV video URL. This per-vehicle value overrides the optional session video URL from Connect to EUD.";
|
||||||
|
property = "armatak_attribute_marker_video_url";
|
||||||
|
control = "Edit";
|
||||||
|
expression = "_this setVariable ['armatak_attribute_marker_video_url',_value]";
|
||||||
|
defaultValue = "''";
|
||||||
|
condition = "objectVehicle";
|
||||||
|
typeName = "STRING";
|
||||||
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -10,12 +10,27 @@ class CfgFunctions {
|
|||||||
class send_group_cots {
|
class send_group_cots {
|
||||||
file = "\armatak\armatak\addons\main\functions\api\fn_send_group_cots.sqf";
|
file = "\armatak\armatak\addons\main\functions\api\fn_send_group_cots.sqf";
|
||||||
};
|
};
|
||||||
|
class send_enemy_cot {
|
||||||
|
file = "\armatak\armatak\addons\main\functions\api\fn_send_enemy_cot.sqf";
|
||||||
|
};
|
||||||
class send_eud_cot {
|
class send_eud_cot {
|
||||||
file = "\armatak\armatak\addons\main\functions\api\fn_send_eud_cot.sqf";
|
file = "\armatak\armatak\addons\main\functions\api\fn_send_eud_cot.sqf";
|
||||||
};
|
};
|
||||||
class send_marker_cot {
|
class send_marker_cot {
|
||||||
file = "\armatak\armatak\addons\main\functions\api\fn_send_marker_cot.sqf";
|
file = "\armatak\armatak\addons\main\functions\api\fn_send_marker_cot.sqf";
|
||||||
};
|
};
|
||||||
|
class send_uas_platform_cot {
|
||||||
|
file = "\armatak\armatak\addons\main\functions\api\fn_send_uas_platform_cot.sqf";
|
||||||
|
};
|
||||||
|
class send_uas_video_cot {
|
||||||
|
file = "\armatak\armatak\addons\main\functions\api\fn_send_uas_video_cot.sqf";
|
||||||
|
};
|
||||||
|
class send_uas_sensor_cot {
|
||||||
|
file = "\armatak\armatak\addons\main\functions\api\fn_send_uas_sensor_cot.sqf";
|
||||||
|
};
|
||||||
|
class set_uas_camera_override {
|
||||||
|
file = "\armatak\armatak\addons\main\functions\api\fn_set_uas_camera_override.sqf";
|
||||||
|
};
|
||||||
class stop_tcp_socket {
|
class stop_tcp_socket {
|
||||||
file = "\armatak\armatak\addons\main\functions\api\fn_stop_tcp_socket.sqf";
|
file = "\armatak\armatak\addons\main\functions\api\fn_stop_tcp_socket.sqf";
|
||||||
};
|
};
|
||||||
@@ -28,9 +43,18 @@ class CfgFunctions {
|
|||||||
class extract_marker_callsign {
|
class extract_marker_callsign {
|
||||||
file = "\armatak\armatak\addons\main\functions\extract_data\fn_extract_marker_callsign.sqf";
|
file = "\armatak\armatak\addons\main\functions\extract_data\fn_extract_marker_callsign.sqf";
|
||||||
};
|
};
|
||||||
|
class extract_marker_video_url {
|
||||||
|
file = "\armatak\armatak\addons\main\functions\extract_data\fn_extract_marker_video_url.sqf";
|
||||||
|
};
|
||||||
class extract_role {
|
class extract_role {
|
||||||
file = "\armatak\armatak\addons\main\functions\extract_data\fn_extract_role.sqf";
|
file = "\armatak\armatak\addons\main\functions\extract_data\fn_extract_role.sqf";
|
||||||
};
|
};
|
||||||
|
class extract_sensor_data {
|
||||||
|
file = "\armatak\armatak\addons\main\functions\extract_data\fn_extract_sensor_data.sqf";
|
||||||
|
};
|
||||||
|
class extract_uas_camera_data {
|
||||||
|
file = "\armatak\armatak\addons\main\functions\extract_data\fn_extract_uas_camera_data.sqf";
|
||||||
|
};
|
||||||
class extract_unit_callsign {
|
class extract_unit_callsign {
|
||||||
file = "\armatak\armatak\addons\main\functions\extract_data\fn_extract_unit_callsign.sqf";
|
file = "\armatak\armatak\addons\main\functions\extract_data\fn_extract_unit_callsign.sqf";
|
||||||
};
|
};
|
||||||
@@ -62,12 +86,30 @@ class CfgFunctions {
|
|||||||
class convert_to_kunduz_valley {
|
class convert_to_kunduz_valley {
|
||||||
file = "\armatak\armatak\addons\main\functions\map\fn_convert_to_kunduz_valley.sqf";
|
file = "\armatak\armatak\addons\main\functions\map\fn_convert_to_kunduz_valley.sqf";
|
||||||
};
|
};
|
||||||
|
class convert_to_lawn {
|
||||||
|
file = "\armatak\armatak\addons\main\functions\map\fn_convert_to_lawn.sqf";
|
||||||
|
};
|
||||||
class convert_to_livonia {
|
class convert_to_livonia {
|
||||||
file = "\armatak\armatak\addons\main\functions\map\fn_convert_to_livonia.sqf";
|
file = "\armatak\armatak\addons\main\functions\map\fn_convert_to_livonia.sqf";
|
||||||
};
|
};
|
||||||
class convert_to_malden {
|
class convert_to_malden {
|
||||||
file = "\armatak\armatak\addons\main\functions\map\fn_convert_to_malden.sqf";
|
file = "\armatak\armatak\addons\main\functions\map\fn_convert_to_malden.sqf";
|
||||||
};
|
};
|
||||||
|
class convert_to_malvinas_malvinasfalkands {
|
||||||
|
file = "\armatak\armatak\addons\main\functions\map\fn_convert_to_malvinas_malvinasfalkands.sqf";
|
||||||
|
};
|
||||||
|
class convert_to_malvinas_p_argentino_stanley {
|
||||||
|
file = "\armatak\armatak\addons\main\functions\map\fn_convert_to_malvinas_p_argentino_stanley.sqf";
|
||||||
|
};
|
||||||
|
class convert_to_malvinas_pebble_island_airfield {
|
||||||
|
file = "\armatak\armatak\addons\main\functions\map\fn_convert_to_malvinas_pebble_island_airfield.sqf";
|
||||||
|
};
|
||||||
|
class convert_to_malvinas_pradera_ganso {
|
||||||
|
file = "\armatak\armatak\addons\main\functions\map\fn_convert_to_malvinas_pradera_ganso.sqf";
|
||||||
|
};
|
||||||
|
class convert_to_malvinas_top_malo_house {
|
||||||
|
file = "\armatak\armatak\addons\main\functions\map\fn_convert_to_malvinas_top_malo_house.sqf";
|
||||||
|
};
|
||||||
class convert_to_southen_sahrani {
|
class convert_to_southen_sahrani {
|
||||||
file = "\armatak\armatak\addons\main\functions\map\fn_convert_to_southen_sahrani.sqf";
|
file = "\armatak\armatak\addons\main\functions\map\fn_convert_to_southen_sahrani.sqf";
|
||||||
};
|
};
|
||||||
@@ -86,6 +128,22 @@ class CfgFunctions {
|
|||||||
class convert_to_vr {
|
class convert_to_vr {
|
||||||
file = "\armatak\armatak\addons\main\functions\map\fn_convert_to_vr.sqf";
|
file = "\armatak\armatak\addons\main\functions\map\fn_convert_to_vr.sqf";
|
||||||
};
|
};
|
||||||
|
class convert_to_zagor_zagorsk_reserved_forest {
|
||||||
|
file = "\armatak\armatak\addons\main\functions\map\fn_convert_to_zagor_zagorsk_reserved_forest.sqf";
|
||||||
|
};
|
||||||
|
class convert_to_colombia {
|
||||||
|
file = "\armatak\armatak\addons\main\functions\map\fn_convert_to_colombia.sqf";
|
||||||
|
};
|
||||||
|
class convert_to_clafghan {
|
||||||
|
file = "\armatak\armatak\addons\main\functions\map\fn_convert_to_clafghan.sqf";
|
||||||
|
};
|
||||||
|
class convert_to_rut_mandol {
|
||||||
|
file = "\armatak\armatak\addons\main\functions\map\fn_convert_to_rut_mandol.sqf";
|
||||||
|
};
|
||||||
|
class convert_to_hellanmaa {
|
||||||
|
file = "\armatak\armatak\addons\main\functions\map\fn_convert_to_hellanmaa.sqf";
|
||||||
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,9 @@ addMissionEventHandler ["ExtensionCallback", {
|
|||||||
};
|
};
|
||||||
case "EUD Disconnected": {
|
case "EUD Disconnected": {
|
||||||
SETVAR(player,EGVAR(client,eudConnected),false);
|
SETVAR(player,EGVAR(client,eudConnected),false);
|
||||||
|
call EFUNC(uav,stopMavlinkBroadcast);
|
||||||
|
"armatak" callExtension ["uas:stop_endpoint", []];
|
||||||
|
"armatak" callExtension ["mdns:stop", []];
|
||||||
};
|
};
|
||||||
default {};
|
default {};
|
||||||
};
|
};
|
||||||
@@ -25,21 +28,74 @@ addMissionEventHandler ["ExtensionCallback", {
|
|||||||
|
|
||||||
if (_function == "UDP Socket is not running") then {
|
if (_function == "UDP Socket is not running") then {
|
||||||
SETVAR(player,EGVAR(client,eudConnected),false);
|
SETVAR(player,EGVAR(client,eudConnected),false);
|
||||||
|
call EFUNC(uav,stopMavlinkBroadcast);
|
||||||
|
"armatak" callExtension ["uas:stop_endpoint", []];
|
||||||
|
"armatak" callExtension ["mdns:stop", []];
|
||||||
};
|
};
|
||||||
|
|
||||||
if (_function == "failed to bind UDP socket") then {
|
if (_function == "failed to bind UDP socket") then {
|
||||||
SETVAR(player,EGVAR(client,eudConnected),false);
|
SETVAR(player,EGVAR(client,eudConnected),false);
|
||||||
|
call EFUNC(uav,stopMavlinkBroadcast);
|
||||||
|
"armatak" callExtension ["uas:stop_endpoint", []];
|
||||||
|
"armatak" callExtension ["mdns:stop", []];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
case "MAVLINK UDP ERROR": {
|
||||||
|
_message = _function;
|
||||||
|
if (_data isNotEqualTo "") then {
|
||||||
|
_message = format ["%1: %2", _function, _data];
|
||||||
|
};
|
||||||
|
|
||||||
|
[_message, "warning", _name] call FUNC(notify);
|
||||||
|
};
|
||||||
|
case "MAVLINK UDP": {
|
||||||
|
private _history = missionNamespace getVariable ["armatak_uav_mavlink_callback_history", []];
|
||||||
|
_history pushBack [diag_tickTime, _function, _data];
|
||||||
|
if ((count _history) > 50) then {
|
||||||
|
_history deleteRange [0, (count _history) - 50];
|
||||||
|
};
|
||||||
|
missionNamespace setVariable ["armatak_uav_mavlink_callback_history", _history];
|
||||||
|
missionNamespace setVariable ["armatak_uav_last_mavlink_callback", [diag_tickTime, _function, _data]];
|
||||||
|
|
||||||
|
switch (_function) do {
|
||||||
|
case "COMMAND_LONG";
|
||||||
|
case "COMMAND_INT";
|
||||||
|
case "COMMAND_ACK";
|
||||||
|
case "MISSION_COUNT";
|
||||||
|
case "MISSION_ITEM";
|
||||||
|
case "MISSION_ITEM_INT";
|
||||||
|
case "MISSION_CLEAR_ALL";
|
||||||
|
case "MISSION_SET_CURRENT";
|
||||||
|
case "SET_HOME_POSITION";
|
||||||
|
case "SET_MODE";
|
||||||
|
case "SET_POSITION_TARGET_GLOBAL_INT";
|
||||||
|
case "MANUAL_CONTROL": {
|
||||||
|
"armatak" callExtension ["log", [["info", format ["MAVLINK UDP CALLBACK %1 %2", _function, _data]]]];
|
||||||
|
[_function, _data] call EFUNC(uav,handleMavlinkCallback);
|
||||||
|
};
|
||||||
|
default {};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
case "TCP SOCKET": {
|
case "TCP SOCKET": {
|
||||||
[_function, "success", _name] call FUNC(notify);
|
[_function, "success", _name] call FUNC(notify);
|
||||||
};
|
};
|
||||||
case "TCP SOCKET ERROR": {
|
case "TCP SOCKET ERROR": {
|
||||||
[_function, "error", _name] call FUNC(notify);
|
_message = _function;
|
||||||
|
if (_data isNotEqualTo "") then {
|
||||||
|
_message = format ["%1: %2", _function, _data];
|
||||||
|
};
|
||||||
|
|
||||||
|
[_message, "error", _name] call FUNC(notify);
|
||||||
};
|
};
|
||||||
case "VIDEO": {
|
case "VIDEO": {
|
||||||
[_function, "success", _name] call FUNC(notify);
|
[_function, "success", _name] call FUNC(notify);
|
||||||
};
|
};
|
||||||
|
case "MDNS": {
|
||||||
|
[_function, "success", _name] call FUNC(notify);
|
||||||
|
};
|
||||||
|
case "MDNS ERROR": {
|
||||||
|
[_function, "warning", _name] call FUNC(notify);
|
||||||
|
};
|
||||||
case "VIDEO ERROR": {
|
case "VIDEO ERROR": {
|
||||||
[_function, "error", _name] call FUNC(notify);
|
[_function, "error", _name] call FUNC(notify);
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ if (!isNull _digitalPointer) then {
|
|||||||
_link_uid = [_unit] call armatak_fnc_extract_uuid;
|
_link_uid = [_unit] call armatak_fnc_extract_uuid;
|
||||||
_contact_callsign = ([player] call armatak_fnc_extract_unit_callsign) + ".DP1";
|
_contact_callsign = ([player] call armatak_fnc_extract_unit_callsign) + ".DP1";
|
||||||
|
|
||||||
_dpCot = [_link_uid, _contact_callsign, _digitalPointerPosition select 0, _digitalPointerPosition select 1, _digitalPointerPosition select 2];
|
_dpCot = [_link_uid, _contact_callsign, _digitalPointerPosition select 1, _digitalPointerPosition select 2, _digitalPointerPosition select 3];
|
||||||
|
|
||||||
"armatak" callExtension ["tcp_socket:send_digital_pointer_cot", [_dpCot]];
|
"armatak" callExtension ["tcp_socket:cot:digital_pointer", [_dpCot]];
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,28 +1,7 @@
|
|||||||
// function name: armatak_fnc_extract_drone
|
// function name: armatak_fnc_send_drone_cot
|
||||||
// function author: Valmo
|
// function author: Valmo
|
||||||
// function description: Gets the drone information for the CoT Router
|
// function description: Gets the drone information for the CoT Router
|
||||||
|
|
||||||
params["_drone"];
|
params["_drone"];
|
||||||
|
|
||||||
private _atak_role = "a-f-A";
|
[_drone] call armatak_fnc_send_uas_platform_cot;
|
||||||
private _atak_callsign = [_unit] call armatak_fnc_extract_unit_callsign;
|
|
||||||
|
|
||||||
switch (side _drone) do {
|
|
||||||
case "WEST": {
|
|
||||||
_atak_role = "a-f-A-M-F-Q"
|
|
||||||
};
|
|
||||||
case "EAST": {
|
|
||||||
_atak_role = "a-h-A-M-F-Q"
|
|
||||||
};
|
|
||||||
case "INDEPENDENT": {
|
|
||||||
_atak_role = "a-n-A-M-F-Q"
|
|
||||||
};
|
|
||||||
case "CIVILIAN": {
|
|
||||||
_atak_role = "a-f-A-C"
|
|
||||||
};
|
|
||||||
default {
|
|
||||||
_atak_role = "a-f-A-M-F-Q"
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
_cot = [_drone, _atak_role, _atak_callsign] call armatak_fnc_send_marker_cot;
|
|
||||||
|
|||||||
15
addons/main/functions/api/fn_send_enemy_cot.sqf
Normal file
15
addons/main/functions/api/fn_send_enemy_cot.sqf
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
// function name: armatak_fnc_send_eud_cot
|
||||||
|
// function author: Valmo
|
||||||
|
// function description: Gets the information necessary for generating the EUD Cursor Over Time
|
||||||
|
|
||||||
|
params ["_unit"];
|
||||||
|
|
||||||
|
_unit_position = _unit call armatak_client_fnc_extractClientPosition;
|
||||||
|
|
||||||
|
_uuid = _unit call armatak_fnc_extract_uuid;
|
||||||
|
_type = _unit call armatak_fnc_extract_role;
|
||||||
|
_callsign = _unit call armatak_fnc_extract_marker_callsign;
|
||||||
|
|
||||||
|
_marker_cot = [_uuid, _type, _unit_position select 1, _unit_position select 2, _unit_position select 3, _callsign, _unit_position select 5, _unit_position select 6];
|
||||||
|
|
||||||
|
"armatak" callExtension ["tcp_socket:cot:marker", [_marker_cot]];
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
// function name: armatak_fnc_extract_eud_cot_info
|
// function name: armatak_fnc_send_eud_cot
|
||||||
// function author: Valmo
|
// function author: Valmo
|
||||||
// function description: Gets the information necessary for generating the EUD Cursor Over Time
|
// function description: Gets the information necessary for generating the EUD Cursor Over Time
|
||||||
|
|
||||||
@@ -8,6 +8,5 @@ _position = _unit call armatak_client_fnc_extractClientPosition;
|
|||||||
|
|
||||||
_uuid = _unit call armatak_fnc_extract_uuid;
|
_uuid = _unit call armatak_fnc_extract_uuid;
|
||||||
|
|
||||||
_eud_cot = [_uuid, _position select 0, _position select 1, _position select 2, _callsign, _group_name, _group_role, _position select 3, speed player / 3.6];
|
_eud_cot = [_uuid, _position select 1, _position select 2, _position select 3, _callsign, _group_name, _group_role, _position select 5, _position select 6];
|
||||||
|
"armatak" callExtension ["tcp_socket:cot:eud", [_eud_cot]];
|
||||||
"armatak" callExtension ["tcp_socket:send_eud_cot", [_eud_cot]];
|
|
||||||
|
|||||||
@@ -5,9 +5,10 @@
|
|||||||
params ["_unit", "_type", "_callsign"];
|
params ["_unit", "_type", "_callsign"];
|
||||||
|
|
||||||
_unit_position = _unit call armatak_client_fnc_extractClientPosition;
|
_unit_position = _unit call armatak_client_fnc_extractClientPosition;
|
||||||
|
_video_url = [_unit] call armatak_fnc_extract_marker_video_url;
|
||||||
|
|
||||||
_uuid = _unit call armatak_fnc_extract_uuid;
|
_uuid = _unit call armatak_fnc_extract_uuid;
|
||||||
|
|
||||||
_marker_cot = [_uuid, _type, _unit_position select 0, _unit_position select 1, _unit_position select 2, _callsign, _unit_position select 3, speed _unit / 3.6];
|
_marker_cot = [_uuid, _type, _unit_position select 1, _unit_position select 2, _unit_position select 3, _callsign, _unit_position select 5, _unit_position select 6, _video_url];
|
||||||
|
|
||||||
"armatak" callExtension ["tcp_socket:send_marker_cot", [_marker_cot]];
|
"armatak" callExtension ["tcp_socket:cot:marker", [_marker_cot]];
|
||||||
|
|||||||
77
addons/main/functions/api/fn_send_uas_platform_cot.sqf
Normal file
77
addons/main/functions/api/fn_send_uas_platform_cot.sqf
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
params ["_drone"];
|
||||||
|
|
||||||
|
private _uuid = _drone call armatak_fnc_extract_uuid;
|
||||||
|
private _uavControl = UAVControl _drone;
|
||||||
|
private _controller = _uavControl param [0, objNull];
|
||||||
|
private _controller_uid = if (!isNull _controller) then { [_controller] call armatak_fnc_extract_uuid } else { _drone getVariable ["armatak_uas_controller_uid", _uuid] };
|
||||||
|
private _callsign = [_drone] call armatak_fnc_extract_marker_callsign;
|
||||||
|
private _video_url = [_drone] call armatak_fnc_extract_marker_video_url;
|
||||||
|
|
||||||
|
private _atak_role = "a-f-A-M-H-Q";
|
||||||
|
switch (side _drone) do {
|
||||||
|
case west: {
|
||||||
|
_atak_role = "a-f-A-M-H-Q";
|
||||||
|
};
|
||||||
|
case east: {
|
||||||
|
_atak_role = "a-h-A-M-H-Q";
|
||||||
|
};
|
||||||
|
case independent: {
|
||||||
|
_atak_role = "a-n-A-M-H-Q";
|
||||||
|
};
|
||||||
|
case civilian: {
|
||||||
|
_atak_role = "a-f-A-C";
|
||||||
|
};
|
||||||
|
default {
|
||||||
|
_atak_role = "a-f-A-M-H-Q";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
private _position = _drone call armatak_client_fnc_extractClientPosition;
|
||||||
|
private _lat = _position select 1;
|
||||||
|
private _lon = _position select 2;
|
||||||
|
private _hae = _position select 3;
|
||||||
|
private _course = _position select 5;
|
||||||
|
private _speed = _position select 6;
|
||||||
|
|
||||||
|
private _cameraData = [_drone] call armatak_fnc_extract_uas_camera_data;
|
||||||
|
private _azimuth = _cameraData select 0;
|
||||||
|
private _elevation = _cameraData select 1;
|
||||||
|
private _fov = _cameraData select 2;
|
||||||
|
private _range = _cameraData select 3;
|
||||||
|
private _vfov = _drone getVariable ["armatak_uas_vfov", _fov];
|
||||||
|
|
||||||
|
private _yaw = round (getDir _drone);
|
||||||
|
private _pitch = (vectorDir _drone) select 2;
|
||||||
|
private _roll = (vectorUp _drone) select 0;
|
||||||
|
private _isFlying = parseNumber (isEngineOn _drone);
|
||||||
|
private _hal = ((getPosATL _drone) select 2) max 0;
|
||||||
|
private _vehicleType = if (_video_url == "") then {
|
||||||
|
typeOf _drone
|
||||||
|
} else {
|
||||||
|
format ["%1|armatak_video_url=%2", typeOf _drone, _video_url]
|
||||||
|
};
|
||||||
|
|
||||||
|
private _payload = [
|
||||||
|
_uuid,
|
||||||
|
_atak_role,
|
||||||
|
_callsign,
|
||||||
|
_lat,
|
||||||
|
_lon,
|
||||||
|
_hae,
|
||||||
|
_course,
|
||||||
|
_speed,
|
||||||
|
_azimuth,
|
||||||
|
_elevation,
|
||||||
|
_fov,
|
||||||
|
_vfov,
|
||||||
|
_range,
|
||||||
|
_yaw,
|
||||||
|
_pitch,
|
||||||
|
_roll,
|
||||||
|
_hal,
|
||||||
|
_vehicleType,
|
||||||
|
_isFlying,
|
||||||
|
_controller_uid
|
||||||
|
];
|
||||||
|
|
||||||
|
"armatak" callExtension ["tcp_socket:cot:uas_platform", [_payload]];
|
||||||
22
addons/main/functions/api/fn_send_uas_sensor_cot.sqf
Normal file
22
addons/main/functions/api/fn_send_uas_sensor_cot.sqf
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
params ["_drone"];
|
||||||
|
|
||||||
|
private _video_url = [_drone] call armatak_fnc_extract_marker_video_url;
|
||||||
|
if (_video_url == "") exitWith {};
|
||||||
|
|
||||||
|
private _uuid = _drone call armatak_fnc_extract_uuid;
|
||||||
|
private _video_uid = _uuid + "-video";
|
||||||
|
private _sensor_uid = _uuid + "-sensor";
|
||||||
|
private _callsign = [_drone] call armatak_fnc_extract_marker_callsign;
|
||||||
|
|
||||||
|
private _position = _drone call armatak_client_fnc_extractClientPosition;
|
||||||
|
private _lat = _position select 1;
|
||||||
|
private _lon = _position select 2;
|
||||||
|
private _hae = _position select 3;
|
||||||
|
|
||||||
|
private _cameraData = [_drone] call armatak_fnc_extract_uas_camera_data;
|
||||||
|
private _azimuth = _cameraData select 0;
|
||||||
|
private _fov = _cameraData select 2;
|
||||||
|
private _range = _cameraData select 3;
|
||||||
|
|
||||||
|
private _payload = [_sensor_uid, _video_uid, _callsign, _lat, _lon, _hae, _azimuth, _fov, _range];
|
||||||
|
"armatak" callExtension ["tcp_socket:cot:uas_sensor", [_payload]];
|
||||||
20
addons/main/functions/api/fn_send_uas_video_cot.sqf
Normal file
20
addons/main/functions/api/fn_send_uas_video_cot.sqf
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
params ["_drone"];
|
||||||
|
|
||||||
|
private _video_url = [_drone] call armatak_fnc_extract_marker_video_url;
|
||||||
|
if (_video_url == "") exitWith {};
|
||||||
|
|
||||||
|
private _uuid = _drone call armatak_fnc_extract_uuid;
|
||||||
|
private _video_uid = _uuid + "-video";
|
||||||
|
private _callsign = [_drone] call armatak_fnc_extract_marker_callsign;
|
||||||
|
|
||||||
|
private _signature = format ["%1|%2|%3", _video_uid, _callsign, _video_url];
|
||||||
|
private _nextRefreshAt = _drone getVariable ["armatak_next_uas_video_refresh_at", 0];
|
||||||
|
private _lastSignature = _drone getVariable ["armatak_last_uas_video_signature", ""];
|
||||||
|
|
||||||
|
if (_signature == _lastSignature && {diag_tickTime < _nextRefreshAt}) exitWith {};
|
||||||
|
|
||||||
|
_drone setVariable ["armatak_last_uas_video_signature", _signature, false];
|
||||||
|
_drone setVariable ["armatak_next_uas_video_refresh_at", diag_tickTime + 300, false];
|
||||||
|
|
||||||
|
private _payload = [_video_uid, _callsign, _video_url];
|
||||||
|
"armatak" callExtension ["tcp_socket:cot:uas_video", [_payload]];
|
||||||
9
addons/main/functions/api/fn_set_uas_camera_override.sqf
Normal file
9
addons/main/functions/api/fn_set_uas_camera_override.sqf
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
params ["_drone", ["_cameraData", []]];
|
||||||
|
|
||||||
|
if (isNull _drone) exitWith {};
|
||||||
|
|
||||||
|
if ((_cameraData isEqualType []) && {(count _cameraData) >= 6}) then {
|
||||||
|
_drone setVariable ["armatak_uas_camera_data_override", _cameraData + [serverTime], false];
|
||||||
|
} else {
|
||||||
|
_drone setVariable ["armatak_uas_camera_data_override", nil, false];
|
||||||
|
};
|
||||||
@@ -1,9 +1,10 @@
|
|||||||
params["_unit"];
|
params["_unit"];
|
||||||
|
|
||||||
|
_group = group _unit;
|
||||||
_group_roles = ["Team Member", "Team Lead", "HQ", "Sniper", "Medic", "Forward Observer", "RTO", "K9"];
|
_group_roles = ["Team Member", "Team Lead", "HQ", "Sniper", "Medic", "Forward Observer", "RTO", "K9"];
|
||||||
_group_role = "Team Member";
|
_group_role = "Team Member";
|
||||||
|
|
||||||
if (["SpecialOperative", (configFile >> "CfgVehicles" >> typeOf _unit >> "role") call BIS_fnc_getCfgData, false] call BIS_fnc_inString) then {
|
if (["SpecialOperative", (configOf _unit >> "role") call BIS_fnc_getCfgData, false] call BIS_fnc_inString) then {
|
||||||
_group_role = _group_roles select 5;
|
_group_role = _group_roles select 5;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -19,7 +20,7 @@ if (((backpack _unit) isKindOf "TFAR_Bag_Base") or (["radio", typeOf _unit, fals
|
|||||||
_group_role = _group_roles select 6;
|
_group_role = _group_roles select 6;
|
||||||
};
|
};
|
||||||
|
|
||||||
if ((["sniper", typeOf _unit, false] call BIS_fnc_inString) or (["marksman", (configFile >> "CfgVehicles" >> typeOf _unit >> "role") call BIS_fnc_getCfgData, false] call BIS_fnc_inString) or (["sharpshooter", typeOf _unit, false] call BIS_fnc_inString)) then {
|
if ((["sniper", typeOf _unit, false] call BIS_fnc_inString) or (["marksman", (configOf _unit >> "role") call BIS_fnc_getCfgData, false] call BIS_fnc_inString) or (["sharpshooter", typeOf _unit, false] call BIS_fnc_inString)) then {
|
||||||
_group_role = _group_roles select 3;
|
_group_role = _group_roles select 3;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -34,7 +35,7 @@ if (["officer", typeOf _unit, false] call BIS_fnc_inString) then {
|
|||||||
_pre_defined_role = _unit getVariable "_atak_group_role";
|
_pre_defined_role = _unit getVariable "_atak_group_role";
|
||||||
|
|
||||||
if (!isNil "_pre_defined_role") then {
|
if (!isNil "_pre_defined_role") then {
|
||||||
_callsign = _pre_defined_callsign;
|
_callsign = _pre_defined_role;
|
||||||
};
|
};
|
||||||
|
|
||||||
_group_role
|
_group_role
|
||||||
|
|||||||
@@ -5,29 +5,45 @@
|
|||||||
params["_unit"];
|
params["_unit"];
|
||||||
|
|
||||||
private _callsign = "";
|
private _callsign = "";
|
||||||
|
private _displayName = localize (getText (configOf _unit >> "displayName"));
|
||||||
|
private _markerCallsignOverride = _unit getVariable ["armatak_attribute_marker_callsign", ""];
|
||||||
|
|
||||||
|
if (_markerCallsignOverride isNotEqualTo "") exitWith {
|
||||||
|
_markerCallsignOverride
|
||||||
|
};
|
||||||
|
|
||||||
|
if (_displayName isEqualTo "") then {
|
||||||
|
_displayName = typeOf _unit;
|
||||||
|
};
|
||||||
|
|
||||||
|
private _vehicleName = vehicleVarName _unit;
|
||||||
|
|
||||||
if ((([_unit] call BIS_fnc_objectType) select 0) == "Vehicle") then {
|
if ((([_unit] call BIS_fnc_objectType) select 0) == "Vehicle") then {
|
||||||
_callsign = getText (configFile >> "CfgVehicles" >> typeOf _unit >> "displayName");
|
_callsign = [_displayName, _vehicleName] select (_vehicleName isNotEqualTo "");
|
||||||
|
|
||||||
if (!isNull driver _unit) then {
|
if (!isNull driver _unit) then {
|
||||||
_callsign = getText (configFile >> "CfgVehicles" >> typeOf _unit >> "displayName") + " | " + ([name (driver _unit)] call armatak_fnc_shorten_name);
|
_callsign = _displayName + " | " + ([name (driver _unit)] call armatak_fnc_shorten_name);
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
if (unitIsUAV _unit) then {
|
if (unitIsUAV _unit) then {
|
||||||
_callsign = getText (configFile >> "CfgVehicles" >> typeOf _unit >> "displayName");
|
_callsign = [_displayName, _vehicleName] select (_vehicleName isNotEqualTo "");
|
||||||
|
|
||||||
|
private _uavControl = UAVControl _unit;
|
||||||
|
private _controller = _uavControl param [0, objNull];
|
||||||
|
if (!isNull _controller) then {
|
||||||
|
_callsign = _callsign + " | " + ([name _controller] call armatak_fnc_shorten_name);
|
||||||
|
};
|
||||||
|
|
||||||
if (isUAVConnected _unit) then {
|
if (isUAVConnected _unit) then {
|
||||||
_callsign = (_callsign) + "[ON]";
|
_callsign = _callsign + " [ON]";
|
||||||
} else {
|
} else {
|
||||||
_callsign = (_callsign) + "[OFF]";
|
_callsign = _callsign + " [OFF]";
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
armatak_attribute_marker_callsign = _unit getVariable "armatak_attribute_marker_callsign";
|
if (_callsign isEqualTo "") then {
|
||||||
|
_callsign = _displayName;
|
||||||
if (!isNil "armatak_attribute_marker_callsign" or armatak_attribute_marker_callsign != '') then {
|
|
||||||
_callsign = armatak_attribute_marker_callsign;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
_callsign
|
_callsign
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
// function name: armatak_fnc_extract_marker_video_url
|
||||||
|
// function author: Codex
|
||||||
|
// function description: Gets the marker video URL configured in 3DEN for a vehicle
|
||||||
|
|
||||||
|
params ["_unit"];
|
||||||
|
|
||||||
|
private _videoUrl = _unit getVariable ["armatak_attribute_marker_video_url", ""];
|
||||||
|
|
||||||
|
if (isNil "_videoUrl") exitWith {
|
||||||
|
""
|
||||||
|
};
|
||||||
|
|
||||||
|
_videoUrl
|
||||||
@@ -7,8 +7,9 @@ params["_unit"];
|
|||||||
private _affiliation = "f";
|
private _affiliation = "f";
|
||||||
private _type = "G";
|
private _type = "G";
|
||||||
private _role = "a-f-G-U-C-I";
|
private _role = "a-f-G-U-C-I";
|
||||||
|
private _side = _unit getVariable ["armatak_current_side", side _unit];
|
||||||
|
|
||||||
switch (str side _unit) do {
|
switch (str _side) do {
|
||||||
case "WEST": {
|
case "WEST": {
|
||||||
_affiliation = "f";
|
_affiliation = "f";
|
||||||
};
|
};
|
||||||
@@ -22,7 +23,7 @@ switch (str side _unit) do {
|
|||||||
_affiliation = "u";
|
_affiliation = "u";
|
||||||
};
|
};
|
||||||
default {
|
default {
|
||||||
_affiliation = "f";
|
_affiliation = "u";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -110,7 +111,6 @@ if ((typeOf (vehicle _unit) != typeOf _unit) or ((_unit_type select 0) == "Vehic
|
|||||||
|
|
||||||
_role = "a-" + _affiliation + "-" + _type;
|
_role = "a-" + _affiliation + "-" + _type;
|
||||||
|
|
||||||
|
|
||||||
armatak_attribute_marker_type = _unit getVariable "armatak_attribute_marker_type";
|
armatak_attribute_marker_type = _unit getVariable "armatak_attribute_marker_type";
|
||||||
|
|
||||||
if (!isNil "armatak_attribute_marker_type" or armatak_attribute_marker_type != '') then {
|
if (!isNil "armatak_attribute_marker_type" or armatak_attribute_marker_type != '') then {
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
params["_unit"];
|
||||||
|
|
||||||
|
_target = getSensorTargets (_unit);
|
||||||
|
|
||||||
|
{
|
||||||
|
private _targetUnit = _x select 0;
|
||||||
|
_position = _x select 1;
|
||||||
|
_status = _x select 2;
|
||||||
|
private _targetType = toLower (typeOf _targetUnit);
|
||||||
|
|
||||||
|
if ((_targetType find "lasertarget") < 0) then {
|
||||||
|
if (isNil {
|
||||||
|
_targetUnit getVariable "armatak_current_side"
|
||||||
|
}) then {
|
||||||
|
_targetUnit setVariable ["armatak_current_side", side _targetUnit];
|
||||||
|
};
|
||||||
|
|
||||||
|
if (_status != "destroyed" && !(_targetUnit in armatak_server_syncedUnits)) then {
|
||||||
|
_unit_position = _targetUnit call armatak_client_fnc_extractClientPosition;
|
||||||
|
|
||||||
|
_uuid = _targetUnit call armatak_fnc_extract_uuid;
|
||||||
|
_type = _targetUnit call armatak_fnc_extract_role;
|
||||||
|
_callsign = getText (configOf _targetUnit >> "displayName");
|
||||||
|
|
||||||
|
_marker_cot = [_uuid, _type, _unit_position select 1, _unit_position select 2, _unit_position select 3, _callsign, _unit_position select 5, _unit_position select 6];
|
||||||
|
|
||||||
|
"armatak" callExtension ["tcp_socket:cot:marker", [_marker_cot]];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
} forEach _target;
|
||||||
@@ -0,0 +1,143 @@
|
|||||||
|
params ["_drone", ["_cameraMode", "turret"]];
|
||||||
|
|
||||||
|
private _override = _drone getVariable ["armatak_uas_camera_data_override", []];
|
||||||
|
private _isLocalController = hasInterface && {!isNull player} && {(getConnectedUAV player) isEqualTo _drone};
|
||||||
|
|
||||||
|
if (!_isLocalController && {_override isEqualType []} && {(count _override) >= 7}) then {
|
||||||
|
private _updatedAt = _override param [6, -1000];
|
||||||
|
if ((time - _updatedAt) <= 5) exitWith {
|
||||||
|
private _overrideSpiAsl = _override param [4, []];
|
||||||
|
private _overrideSpiGeo = _override param [5, []];
|
||||||
|
_drone setVariable ["armatak_uas_spi_asl", _overrideSpiAsl, false];
|
||||||
|
_drone setVariable ["armatak_uas_spi_geo", _overrideSpiGeo, false];
|
||||||
|
_override select [0, 6]
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
private _defaultFov = _drone getVariable ["armatak_uas_fov", 60];
|
||||||
|
private _maxRange = _drone getVariable ["armatak_uas_max_range", 15000];
|
||||||
|
private _originASL = getPosASL _drone;
|
||||||
|
private _originAGL = ASLToAGL _originASL;
|
||||||
|
private _cameraDir = [];
|
||||||
|
private _spiASL = [];
|
||||||
|
private _slantRange = 0;
|
||||||
|
|
||||||
|
if (_cameraMode isNotEqualTo "fpv") then {
|
||||||
|
private _laserTarget = laserTarget _drone;
|
||||||
|
if (!isNull _laserTarget) then {
|
||||||
|
private _laserTargetWorld = getPosWorld _laserTarget;
|
||||||
|
private _laserTargetAslZ = (getPosASL _laserTarget) select 2;
|
||||||
|
_spiASL = [_laserTargetWorld select 0, _laserTargetWorld select 1, _laserTargetAslZ];
|
||||||
|
_cameraDir = _spiASL vectorDiff _originASL;
|
||||||
|
_slantRange = _originASL vectorDistance _spiASL;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
if (_cameraDir isEqualTo [] && {_cameraMode isNotEqualTo "fpv"}) then {
|
||||||
|
private _uavControl = UAVControl _drone;
|
||||||
|
private _controlledTurretPath = _uavControl param [1, []];
|
||||||
|
private _candidateTurrets = [];
|
||||||
|
|
||||||
|
if ((_controlledTurretPath isEqualType []) && {_controlledTurretPath isNotEqualTo []}) then {
|
||||||
|
_candidateTurrets pushBack _controlledTurretPath;
|
||||||
|
};
|
||||||
|
|
||||||
|
{
|
||||||
|
if !(_x in _candidateTurrets) then {
|
||||||
|
_candidateTurrets pushBack _x;
|
||||||
|
};
|
||||||
|
} forEach (allTurrets _drone);
|
||||||
|
|
||||||
|
{
|
||||||
|
private _turretWeapons = _drone weaponsTurret _x;
|
||||||
|
if (_turretWeapons isNotEqualTo []) exitWith {
|
||||||
|
private _weapon = _turretWeapons select 0;
|
||||||
|
private _weaponDirection = _drone weaponDirection _weapon;
|
||||||
|
if (_weaponDirection isNotEqualTo [0, 0, 0]) then {
|
||||||
|
_cameraDir = _weaponDirection;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
} forEach _candidateTurrets;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (_cameraDir isEqualTo []) then {
|
||||||
|
_cameraDir = vectorDirVisual _drone;
|
||||||
|
};
|
||||||
|
|
||||||
|
private _dirMagnitude = vectorMagnitude _cameraDir;
|
||||||
|
if (_dirMagnitude <= 0) then {
|
||||||
|
private _fallbackAzimuth = getDir _drone;
|
||||||
|
_cameraDir = [sin _fallbackAzimuth, cos _fallbackAzimuth, -1];
|
||||||
|
_dirMagnitude = vectorMagnitude _cameraDir;
|
||||||
|
};
|
||||||
|
|
||||||
|
_cameraDir = _cameraDir vectorMultiply (1 / _dirMagnitude);
|
||||||
|
|
||||||
|
private _dirX = _cameraDir select 0;
|
||||||
|
private _dirY = _cameraDir select 1;
|
||||||
|
private _dirZ = _cameraDir select 2;
|
||||||
|
private _horizontalMagnitude = sqrt ((_dirX * _dirX) + (_dirY * _dirY));
|
||||||
|
|
||||||
|
private _azimuth = (((_dirX atan2 _dirY) + 360) mod 360);
|
||||||
|
private _elevation = (_dirZ atan2 (_horizontalMagnitude max 0.001));
|
||||||
|
|
||||||
|
if (_spiASL isEqualTo []) then {
|
||||||
|
private _altitudeAGL = (_originAGL select 2) max 0.1;
|
||||||
|
private _probeASL = _originASL vectorAdd (_cameraDir vectorMultiply _maxRange);
|
||||||
|
|
||||||
|
if (_dirZ < -0.01 && {terrainIntersectASL [_originASL, _probeASL]}) then {
|
||||||
|
private _near = _originASL;
|
||||||
|
private _far = _probeASL;
|
||||||
|
|
||||||
|
for "_i" from 0 to 24 do {
|
||||||
|
private _mid = [
|
||||||
|
((_near select 0) + (_far select 0)) / 2,
|
||||||
|
((_near select 1) + (_far select 1)) / 2,
|
||||||
|
((_near select 2) + (_far select 2)) / 2
|
||||||
|
];
|
||||||
|
|
||||||
|
if (terrainIntersectASL [_originASL, _mid]) then {
|
||||||
|
_far = _mid;
|
||||||
|
} else {
|
||||||
|
_near = _mid;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
_spiASL = _far;
|
||||||
|
_slantRange = _originASL vectorDistance _spiASL;
|
||||||
|
} else {
|
||||||
|
private _verticalComponent = abs _dirZ;
|
||||||
|
|
||||||
|
if (_verticalComponent > 0.01) then {
|
||||||
|
_slantRange = (_altitudeAGL / _verticalComponent) min _maxRange;
|
||||||
|
} else {
|
||||||
|
_slantRange = _maxRange;
|
||||||
|
};
|
||||||
|
|
||||||
|
_slantRange = _slantRange max 1;
|
||||||
|
_spiASL = _originASL vectorAdd (_cameraDir vectorMultiply _slantRange);
|
||||||
|
_spiASL set [2, getTerrainHeightASL [_spiASL select 0, _spiASL select 1]];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
if (_slantRange <= 0) then {
|
||||||
|
_slantRange = (_originASL vectorDistance _spiASL) max 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
private _spiAgl = ASLToAGL _spiASL;
|
||||||
|
private _spiWorld = [_spiAgl select 0, _spiAgl select 1, (_spiAgl select 2) max 0];
|
||||||
|
private _spiGeo = _spiWorld call armatak_client_fnc_convertClientLocation;
|
||||||
|
|
||||||
|
_drone setVariable ["armatak_uas_spi_asl", _spiASL, false];
|
||||||
|
_drone setVariable ["armatak_uas_spi_geo", _spiGeo, false];
|
||||||
|
|
||||||
|
[
|
||||||
|
round _azimuth,
|
||||||
|
round _elevation,
|
||||||
|
round _defaultFov,
|
||||||
|
round (_slantRange max 1),
|
||||||
|
_spiASL,
|
||||||
|
_spiGeo
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
@@ -12,14 +12,18 @@ if (roleDescription _unit != "") then {
|
|||||||
_callsign = name _unit;
|
_callsign = name _unit;
|
||||||
|
|
||||||
if (_callsign == "Error: No unit") then {
|
if (_callsign == "Error: No unit") then {
|
||||||
_callsign = getText (configFile >> "CfgVehicles" >> typeOf _unit >> "displayName");
|
_callsign = getText (configOf _unit >> "displayName");
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
armatak_attribute_unit_callsign = _unit getVariable "armatak_attribute_unit_callsign";
|
if (side _unit == east) then {
|
||||||
|
_callsign = getText (configOf _unit >> "displayName");
|
||||||
|
};
|
||||||
|
|
||||||
if (!isNil "armatak_attribute_unit_callsign" or armatak_attribute_unit_callsign != '') then {
|
private _unitCallsignOverride = _unit getVariable ["armatak_attribute_unit_callsign", ""];
|
||||||
_callsign = armatak_attribute_unit_callsign;
|
|
||||||
|
if (_unitCallsignOverride isNotEqualTo "") then {
|
||||||
|
_callsign = _unitCallsignOverride;
|
||||||
};
|
};
|
||||||
|
|
||||||
_callsign
|
_callsign
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
params[_type, _message];
|
params["_type", "_message"];
|
||||||
|
|
||||||
"armatak" callExtension ["log", [_type, _message]]
|
"armatak" callExtension ["log", [_type, _message]]
|
||||||
|
|||||||
23
addons/main/functions/map/fn_convert_to_clafghan.sqf
Normal file
23
addons/main/functions/map/fn_convert_to_clafghan.sqf
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
params["_latitude", "_longitude", "_altitude"];
|
||||||
|
|
||||||
|
_playerPosition = [_latitude, _longitude, _altitude];
|
||||||
|
|
||||||
|
_playerLatitude = _playerPosition select 0;
|
||||||
|
_playerLongitude = _playerPosition select 1;
|
||||||
|
|
||||||
|
_playerMaxLatitude = 20480;
|
||||||
|
_playerMaxLongitude = 20480;
|
||||||
|
|
||||||
|
_MapMaxLongitude = 33.728772;
|
||||||
|
_MapMinLongitude = 33.542815;
|
||||||
|
|
||||||
|
_MapMaxLatitude = 63.169746;
|
||||||
|
_MapMinLatitude = 62.938820;
|
||||||
|
|
||||||
|
_LongitudeDifference = _MapMaxLongitude - _MapMinLongitude;
|
||||||
|
_LatitudeDifference = _MapMaxLatitude - _MapMinLatitude;
|
||||||
|
|
||||||
|
_RealLongitude = (_playerLongitude / _playerMaxLongitude) * _LongitudeDifference + _MapMinLongitude;
|
||||||
|
_RealLatitude = (_playerLatitude / _playerMaxLatitude) * _LatitudeDifference + _MapMinLatitude;
|
||||||
|
|
||||||
|
[_RealLongitude, _RealLatitude, _playerPosition select 2]
|
||||||
23
addons/main/functions/map/fn_convert_to_colombia.sqf
Normal file
23
addons/main/functions/map/fn_convert_to_colombia.sqf
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
params["_latitude", "_longitude", "_altitude"];
|
||||||
|
|
||||||
|
_playerPosition = [_latitude, _longitude, _altitude];
|
||||||
|
|
||||||
|
_playerLatitude = _playerPosition select 0;
|
||||||
|
_playerLongitude = _playerPosition select 1;
|
||||||
|
|
||||||
|
_playerMaxLatitude = 20480;
|
||||||
|
_playerMaxLongitude = 20480;
|
||||||
|
|
||||||
|
_MapMaxLatitude = -67.765153;
|
||||||
|
_MapMinLatitude = -68.223664;
|
||||||
|
|
||||||
|
_MapMaxLongitude = 10.593815;
|
||||||
|
_MapMinLongitude = 10.137466;
|
||||||
|
|
||||||
|
_LongitudeDifference = _MapMaxLongitude - _MapMinLongitude;
|
||||||
|
_LatitudeDifference = _MapMaxLatitude - _MapMinLatitude;
|
||||||
|
|
||||||
|
_RealLongitude = (_playerLongitude / _playerMaxLongitude) * _LongitudeDifference + _MapMinLongitude;
|
||||||
|
_RealLatitude = (_playerLatitude / _playerMaxLatitude) * _LatitudeDifference + _MapMinLatitude;
|
||||||
|
|
||||||
|
[_RealLongitude, _RealLatitude, _playerPosition select 2]
|
||||||
30
addons/main/functions/map/fn_convert_to_hellanmaa.sqf
Normal file
30
addons/main/functions/map/fn_convert_to_hellanmaa.sqf
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
params ["_longitudeInGame", "_latitudeInGame", "_altitude"];
|
||||||
|
|
||||||
|
private _mapWidth = 8192;
|
||||||
|
private _mapHeight = 8192;
|
||||||
|
|
||||||
|
// SW corner (used as origin)
|
||||||
|
private _SW_lat = 63.005389;
|
||||||
|
private _SW_lon = 22.638957;
|
||||||
|
|
||||||
|
// SE corner
|
||||||
|
private _SE_lat = 63.010092;
|
||||||
|
private _SE_lon = 22.800107;
|
||||||
|
|
||||||
|
// NW corner
|
||||||
|
private _NW_lat = 63.078713;
|
||||||
|
private _NW_lon = 22.628542;
|
||||||
|
|
||||||
|
private _edgeSE_lat = _SE_lat - _SW_lat;
|
||||||
|
private _edgeSE_lon = _SE_lon - _SW_lon;
|
||||||
|
|
||||||
|
private _edgeNW_lat = _NW_lat - _SW_lat;
|
||||||
|
private _edgeNW_lon = _NW_lon - _SW_lon;
|
||||||
|
|
||||||
|
private _fx = _longitudeInGame / _mapWidth;
|
||||||
|
private _fy = _latitudeInGame / _mapHeight;
|
||||||
|
|
||||||
|
private _realLat = _SW_lat + (_fx * _edgeSE_lat) + (_fy * _edgeNW_lat);
|
||||||
|
private _realLon = _SW_lon + (_fx * _edgeSE_lon) + (_fy * _edgeNW_lon);
|
||||||
|
|
||||||
|
[_realLat, _realLon, _altitude]
|
||||||
23
addons/main/functions/map/fn_convert_to_lawn.sqf
Normal file
23
addons/main/functions/map/fn_convert_to_lawn.sqf
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
params["_latitude", "_longitude", "_altitude"];
|
||||||
|
|
||||||
|
_playerPosition = [_latitude, _longitude, _altitude];
|
||||||
|
|
||||||
|
_playerLatitude = _playerPosition select 0;
|
||||||
|
_playerLongitude = _playerPosition select 1;
|
||||||
|
|
||||||
|
_playerMaxLongitude = 4992;
|
||||||
|
_playerMaxLatitude = 4992;
|
||||||
|
|
||||||
|
_MapMaxLongitude = -99.722665;
|
||||||
|
_MapMinLongitude = -99.775505;
|
||||||
|
|
||||||
|
_MapMaxLatitude = 32.159272;
|
||||||
|
_MapMinLatitude = 32.114011;
|
||||||
|
|
||||||
|
_LongitudeDifference = _MapMaxLongitude - _MapMinLongitude;
|
||||||
|
_LatitudeDifference = _MapMaxLatitude - _MapMinLatitude;
|
||||||
|
|
||||||
|
_RealLongitude = (_playerLongitude / _playerMaxLongitude) * _LongitudeDifference + _MapMinLongitude;
|
||||||
|
_RealLatitude = (_playerLatitude / _playerMaxLatitude) * _LatitudeDifference + _MapMinLatitude;
|
||||||
|
|
||||||
|
[_RealLongitude, _RealLatitude, _playerPosition select 2]
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
params ["_longitudeInGame", "_latitudeInGame", "_altitude"];
|
||||||
|
|
||||||
|
private _mapWidth = 11264;
|
||||||
|
private _mapHeight = 11264;
|
||||||
|
|
||||||
|
// SW corner (used as origin)
|
||||||
|
private _SW_lat = -51.736078;
|
||||||
|
private _SW_lon = -57.915032;
|
||||||
|
|
||||||
|
// SE corner
|
||||||
|
private _SE_lat = -51.736078;
|
||||||
|
private _SE_lon = -58.077879;
|
||||||
|
|
||||||
|
// NW corner
|
||||||
|
private _NW_lat = -51.634750;
|
||||||
|
private _NW_lon = -58.077879;
|
||||||
|
|
||||||
|
private _edgeSE_lat = _SE_lat - _SW_lat;
|
||||||
|
private _edgeSE_lon = _SE_lon - _SW_lon;
|
||||||
|
|
||||||
|
private _edgeNW_lat = _NW_lat - _SW_lat;
|
||||||
|
private _edgeNW_lon = _NW_lon - _SW_lon;
|
||||||
|
|
||||||
|
private _fx = _longitudeInGame / _mapWidth;
|
||||||
|
private _fy = _latitudeInGame / _mapHeight;
|
||||||
|
|
||||||
|
private _realLat = _SW_lat + (_fx * _edgeSE_lat) + (_fy * _edgeNW_lat);
|
||||||
|
private _realLon = _SW_lon + (_fx * _edgeSE_lon) + (_fy * _edgeNW_lon);
|
||||||
|
|
||||||
|
[_realLat, _realLon, _altitude]
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
params ["_longitudeInGame", "_latitudeInGame", "_altitude"];
|
||||||
|
|
||||||
|
private _mapWidth = 16384;
|
||||||
|
private _mapHeight = 16384;
|
||||||
|
|
||||||
|
// SW corner (used as origin)
|
||||||
|
private _SW_lat = -51.806546;
|
||||||
|
private _SW_lon = -57.939747;
|
||||||
|
|
||||||
|
// SE corner
|
||||||
|
private _SE_lat = -51.806546;
|
||||||
|
private _SE_lon = -57.701978;
|
||||||
|
|
||||||
|
// NW corner
|
||||||
|
private _NW_lat = -51.658913;
|
||||||
|
private _NW_lon = -57.939747;
|
||||||
|
|
||||||
|
private _edgeSE_lat = _SE_lat - _SW_lat;
|
||||||
|
private _edgeSE_lon = _SE_lon - _SW_lon;
|
||||||
|
|
||||||
|
private _edgeNW_lat = _NW_lat - _SW_lat;
|
||||||
|
private _edgeNW_lon = _NW_lon - _SW_lon;
|
||||||
|
|
||||||
|
private _fx = _longitudeInGame / _mapWidth;
|
||||||
|
private _fy = _latitudeInGame / _mapHeight;
|
||||||
|
|
||||||
|
private _realLat = _SW_lat + (_fx * _edgeSE_lat) + (_fy * _edgeNW_lat);
|
||||||
|
private _realLon = _SW_lon + (_fx * _edgeSE_lon) + (_fy * _edgeNW_lon);
|
||||||
|
|
||||||
|
[_realLat, _realLon, _altitude]
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
params ["_longitudeInGame", "_latitudeInGame", "_altitude"];
|
||||||
|
|
||||||
|
private _mapWidth = 10240;
|
||||||
|
private _mapHeight = 10240;
|
||||||
|
|
||||||
|
// SW corner (used as origin)
|
||||||
|
private _SW_lat = -51.863358;
|
||||||
|
private _SW_lon = -59.054585;
|
||||||
|
|
||||||
|
// SE corner
|
||||||
|
private _SE_lat = -51.863358;
|
||||||
|
private _SE_lon = -58.906155;
|
||||||
|
|
||||||
|
// NW corner
|
||||||
|
private _NW_lat = -51.771493;
|
||||||
|
private _NW_lon = -59.054585;
|
||||||
|
|
||||||
|
private _edgeSE_lat = _SE_lat - _SW_lat;
|
||||||
|
private _edgeSE_lon = _SE_lon - _SW_lon;
|
||||||
|
|
||||||
|
private _edgeNW_lat = _NW_lat - _SW_lat;
|
||||||
|
private _edgeNW_lon = _NW_lon - _SW_lon;
|
||||||
|
|
||||||
|
private _fx = _longitudeInGame / _mapWidth;
|
||||||
|
private _fy = _latitudeInGame / _mapHeight;
|
||||||
|
|
||||||
|
private _realLat = _SW_lat + (_fx * _edgeSE_lat) + (_fy * _edgeNW_lat);
|
||||||
|
private _realLon = _SW_lon + (_fx * _edgeSE_lon) + (_fy * _edgeNW_lon);
|
||||||
|
|
||||||
|
[_realLat, _realLon, _altitude]
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
params["_latitude", "_longitude", "_altitude"];
|
||||||
|
|
||||||
|
_playerPosition = [_latitude, _longitude, _altitude];
|
||||||
|
|
||||||
|
_playerLatitude = _playerPosition select 0;
|
||||||
|
_playerLongitude = _playerPosition select 1;
|
||||||
|
|
||||||
|
_playerMaxLongitude = 5120;
|
||||||
|
_playerMaxLatitude = 5120;
|
||||||
|
|
||||||
|
_MapMaxLatitude = -51.619725;
|
||||||
|
_MapMinLatitude = -51.664223;
|
||||||
|
|
||||||
|
_MapMaxLongitude = -58.394630;
|
||||||
|
_MapMinLongitude = -58.469580;
|
||||||
|
|
||||||
|
_LongitudeDifference = _MapMaxLongitude - _MapMinLongitude;
|
||||||
|
_LatitudeDifference = _MapMaxLatitude - _MapMinLatitude;
|
||||||
|
|
||||||
|
_RealLongitude = (_playerLongitude / _playerMaxLongitude) * _LongitudeDifference + _MapMinLongitude;
|
||||||
|
_RealLatitude = (_playerLatitude / _playerMaxLatitude) * _LatitudeDifference + _MapMinLatitude;
|
||||||
|
|
||||||
|
[_RealLongitude, _RealLatitude, _playerPosition select 2]
|
||||||
23
addons/main/functions/map/fn_convert_to_rut_mandol.sqf
Normal file
23
addons/main/functions/map/fn_convert_to_rut_mandol.sqf
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
params["_latitude", "_longitude", "_altitude"];
|
||||||
|
|
||||||
|
_playerPosition = [_latitude, _longitude, _altitude];
|
||||||
|
|
||||||
|
_playerLatitude = _playerPosition select 0;
|
||||||
|
_playerLongitude = _playerPosition select 1;
|
||||||
|
|
||||||
|
_playerMaxLatitude = 32768;
|
||||||
|
_playerMaxLongitude = 32768;
|
||||||
|
|
||||||
|
_MapMaxLongitude = 35.285485;
|
||||||
|
_MapMinLongitude = 34.927617;
|
||||||
|
|
||||||
|
_MapMaxLatitude = 70.445404;
|
||||||
|
_MapMinLatitude = 70.016783;
|
||||||
|
|
||||||
|
_LongitudeDifference = _MapMaxLongitude - _MapMinLongitude;
|
||||||
|
_LatitudeDifference = _MapMaxLatitude - _MapMinLatitude;
|
||||||
|
|
||||||
|
_RealLongitude = (_playerLongitude / _playerMaxLongitude) * _LongitudeDifference + _MapMinLongitude;
|
||||||
|
_RealLatitude = (_playerLatitude / _playerMaxLatitude) * _LatitudeDifference + _MapMinLatitude;
|
||||||
|
|
||||||
|
[_RealLongitude, _RealLatitude, _playerPosition select 2]
|
||||||
@@ -1,19 +1,19 @@
|
|||||||
params ["_longitudeInGame", "_latitudeInGame", "_altitude"];
|
params ["_longitudeInGame", "_latitudeInGame", "_altitude"];
|
||||||
|
|
||||||
private _mapWidth = 30720;
|
private _mapWidth = 8192;
|
||||||
private _mapHeight = 30720;
|
private _mapHeight = 8192;
|
||||||
|
|
||||||
// SW corner (used as origin)
|
// SW corner (used as origin)
|
||||||
private _SW_lat = 39.456910;
|
private _SW_lat = 39.458019;
|
||||||
private _SW_lon = 24.940792;
|
private _SW_lon = 24.939314;
|
||||||
|
|
||||||
// SE corner
|
// SE corner
|
||||||
private _SE_lat = 39.459151;
|
private _SE_lat = 39.458019;
|
||||||
private _SE_lon = 25.083440;
|
private _SE_lon = 25.081992;
|
||||||
|
|
||||||
// NW corner
|
// NW corner
|
||||||
private _NW_lat = 39.567714;
|
private _NW_lat = 39.568847;
|
||||||
private _NW_lon = 24.937866;
|
private _NW_lon = 24.939314;
|
||||||
|
|
||||||
private _edgeSE_lat = _SE_lat - _SW_lat;
|
private _edgeSE_lat = _SE_lat - _SW_lat;
|
||||||
private _edgeSE_lon = _SE_lon - _SW_lon;
|
private _edgeSE_lon = _SE_lon - _SW_lon;
|
||||||
|
|||||||
@@ -1,19 +1,19 @@
|
|||||||
params ["_longitudeInGame", "_latitudeInGame", "_altitude"];
|
params ["_longitudeInGame", "_latitudeInGame", "_altitude"];
|
||||||
|
|
||||||
private _mapWidth = 30720;
|
private _mapWidth = 15360;
|
||||||
private _mapHeight = 30720;
|
private _mapHeight = 15360;
|
||||||
|
|
||||||
// SW corner (used as origin)
|
// SW corner (used as origin)
|
||||||
private _SW_lat = -19.086825;
|
private _SW_lat = -19.086803;
|
||||||
private _SW_lon = 176.812772;
|
private _SW_lon = 176.812619;
|
||||||
|
|
||||||
// SE corner
|
// SE corner
|
||||||
private _SE_lat = -19.086825;
|
private _SE_lat = -19.086803;
|
||||||
private _SE_lon = 178.687920;
|
private _SE_lon = 178.704583;
|
||||||
|
|
||||||
// NW corner
|
// NW corner
|
||||||
private _NW_lat = -17.196898;
|
private _NW_lat = -17.196900;
|
||||||
private _NW_lon = 176.812622;
|
private _NW_lon = 176.812619;
|
||||||
|
|
||||||
private _edgeSE_lat = _SE_lat - _SW_lat;
|
private _edgeSE_lat = _SE_lat - _SW_lat;
|
||||||
private _edgeSE_lon = _SE_lon - _SW_lon;
|
private _edgeSE_lon = _SE_lon - _SW_lon;
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
params["_latitude", "_longitude", "_altitude"];
|
||||||
|
|
||||||
|
_playerPosition = [_latitude, _longitude, _altitude];
|
||||||
|
|
||||||
|
_playerLatitude = _playerPosition select 0;
|
||||||
|
_playerLongitude = _playerPosition select 1;
|
||||||
|
|
||||||
|
_playerMaxLongitude = 20480;
|
||||||
|
_playerMaxLatitude = 20480;
|
||||||
|
|
||||||
|
_MapMaxLongitude = 48.351216;
|
||||||
|
_MapMinLongitude = 48.097496;
|
||||||
|
|
||||||
|
_MapMaxLatitude = 38.345389;
|
||||||
|
_MapMinLatitude = 37.956754;
|
||||||
|
|
||||||
|
_LongitudeDifference = _MapMaxLongitude - _MapMinLongitude;
|
||||||
|
_LatitudeDifference = _MapMaxLatitude - _MapMinLatitude;
|
||||||
|
|
||||||
|
_RealLongitude = (_playerLongitude / _playerMaxLongitude) * _LongitudeDifference + _MapMinLongitude;
|
||||||
|
_RealLatitude = (_playerLatitude / _playerMaxLatitude) * _LatitudeDifference + _MapMinLatitude;
|
||||||
|
|
||||||
|
[_RealLongitude, _RealLatitude, _playerPosition select 2]
|
||||||
@@ -1,22 +1,11 @@
|
|||||||
class CfgVehicles {
|
class CfgVehicles {
|
||||||
class Logic;
|
class Logic;
|
||||||
class Module_F : Logic
|
class Module_F : Logic {
|
||||||
{
|
class AttributesBase {
|
||||||
class AttributesBase
|
|
||||||
{
|
|
||||||
class Default;
|
|
||||||
class Edit;
|
class Edit;
|
||||||
class Combo;
|
|
||||||
class Checkbox;
|
|
||||||
class CheckboxNumber;
|
|
||||||
class ModuleDescription;
|
class ModuleDescription;
|
||||||
class Units;
|
|
||||||
};
|
|
||||||
|
|
||||||
class ModuleDescription
|
|
||||||
{
|
|
||||||
class AnyBrain;
|
|
||||||
};
|
};
|
||||||
|
class ModuleDescription;
|
||||||
};
|
};
|
||||||
|
|
||||||
class GVAR(moduleBase): Module_F {
|
class GVAR(moduleBase): Module_F {
|
||||||
@@ -30,13 +19,10 @@ class CfgVehicles {
|
|||||||
scopeCurator = 2;
|
scopeCurator = 2;
|
||||||
};
|
};
|
||||||
|
|
||||||
class GVAR(coreModule): GVAR(moduleBase) {
|
class GVAR(connectionModuleBase): GVAR(moduleBase) {
|
||||||
scope = 2;
|
|
||||||
scopeCurator = 0;
|
scopeCurator = 0;
|
||||||
displayname = "CoT Router";
|
|
||||||
icon = "\a3\Modules_F_Curator\Data\iconRadio_ca.paa";
|
icon = "\a3\Modules_F_Curator\Data\iconRadio_ca.paa";
|
||||||
category = QEGVAR(main,moduleCategory);
|
category = QEGVAR(main,moduleCategory);
|
||||||
function = QFUNC(3denCoreModuleConfig);
|
|
||||||
functionPriority = 1;
|
functionPriority = 1;
|
||||||
isGlobal = 0;
|
isGlobal = 0;
|
||||||
isTriggerActivated = 1;
|
isTriggerActivated = 1;
|
||||||
@@ -47,19 +33,25 @@ class CfgVehicles {
|
|||||||
canSetArea = 0;
|
canSetArea = 0;
|
||||||
canSetAreaShape = 0;
|
canSetAreaShape = 0;
|
||||||
canSetAreaHeight = 0;
|
canSetAreaHeight = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
class GVAR(tcpModule): GVAR(connectionModuleBase) {
|
||||||
|
scope = 2;
|
||||||
|
displayName = "CoT Router (TCP)";
|
||||||
|
function = QFUNC(3denTcpModuleConfig);
|
||||||
|
|
||||||
class Attributes: AttributesBase {
|
class Attributes: AttributesBase {
|
||||||
class GVAR(moduleInstanceAddress): Edit {
|
class GVAR(moduleInstanceAddress): Edit {
|
||||||
property = QGVAR(moduleInstanceAddress);
|
property = QGVAR(moduleInstanceAddress);
|
||||||
displayname = "TAK Server Address";
|
displayName = "TAK Server Address";
|
||||||
tooltip = "TAK Server Instance Address";
|
tooltip = "Hostname or IP address for the TAK or IronTAK server.";
|
||||||
typeName = "STRING";
|
typeName = "STRING";
|
||||||
defaultValue = "localhost";
|
defaultValue = "'localhost'";
|
||||||
};
|
};
|
||||||
class GVAR(moduleInstancePort): Edit {
|
class GVAR(moduleInstancePort): Edit {
|
||||||
property = QGVAR(moduleInstancePort);
|
property = QGVAR(moduleInstancePort);
|
||||||
displayname = "TAK Server TCP Port";
|
displayName = "TAK Server TCP Port";
|
||||||
tooltip = "TAK Server instance Port for TCP connection";
|
tooltip = "Port for the unauthenticated TCP socket.";
|
||||||
typeName = "NUMBER";
|
typeName = "NUMBER";
|
||||||
defaultValue = "8088";
|
defaultValue = "8088";
|
||||||
};
|
};
|
||||||
@@ -67,17 +59,68 @@ class CfgVehicles {
|
|||||||
};
|
};
|
||||||
|
|
||||||
class ModuleDescription: ModuleDescription {
|
class ModuleDescription: ModuleDescription {
|
||||||
description = "Generate the initial ARMATAK configuration, syncronizing all players to the TAK server instance";
|
description = "Connect ArmaTAK to a TAK server over plain TCP.";
|
||||||
sync[] = {"LocationArea_F"};
|
sync[] = {"LocationArea_F"};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
class GVAR(coreModuleCurator): GVAR(coreModule) {
|
class GVAR(enrollModule): GVAR(connectionModuleBase) {
|
||||||
|
scope = 2;
|
||||||
|
displayName = "CoT Router (Authenticated)";
|
||||||
|
function = QFUNC(3denEnrollModuleConfig);
|
||||||
|
|
||||||
|
class Attributes: AttributesBase {
|
||||||
|
class GVAR(moduleInstanceAddress): Edit {
|
||||||
|
property = QGVAR(moduleInstanceAddress);
|
||||||
|
displayname = "TAK Server Address";
|
||||||
|
tooltip = "Hostname or IP address used for enrollment and the final TLS connection.";
|
||||||
|
typeName = "STRING";
|
||||||
|
defaultValue = "'localhost'";
|
||||||
|
};
|
||||||
|
class GVAR(moduleEnrollmentPort): Edit {
|
||||||
|
property = QGVAR(moduleEnrollmentPort);
|
||||||
|
displayName = "Enrollment HTTPS Port";
|
||||||
|
tooltip = "Port used for GET /Marti/api/tls/config and POST /Marti/api/tls/signClient/v2.";
|
||||||
|
typeName = "NUMBER";
|
||||||
|
defaultValue = "8446";
|
||||||
|
};
|
||||||
|
class GVAR(moduleEnrollmentUsername): Edit {
|
||||||
|
property = QGVAR(moduleEnrollmentUsername);
|
||||||
|
displayName = "Enrollment Username";
|
||||||
|
tooltip = "Username used in Basic Auth for client certificate enrollment.";
|
||||||
|
typeName = "STRING";
|
||||||
|
defaultValue = "''";
|
||||||
|
};
|
||||||
|
class GVAR(moduleEnrollmentPassword): Edit {
|
||||||
|
property = QGVAR(moduleEnrollmentPassword);
|
||||||
|
displayName = "Enrollment Password";
|
||||||
|
tooltip = "Password used in Basic Auth for client certificate enrollment.";
|
||||||
|
typeName = "STRING";
|
||||||
|
defaultValue = "''";
|
||||||
|
};
|
||||||
|
class ModuleDescription: ModuleDescription {};
|
||||||
|
};
|
||||||
|
|
||||||
|
class ModuleDescription: ModuleDescription {
|
||||||
|
description = "Enroll a client certificate and connect ArmaTAK over mTLS.";
|
||||||
|
sync[] = {"LocationArea_F"};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
class GVAR(tcpModuleCurator): GVAR(tcpModule) {
|
||||||
scope = 1;
|
scope = 1;
|
||||||
scopeCurator = 2;
|
scopeCurator = 2;
|
||||||
function = "";
|
function = "";
|
||||||
displayName = "CoT Router (Zeus)";
|
displayName = "CoT Router (TCP, Zeus)";
|
||||||
curatorInfoType = "armatak_zeus_core_module_dialog";
|
curatorInfoType = "armatak_zeus_tcp_module_dialog";
|
||||||
|
};
|
||||||
|
|
||||||
|
class GVAR(enrollModuleCurator): GVAR(enrollModule) {
|
||||||
|
scope = 1;
|
||||||
|
scopeCurator = 2;
|
||||||
|
function = "";
|
||||||
|
displayName = "CoT Router (Authenticated, Zeus)";
|
||||||
|
curatorInfoType = "armatak_zeus_enroll_module_dialog";
|
||||||
};
|
};
|
||||||
|
|
||||||
class GVAR(markEntity): GVAR(moduleBase) {
|
class GVAR(markEntity): GVAR(moduleBase) {
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
PREP(3denCoreModuleConfig);
|
PREP(3denEnrollModuleConfig);
|
||||||
|
PREP(3denTcpModuleConfig);
|
||||||
PREP(routerEntityAdd);
|
PREP(routerEntityAdd);
|
||||||
PREP(routerEntityRemove);
|
PREP(routerEntityRemove);
|
||||||
PREP(ZeusCoreModuleConfig);
|
PREP(startCotRouter);
|
||||||
|
PREP(ZeusEnrollModuleConfig);
|
||||||
|
PREP(ZeusTcpModuleConfig);
|
||||||
|
|||||||
@@ -4,8 +4,10 @@ class CfgPatches {
|
|||||||
class ADDON {
|
class ADDON {
|
||||||
name = COMPONENT_NAME;
|
name = COMPONENT_NAME;
|
||||||
units[] = {
|
units[] = {
|
||||||
QGVAR(coreModule),
|
QGVAR(tcpModule),
|
||||||
QGVAR(coreModuleCurator),
|
QGVAR(tcpModuleCurator),
|
||||||
|
QGVAR(enrollModule),
|
||||||
|
QGVAR(enrollModuleCurator),
|
||||||
QGVAR(markEntity)
|
QGVAR(markEntity)
|
||||||
};
|
};
|
||||||
weapons[] = {};
|
weapons[] = {};
|
||||||
|
|||||||
@@ -3,69 +3,172 @@ class RscBackground;
|
|||||||
class RscButton;
|
class RscButton;
|
||||||
class RscEdit;
|
class RscEdit;
|
||||||
|
|
||||||
class armatak_zeus_core_module_dialog {
|
class armatak_zeus_tcp_module_dialog {
|
||||||
idd = 999991;
|
idd = 999991;
|
||||||
movingEnable = 0;
|
movingEnable = 0;
|
||||||
class ControlsBackground {
|
class ControlsBackground {
|
||||||
class armatak_gui_module_zeus_core_dialog_main_frame: RscBackground {
|
class main_frame: RscBackground {
|
||||||
idc = 1800;
|
idc = 1800;
|
||||||
x = "0.386562 * safezoneW + safezoneX";
|
x = "0.386562 * safezoneW + safezoneX";
|
||||||
y = "0.401 * safezoneH + safezoneY";
|
y = "0.29 * safezoneH + safezoneY";
|
||||||
w = "0.216563 * safezoneW";
|
w = "0.216563 * safezoneW";
|
||||||
h = "0.242 * safezoneH";
|
h = "0.32 * safezoneH";
|
||||||
colorBackground[]={0,0,0,0.45};
|
colorBackground[] = {0,0,0,0.45};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
class Controls {
|
class Controls {
|
||||||
class armatak_gui_module_zeus_core_dialog_address_edit: RscEdit {
|
class address_text: RscText {
|
||||||
idc = 14000;
|
|
||||||
text = "localhost";
|
|
||||||
x = "0.391719 * safezoneW + safezoneX";
|
|
||||||
y = "0.445 * safezoneH + safezoneY";
|
|
||||||
w = "0.20625 * safezoneW";
|
|
||||||
h = "0.044 * safezoneH";
|
|
||||||
colorBackground[]={0,0,0,0.5};
|
|
||||||
};
|
|
||||||
class armatak_gui_module_zeus_core_dialog_address_port_edit: RscEdit {
|
|
||||||
idc = 14001;
|
|
||||||
text = "8088";
|
|
||||||
x = "0.391719 * safezoneW + safezoneX";
|
|
||||||
y = "0.522 * safezoneH + safezoneY";
|
|
||||||
w = "0.20625 * safezoneW";
|
|
||||||
h = "0.044 * safezoneH";
|
|
||||||
colorBackground[]={0,0,0,0.5};
|
|
||||||
};
|
|
||||||
class armatak_gui_module_zeus_core_dialog_address_text: RscText {
|
|
||||||
idc = 1000;
|
idc = 1000;
|
||||||
text = "TAK Server Address";
|
text = "TAK Server Address";
|
||||||
x = "0.391719 * safezoneW + safezoneX";
|
x = "0.391719 * safezoneW + safezoneX";
|
||||||
y = "0.412 * safezoneH + safezoneY";
|
y = "0.332 * safezoneH + safezoneY";
|
||||||
w = "0.20625 * safezoneW";
|
w = "0.20625 * safezoneW";
|
||||||
h = "0.033 * safezoneH";
|
h = "0.033 * safezoneH";
|
||||||
};
|
};
|
||||||
class armatak_gui_module_zeus_core_dialog_address_port_text: RscText {
|
class address_edit: RscEdit {
|
||||||
|
idc = 14000;
|
||||||
|
text = "localhost";
|
||||||
|
x = "0.391719 * safezoneW + safezoneX";
|
||||||
|
y = "0.365 * safezoneH + safezoneY";
|
||||||
|
w = "0.20625 * safezoneW";
|
||||||
|
h = "0.044 * safezoneH";
|
||||||
|
colorBackground[] = {0,0,0,0.5};
|
||||||
|
};
|
||||||
|
class port_text: RscText {
|
||||||
idc = 1001;
|
idc = 1001;
|
||||||
text = "TAK Server Port";
|
text = "TAK Server Port";
|
||||||
x = "0.391719 * safezoneW + safezoneX";
|
x = "0.391719 * safezoneW + safezoneX";
|
||||||
y = "0.489 * safezoneH + safezoneY";
|
y = "0.425 * safezoneH + safezoneY";
|
||||||
w = "0.20625 * safezoneW";
|
w = "0.20625 * safezoneW";
|
||||||
h = "0.033 * safezoneH";
|
h = "0.033 * safezoneH";
|
||||||
};
|
};
|
||||||
class armatak_gui_module_zeus_core_dialog_address_button_cancel: RscButton {
|
class port_edit: RscEdit {
|
||||||
|
idc = 14001;
|
||||||
|
text = "8088";
|
||||||
|
x = "0.391719 * safezoneW + safezoneX";
|
||||||
|
y = "0.458 * safezoneH + safezoneY";
|
||||||
|
w = "0.20625 * safezoneW";
|
||||||
|
h = "0.044 * safezoneH";
|
||||||
|
colorBackground[] = {0,0,0,0.5};
|
||||||
|
};
|
||||||
|
class button_cancel: RscButton {
|
||||||
idc = 1601;
|
idc = 1601;
|
||||||
text = "Cancel";
|
text = "Cancel";
|
||||||
action = "closeDialog 2;";
|
action = "closeDialog 2;";
|
||||||
x = "0.551563 * safezoneW + safezoneX";
|
x = "0.551563 * safezoneW + safezoneX";
|
||||||
y = "0.577 * safezoneH + safezoneY";
|
y = "0.535 * safezoneH + safezoneY";
|
||||||
w = "0.0464063 * safezoneW";
|
w = "0.0464063 * safezoneW";
|
||||||
h = "0.055 * safezoneH";
|
h = "0.055 * safezoneH";
|
||||||
};
|
};
|
||||||
class armatak_gui_module_zeus_core_dialog_address_button_ok: RscButton {
|
class button_ok: RscButton {
|
||||||
idc = 1600;
|
idc = 1600;
|
||||||
text = "Ok";
|
text = "Ok";
|
||||||
action = QUOTE(call FUNC(zeusCoreModuleConfig));
|
action = QUOTE(call FUNC(ZeusTcpModuleConfig));
|
||||||
x = "0.5 * safezoneW + safezoneX";
|
x = "0.5 * safezoneW + safezoneX";
|
||||||
y = "0.577 * safezoneH + safezoneY";
|
y = "0.535 * safezoneH + safezoneY";
|
||||||
|
w = "0.0464063 * safezoneW";
|
||||||
|
h = "0.055 * safezoneH";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
class armatak_zeus_enroll_module_dialog {
|
||||||
|
idd = 999992;
|
||||||
|
movingEnable = 0;
|
||||||
|
class ControlsBackground {
|
||||||
|
class main_frame: RscBackground {
|
||||||
|
idc = 1810;
|
||||||
|
x = "0.386562 * safezoneW + safezoneX";
|
||||||
|
y = "0.2 * safezoneH + safezoneY";
|
||||||
|
w = "0.216563 * safezoneW";
|
||||||
|
h = "0.52 * safezoneH";
|
||||||
|
colorBackground[] = {0,0,0,0.45};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
class Controls {
|
||||||
|
class address_text: RscText {
|
||||||
|
idc = 1010;
|
||||||
|
text = "TAK Server Address";
|
||||||
|
x = "0.391719 * safezoneW + safezoneX";
|
||||||
|
y = "0.242 * safezoneH + safezoneY";
|
||||||
|
w = "0.20625 * safezoneW";
|
||||||
|
h = "0.033 * safezoneH";
|
||||||
|
};
|
||||||
|
class address_edit: RscEdit {
|
||||||
|
idc = 14100;
|
||||||
|
text = "localhost";
|
||||||
|
x = "0.391719 * safezoneW + safezoneX";
|
||||||
|
y = "0.275 * safezoneH + safezoneY";
|
||||||
|
w = "0.20625 * safezoneW";
|
||||||
|
h = "0.044 * safezoneH";
|
||||||
|
colorBackground[] = {0,0,0,0.5};
|
||||||
|
};
|
||||||
|
class enroll_port_text: RscText {
|
||||||
|
idc = 1011;
|
||||||
|
text = "Enrollment HTTPS Port";
|
||||||
|
x = "0.391719 * safezoneW + safezoneX";
|
||||||
|
y = "0.335 * safezoneH + safezoneY";
|
||||||
|
w = "0.20625 * safezoneW";
|
||||||
|
h = "0.033 * safezoneH";
|
||||||
|
};
|
||||||
|
class enroll_port_edit: RscEdit {
|
||||||
|
idc = 14101;
|
||||||
|
text = "8446";
|
||||||
|
x = "0.391719 * safezoneW + safezoneX";
|
||||||
|
y = "0.368 * safezoneH + safezoneY";
|
||||||
|
w = "0.20625 * safezoneW";
|
||||||
|
h = "0.044 * safezoneH";
|
||||||
|
colorBackground[] = {0,0,0,0.5};
|
||||||
|
};
|
||||||
|
class username_text: RscText {
|
||||||
|
idc = 1012;
|
||||||
|
text = "Enrollment Username";
|
||||||
|
x = "0.391719 * safezoneW + safezoneX";
|
||||||
|
y = "0.428 * safezoneH + safezoneY";
|
||||||
|
w = "0.20625 * safezoneW";
|
||||||
|
h = "0.033 * safezoneH";
|
||||||
|
};
|
||||||
|
class username_edit: RscEdit {
|
||||||
|
idc = 14102;
|
||||||
|
text = "";
|
||||||
|
x = "0.391719 * safezoneW + safezoneX";
|
||||||
|
y = "0.461 * safezoneH + safezoneY";
|
||||||
|
w = "0.20625 * safezoneW";
|
||||||
|
h = "0.044 * safezoneH";
|
||||||
|
colorBackground[] = {0,0,0,0.5};
|
||||||
|
};
|
||||||
|
class password_text: RscText {
|
||||||
|
idc = 1013;
|
||||||
|
text = "Enrollment Password";
|
||||||
|
x = "0.391719 * safezoneW + safezoneX";
|
||||||
|
y = "0.521 * safezoneH + safezoneY";
|
||||||
|
w = "0.20625 * safezoneW";
|
||||||
|
h = "0.033 * safezoneH";
|
||||||
|
};
|
||||||
|
class password_edit: RscEdit {
|
||||||
|
idc = 14103;
|
||||||
|
text = "";
|
||||||
|
x = "0.391719 * safezoneW + safezoneX";
|
||||||
|
y = "0.554 * safezoneH + safezoneY";
|
||||||
|
w = "0.20625 * safezoneW";
|
||||||
|
h = "0.044 * safezoneH";
|
||||||
|
colorBackground[] = {0,0,0,0.5};
|
||||||
|
};
|
||||||
|
class button_cancel: RscButton {
|
||||||
|
idc = 1611;
|
||||||
|
text = "Cancel";
|
||||||
|
action = "closeDialog 2;";
|
||||||
|
x = "0.551563 * safezoneW + safezoneX";
|
||||||
|
y = "0.645 * safezoneH + safezoneY";
|
||||||
|
w = "0.0464063 * safezoneW";
|
||||||
|
h = "0.055 * safezoneH";
|
||||||
|
};
|
||||||
|
class button_ok: RscButton {
|
||||||
|
idc = 1610;
|
||||||
|
text = "Ok";
|
||||||
|
action = QUOTE(call FUNC(ZeusEnrollModuleConfig));
|
||||||
|
x = "0.5 * safezoneW + safezoneX";
|
||||||
|
y = "0.645 * safezoneH + safezoneY";
|
||||||
w = "0.0464063 * safezoneW";
|
w = "0.0464063 * safezoneW";
|
||||||
h = "0.055 * safezoneH";
|
h = "0.055 * safezoneH";
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,57 +0,0 @@
|
|||||||
#include "..\script_component.hpp"
|
|
||||||
|
|
||||||
params [
|
|
||||||
["_logic", objNull, [objNull]],
|
|
||||||
["_units", [], [[]]],
|
|
||||||
["_activated", true, [true]]
|
|
||||||
];
|
|
||||||
|
|
||||||
if (isServer) exitWith {
|
|
||||||
["Connecting to TCP Socket", "success", "TCP Socket"] call EFUNC(main,notify);
|
|
||||||
|
|
||||||
_tak_server_instance_address = _logic getVariable QGVAR(moduleInstanceAddress);
|
|
||||||
_tak_server_instance_port = _logic getVariable QGVAR(moduleInstancePort);
|
|
||||||
|
|
||||||
_tak_server_fulladdress = _tak_server_instance_address + ":" + (str _tak_server_instance_port);
|
|
||||||
|
|
||||||
missionNamespace setVariable ["armatak_server_instance", _tak_server_fulladdress];
|
|
||||||
missionNamespace setVariable ["armatak_tcp_socket_is_running", true];
|
|
||||||
|
|
||||||
"armatak" callExtension ["tcp_socket:start", [_tak_server_fulladdress]];
|
|
||||||
|
|
||||||
_syncUnits = synchronizedObjects _logic;
|
|
||||||
|
|
||||||
missionNamespace setVariable ["armatak_marked_units", _syncUnits];
|
|
||||||
|
|
||||||
GVAR(syncedUnits) = missionNamespace getVariable "armatak_marked_units";
|
|
||||||
|
|
||||||
[{
|
|
||||||
GVAR(syncedUnits) = missionNamespace getVariable "armatak_marked_units";
|
|
||||||
|
|
||||||
{
|
|
||||||
_objectType = _x call BIS_fnc_objectType;
|
|
||||||
switch (true) do {
|
|
||||||
case ((_objectType select 0) == "Soldier"): {
|
|
||||||
_callsign = [_x] call armatak_fnc_extract_unit_callsign;
|
|
||||||
_group_name = [group _x] call armatak_fnc_extract_group_color;
|
|
||||||
_group_role = [_x] call armatak_fnc_extract_group_role;
|
|
||||||
|
|
||||||
[_x, _callsign, _group_name, _group_role] call armatak_fnc_send_eud_cot;
|
|
||||||
[_x] call armatak_fnc_send_digital_pointer_cot;
|
|
||||||
};
|
|
||||||
case ((_objectType select 0) == "Vehicle"): {
|
|
||||||
_atak_type = [_x] call armatak_fnc_extract_role;
|
|
||||||
_callsign = [_x] call armatak_fnc_extract_marker_callsign;
|
|
||||||
|
|
||||||
[_x, _atak_type, _callsign] call armatak_fnc_send_marker_cot;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
if (unitIsUAV _x) then {
|
|
||||||
[_x] call armatak_fnc_send_drone_cot;
|
|
||||||
[_x] call armatak_fnc_send_digital_pointer_cot;
|
|
||||||
};
|
|
||||||
} forEach GVAR(syncedUnits);
|
|
||||||
}, 2, []] call CBA_fnc_addPerFrameHandler;
|
|
||||||
};
|
|
||||||
|
|
||||||
true;
|
|
||||||
37
addons/server/functions/fnc_3denEnrollModuleConfig.sqf
Normal file
37
addons/server/functions/fnc_3denEnrollModuleConfig.sqf
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
#include "..\script_component.hpp"
|
||||||
|
|
||||||
|
params [
|
||||||
|
["_logic", objNull, [objNull]],
|
||||||
|
["_units", [], [[]]],
|
||||||
|
["_activated", true, [true]]
|
||||||
|
];
|
||||||
|
|
||||||
|
if (isServer) exitWith {
|
||||||
|
if (missionNamespace getVariable ["armatak_tcp_socket_is_running", false]) exitWith {
|
||||||
|
["Socket was called twice", "error", "TCP Socket"] call EFUNC(main,notify);
|
||||||
|
};
|
||||||
|
|
||||||
|
["Connecting to authenticated TAK socket", "success", "TCP Socket"] call EFUNC(main,notify);
|
||||||
|
|
||||||
|
_tak_server_instance_address = _logic getVariable [QGVAR(moduleInstanceAddress), "localhost"];
|
||||||
|
_tak_server_enrollment_port = _logic getVariable [QGVAR(moduleEnrollmentPort), 8446];
|
||||||
|
_tak_server_enrollment_username = _logic getVariable [QGVAR(moduleEnrollmentUsername), ""];
|
||||||
|
_tak_server_enrollment_password = _logic getVariable [QGVAR(moduleEnrollmentPassword), ""];
|
||||||
|
|
||||||
|
"armatak" callExtension [
|
||||||
|
"tcp_socket:start_enroll_mtls",
|
||||||
|
[
|
||||||
|
_tak_server_instance_address,
|
||||||
|
_tak_server_instance_address,
|
||||||
|
str _tak_server_enrollment_port,
|
||||||
|
_tak_server_enrollment_username,
|
||||||
|
_tak_server_enrollment_password,
|
||||||
|
""
|
||||||
|
]
|
||||||
|
];
|
||||||
|
|
||||||
|
missionNamespace setVariable ["armatak_server_syncedUnits", synchronizedObjects _logic];
|
||||||
|
_tak_server_instance_address call FUNC(startCotRouter);
|
||||||
|
};
|
||||||
|
|
||||||
|
true
|
||||||
26
addons/server/functions/fnc_3denTcpModuleConfig.sqf
Normal file
26
addons/server/functions/fnc_3denTcpModuleConfig.sqf
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
#include "..\script_component.hpp"
|
||||||
|
|
||||||
|
params [
|
||||||
|
["_logic", objNull, [objNull]],
|
||||||
|
["_units", [], [[]]],
|
||||||
|
["_activated", true, [true]]
|
||||||
|
];
|
||||||
|
|
||||||
|
if (isServer) exitWith {
|
||||||
|
if (missionNamespace getVariable ["armatak_tcp_socket_is_running", false]) exitWith {
|
||||||
|
["Socket was called twice", "error", "TCP Socket"] call EFUNC(main,notify);
|
||||||
|
};
|
||||||
|
|
||||||
|
["Connecting to TCP Socket", "success", "TCP Socket"] call EFUNC(main,notify);
|
||||||
|
|
||||||
|
_tak_server_instance_address = _logic getVariable [QGVAR(moduleInstanceAddress), "localhost"];
|
||||||
|
_tak_server_instance_port = _logic getVariable [QGVAR(moduleInstancePort), 8088];
|
||||||
|
_tak_server_fulladdress = _tak_server_instance_address + ":" + (str _tak_server_instance_port);
|
||||||
|
|
||||||
|
"armatak" callExtension ["tcp_socket:start", [_tak_server_fulladdress]];
|
||||||
|
|
||||||
|
missionNamespace setVariable ["armatak_server_syncedUnits", synchronizedObjects _logic];
|
||||||
|
_tak_server_fulladdress call FUNC(startCotRouter);
|
||||||
|
};
|
||||||
|
|
||||||
|
true
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
#include "..\script_component.hpp"
|
|
||||||
|
|
||||||
params ["_logic"];
|
|
||||||
|
|
||||||
_socket_is_running = missionNamespace getVariable ["armatak_tcp_socket_is_running", false];
|
|
||||||
|
|
||||||
if (_socket_is_running) exitWith {
|
|
||||||
["Socket was called twice", "error", "TCP Socket"] call EFUNC(main,notify);
|
|
||||||
closeDialog 1;
|
|
||||||
};
|
|
||||||
|
|
||||||
disableSerialization;
|
|
||||||
|
|
||||||
["Connecting to TCP Socket", "success", "TCP Socket"] call EFUNC(main,notify);
|
|
||||||
|
|
||||||
_tak_server_instance_address = ctrlText 14000;
|
|
||||||
_tak_server_instance_port = ctrlText 14001;
|
|
||||||
|
|
||||||
_tak_server_fulladdress = ((_tak_server_instance_address) + ":" + (_tak_server_instance_port));
|
|
||||||
|
|
||||||
missionNamespace setVariable ["armatak_server_instance", _tak_server_fulladdress];
|
|
||||||
missionNamespace setVariable ["armatak_tcp_socket_is_running", true];
|
|
||||||
|
|
||||||
"armatak" callExtension ["tcp_socket:start", [_tak_server_fulladdress]];
|
|
||||||
|
|
||||||
_syncUnits = [];
|
|
||||||
|
|
||||||
missionNamespace setVariable ["armatak_marked_units", _syncUnits];
|
|
||||||
|
|
||||||
GVAR(syncedUnits) = missionNamespace getVariable "armatak_marked_units";
|
|
||||||
|
|
||||||
[{
|
|
||||||
GVAR(syncedUnits) = missionNamespace getVariable "armatak_marked_units";
|
|
||||||
|
|
||||||
{
|
|
||||||
_objectType = _x call BIS_fnc_objectType;
|
|
||||||
switch (true) do {
|
|
||||||
case ((_objectType select 0) == "Soldier"): {
|
|
||||||
_callsign = [_x] call armatak_fnc_extract_unit_callsign;
|
|
||||||
_group_name = [group _x] call armatak_fnc_extract_group_color;
|
|
||||||
_group_role = [_x] call armatak_fnc_extract_group_role;
|
|
||||||
|
|
||||||
[_x, _callsign, _group_name, _group_role] call armatak_fnc_send_eud_cot;
|
|
||||||
[_x] call armatak_fnc_send_digital_pointer_cot;
|
|
||||||
};
|
|
||||||
case ((_objectType select 0) == "Vehicle"): {
|
|
||||||
_atak_type = [_x] call armatak_fnc_extract_role;
|
|
||||||
_callsign = [_x] call armatak_fnc_extract_marker_callsign;
|
|
||||||
|
|
||||||
[_x, _atak_type, _callsign] call armatak_fnc_send_marker_cot;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
if (unitIsUAV _x) then {
|
|
||||||
[_x] call armatak_fnc_send_drone_cot;
|
|
||||||
[_x] call armatak_fnc_send_digital_pointer_cot;
|
|
||||||
};
|
|
||||||
} forEach GVAR(syncedUnits);
|
|
||||||
}, 2, []] call CBA_fnc_addPerFrameHandler;
|
|
||||||
deleteVehicle _logic;
|
|
||||||
closeDialog 1;
|
|
||||||
33
addons/server/functions/fnc_ZeusEnrollModuleConfig.sqf
Normal file
33
addons/server/functions/fnc_ZeusEnrollModuleConfig.sqf
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
#include "..\script_component.hpp"
|
||||||
|
|
||||||
|
params ["_logic"];
|
||||||
|
|
||||||
|
if (missionNamespace getVariable ["armatak_tcp_socket_is_running", false]) exitWith {
|
||||||
|
["Socket was called twice", "error", "TCP Socket"] call EFUNC(main,notify);
|
||||||
|
closeDialog 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
disableSerialization;
|
||||||
|
|
||||||
|
["Connecting to authenticated TAK socket", "success", "TCP Socket"] call EFUNC(main,notify);
|
||||||
|
|
||||||
|
_tak_server_instance_address = ctrlText 14100;
|
||||||
|
_tak_server_enrollment_port = ctrlText 14101;
|
||||||
|
_tak_server_enrollment_username = ctrlText 14102;
|
||||||
|
_tak_server_enrollment_password = ctrlText 14103;
|
||||||
|
|
||||||
|
"armatak" callExtension [
|
||||||
|
"tcp_socket:start_enroll_mtls",
|
||||||
|
[
|
||||||
|
_tak_server_instance_address,
|
||||||
|
_tak_server_instance_address,
|
||||||
|
_tak_server_enrollment_port,
|
||||||
|
_tak_server_enrollment_username,
|
||||||
|
_tak_server_enrollment_password,
|
||||||
|
""
|
||||||
|
]
|
||||||
|
];
|
||||||
|
|
||||||
|
_tak_server_instance_address call FUNC(startCotRouter);
|
||||||
|
deleteVehicle _logic;
|
||||||
|
closeDialog 1;
|
||||||
22
addons/server/functions/fnc_ZeusTcpModuleConfig.sqf
Normal file
22
addons/server/functions/fnc_ZeusTcpModuleConfig.sqf
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
#include "..\script_component.hpp"
|
||||||
|
|
||||||
|
params ["_logic"];
|
||||||
|
|
||||||
|
if (missionNamespace getVariable ["armatak_tcp_socket_is_running", false]) exitWith {
|
||||||
|
["Socket was called twice", "error", "TCP Socket"] call EFUNC(main,notify);
|
||||||
|
closeDialog 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
disableSerialization;
|
||||||
|
|
||||||
|
["Connecting to TCP Socket", "success", "TCP Socket"] call EFUNC(main,notify);
|
||||||
|
|
||||||
|
_tak_server_instance_address = ctrlText 14000;
|
||||||
|
_tak_server_instance_port = ctrlText 14001;
|
||||||
|
_tak_server_fulladdress = _tak_server_instance_address + ":" + _tak_server_instance_port;
|
||||||
|
|
||||||
|
"armatak" callExtension ["tcp_socket:start", [_tak_server_fulladdress]];
|
||||||
|
|
||||||
|
_tak_server_fulladdress call FUNC(startCotRouter);
|
||||||
|
deleteVehicle _logic;
|
||||||
|
closeDialog 1;
|
||||||
@@ -27,16 +27,16 @@ switch (false) do {
|
|||||||
deleteVehicle _logic;
|
deleteVehicle _logic;
|
||||||
};
|
};
|
||||||
default {
|
default {
|
||||||
if (_unit in (missionNamespace getVariable ["armatak_marked_units", []])) exitWith {
|
if (_unit in (missionNamespace getVariable ["armatak_server_syncedUnits", []])) exitWith {
|
||||||
["Unit already marked", "warning", "TCP Socket"] call EFUNC(main,notify);
|
["Unit already marked", "warning", "TCP Socket"] call EFUNC(main,notify);
|
||||||
deleteVehicle _logic;
|
deleteVehicle _logic;
|
||||||
};
|
};
|
||||||
|
|
||||||
GVAR(syncedUnits) = missionNamespace getVariable "armatak_marked_units";
|
GVAR(syncedUnits) = missionNamespace getVariable "armatak_server_syncedUnits";
|
||||||
|
|
||||||
GVAR(syncedUnits) pushBack _unit;
|
GVAR(syncedUnits) pushBack _unit;
|
||||||
|
|
||||||
missionNamespace setVariable ["armatak_marked_units", GVAR(syncedUnits)];
|
missionNamespace setVariable ["armatak_server_syncedUnits", GVAR(syncedUnits)];
|
||||||
SETVAR(_unit,GVAR(isRouting),true);
|
SETVAR(_unit,GVAR(isRouting),true);
|
||||||
|
|
||||||
deleteVehicle _logic;
|
deleteVehicle _logic;
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ switch (false) do {
|
|||||||
};
|
};
|
||||||
} forEach GVAR(syncedUnits);
|
} forEach GVAR(syncedUnits);
|
||||||
|
|
||||||
missionNmaespace setVariable ["armatak_marked_units", GVAR(syncedUnits)];
|
missionNamespace setVariable ["armatak_server_syncedUnits", GVAR(syncedUnits)];
|
||||||
SETVAR(_unit,GVAR(isRouting),false);
|
SETVAR(_unit,GVAR(isRouting),false);
|
||||||
|
|
||||||
deleteVehicle _logic;
|
deleteVehicle _logic;
|
||||||
|
|||||||
59
addons/server/functions/fnc_startCotRouter.sqf
Normal file
59
addons/server/functions/fnc_startCotRouter.sqf
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
#include "..\script_component.hpp"
|
||||||
|
|
||||||
|
params [["_server_instance", "", [""]]];
|
||||||
|
|
||||||
|
missionNamespace setVariable ["armatak_server_instance", _server_instance];
|
||||||
|
missionNamespace setVariable ["armatak_tcp_socket_is_running", true];
|
||||||
|
|
||||||
|
if (isNil { missionNamespace getVariable "armatak_server_syncedUnits" }) then {
|
||||||
|
missionNamespace setVariable ["armatak_server_syncedUnits", []];
|
||||||
|
};
|
||||||
|
|
||||||
|
GVAR(syncedUnits) = missionNamespace getVariable "armatak_server_syncedUnits";
|
||||||
|
|
||||||
|
[{
|
||||||
|
GVAR(syncedUnits) = missionNamespace getVariable "armatak_server_syncedUnits";
|
||||||
|
|
||||||
|
{
|
||||||
|
_objectType = _x call BIS_fnc_objectType;
|
||||||
|
switch (true) do {
|
||||||
|
case ((_objectType select 0) == "Soldier"): {
|
||||||
|
_callsign = [_x] call armatak_fnc_extract_unit_callsign;
|
||||||
|
_group_name = [group _x] call armatak_fnc_extract_group_color;
|
||||||
|
_group_role = [_x] call armatak_fnc_extract_group_role;
|
||||||
|
|
||||||
|
[_x, _callsign, _group_name, _group_role] call armatak_fnc_send_eud_cot;
|
||||||
|
[_x] call armatak_fnc_send_digital_pointer_cot;
|
||||||
|
};
|
||||||
|
case (unitIsUAV _x): {
|
||||||
|
if !(_x getVariable ["armatak_uav_mavlink_broadcasting", false]) then {
|
||||||
|
_atak_type = [_x] call armatak_fnc_extract_role;
|
||||||
|
_callsign = [_x] call armatak_fnc_extract_marker_callsign;
|
||||||
|
|
||||||
|
[_x, _atak_type, _callsign] call armatak_fnc_send_drone_cot;
|
||||||
|
[_x] call armatak_fnc_send_digital_pointer_cot;
|
||||||
|
_x call armatak_fnc_extract_sensor_data;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
case ((_objectType select 0) == "Vehicle"): {
|
||||||
|
_atak_type = [_x] call armatak_fnc_extract_role;
|
||||||
|
_callsign = [_x] call armatak_fnc_extract_marker_callsign;
|
||||||
|
|
||||||
|
[_x, _atak_type, _callsign] call armatak_fnc_send_marker_cot;
|
||||||
|
_x call armatak_fnc_extract_sensor_data;
|
||||||
|
};
|
||||||
|
case ((_objectType select 0) == "VehicleAutonomous"): {
|
||||||
|
if !(_x getVariable ["armatak_uav_mavlink_broadcasting", false]) then {
|
||||||
|
_atak_type = [_x] call armatak_fnc_extract_role;
|
||||||
|
_callsign = [_x] call armatak_fnc_extract_marker_callsign;
|
||||||
|
|
||||||
|
[_x, _atak_type, _callsign] call armatak_fnc_send_drone_cot;
|
||||||
|
[_x] call armatak_fnc_send_digital_pointer_cot;
|
||||||
|
_x call armatak_fnc_extract_sensor_data;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
} forEach GVAR(syncedUnits);
|
||||||
|
}, 1, []] call CBA_fnc_addPerFrameHandler;
|
||||||
|
|
||||||
|
true
|
||||||
1
addons/uav/$PBOPREFIX$
Normal file
1
addons/uav/$PBOPREFIX$
Normal file
@@ -0,0 +1 @@
|
|||||||
|
armatak\armatak\addons\uav
|
||||||
@@ -9,3 +9,9 @@ class Extended_PreInit_EventHandlers {
|
|||||||
init = QUOTE(call COMPILE_SCRIPT(XEH_preInit));
|
init = QUOTE(call COMPILE_SCRIPT(XEH_preInit));
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class Extended_PostInit_EventHandlers {
|
||||||
|
class ADDON {
|
||||||
|
init = QUOTE(call COMPILE_SCRIPT(XEH_postInit));
|
||||||
|
};
|
||||||
|
};
|
||||||
6
addons/uav/XEH_PREP.hpp
Normal file
6
addons/uav/XEH_PREP.hpp
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
PREP(startMavlinkBroadcast);
|
||||||
|
PREP(stopMavlinkBroadcast);
|
||||||
|
PREP(updateMavlinkBroadcast);
|
||||||
|
PREP(resolveVideoUri);
|
||||||
|
PREP(handleMavlinkCallback);
|
||||||
|
PREP(parseMavlinkCallbackData);
|
||||||
5
addons/uav/XEH_postInit.sqf
Normal file
5
addons/uav/XEH_postInit.sqf
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
#include "script_component.hpp"
|
||||||
|
|
||||||
|
if (!hasInterface) exitWith {};
|
||||||
|
|
||||||
|
SETVAR(player,GVAR(mavlinkPFH),-1);
|
||||||
@@ -3,15 +3,13 @@
|
|||||||
class CfgPatches {
|
class CfgPatches {
|
||||||
class ADDON {
|
class ADDON {
|
||||||
name = COMPONENT_NAME;
|
name = COMPONENT_NAME;
|
||||||
units[] = {
|
units[] = {};
|
||||||
QGVAR(videoModule)
|
|
||||||
};
|
|
||||||
weapons[] = {};
|
weapons[] = {};
|
||||||
requiredAddons[] = {
|
requiredAddons[] = {
|
||||||
"cba_main",
|
"cba_main",
|
||||||
"ace_main",
|
"ace_main",
|
||||||
"armatak_main",
|
"armatak_main",
|
||||||
"armatak_server"
|
"armatak_client"
|
||||||
};
|
};
|
||||||
requiredVersion = REQUIRED_VERSION;
|
requiredVersion = REQUIRED_VERSION;
|
||||||
author = PROJECT_AUTHOR;
|
author = PROJECT_AUTHOR;
|
||||||
@@ -20,4 +18,3 @@ class CfgPatches {
|
|||||||
};
|
};
|
||||||
|
|
||||||
#include "CfgEventHandlers.hpp"
|
#include "CfgEventHandlers.hpp"
|
||||||
//#include "CfgVehicles.hpp"
|
|
||||||
382
addons/uav/functions/fnc_handleMavlinkCallback.sqf
Normal file
382
addons/uav/functions/fnc_handleMavlinkCallback.sqf
Normal file
@@ -0,0 +1,382 @@
|
|||||||
|
#include "..\script_component.hpp"
|
||||||
|
|
||||||
|
params ["_function", ["_data", "", [""]]];
|
||||||
|
|
||||||
|
if (!hasInterface) exitWith {};
|
||||||
|
|
||||||
|
private _payload = [_data] call FUNC(parseMavlinkCallbackData);
|
||||||
|
private _uav = getConnectedUAV player;
|
||||||
|
if (isNull _uav) then {
|
||||||
|
_uav = player getVariable [QGVAR(broadcastingUav), objNull];
|
||||||
|
};
|
||||||
|
|
||||||
|
if (isNull _uav) exitWith {
|
||||||
|
"armatak" callExtension ["log", [["warn", format ["Ignoring MAVLINK UDP callback %1 because no UAV is connected: %2", _function, _data]]]];
|
||||||
|
};
|
||||||
|
|
||||||
|
private _number = {
|
||||||
|
params ["_key", ["_default", 0]];
|
||||||
|
private _raw = _payload getOrDefault [_key, str _default];
|
||||||
|
private _value = parseNumber _raw;
|
||||||
|
if (!finite _value) exitWith {_default};
|
||||||
|
_value
|
||||||
|
};
|
||||||
|
|
||||||
|
private _uavGroup = {
|
||||||
|
params ["_vehicle"];
|
||||||
|
private _crew = crew _vehicle;
|
||||||
|
if (_crew isEqualTo []) exitWith {grpNull};
|
||||||
|
group (_crew select 0)
|
||||||
|
};
|
||||||
|
|
||||||
|
private _clearWaypoints = {
|
||||||
|
params ["_group"];
|
||||||
|
if (isNull _group) exitWith {};
|
||||||
|
for "_i" from ((count waypoints _group) - 1) to 0 step -1 do {
|
||||||
|
deleteWaypoint [_group, _i];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
private _clearUavRoute = {
|
||||||
|
private _group = [_uav] call _uavGroup;
|
||||||
|
if (isNull _group) exitWith {false};
|
||||||
|
[_group] call _clearWaypoints;
|
||||||
|
_uav setVariable ["armatak_uas_mission_items", [], true];
|
||||||
|
true
|
||||||
|
};
|
||||||
|
|
||||||
|
private _geoToAtl = {
|
||||||
|
params ["_vehicle", "_lat", "_lon", ["_alt", -1]];
|
||||||
|
|
||||||
|
private _current = [_vehicle] call EFUNC(client,extractClientPosition);
|
||||||
|
private _currentLat = _current select 1;
|
||||||
|
private _currentLon = _current select 2;
|
||||||
|
private _currentAtl = getPosATL _vehicle;
|
||||||
|
|
||||||
|
private _northM = (_lat - _currentLat) * 111320;
|
||||||
|
private _eastM = (_lon - _currentLon) * (111320 * (cos _currentLat));
|
||||||
|
|
||||||
|
[
|
||||||
|
(_currentAtl select 0) + _eastM,
|
||||||
|
(_currentAtl select 1) + _northM,
|
||||||
|
if (_alt >= 0) then {_alt} else {_currentAtl select 2}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
private _commandMove = {
|
||||||
|
params ["_vehicle", "_positionAtl", ["_type", "MOVE"], ["_radius", 80], ["_completion", 50]];
|
||||||
|
|
||||||
|
private _group = [_vehicle] call _uavGroup;
|
||||||
|
if (isNull _group) exitWith {false};
|
||||||
|
|
||||||
|
[_group] call _clearWaypoints;
|
||||||
|
|
||||||
|
_vehicle engineOn true;
|
||||||
|
_vehicle setVariable ["armatak_uas_armed", true, true];
|
||||||
|
_vehicle setFuel ((fuel _vehicle) max 0.1);
|
||||||
|
_vehicle flyInHeight ((_positionAtl select 2) max 10);
|
||||||
|
_vehicle doMove _positionAtl;
|
||||||
|
|
||||||
|
private _wp = _group addWaypoint [_positionAtl, 0];
|
||||||
|
_wp setWaypointType _type;
|
||||||
|
_wp setWaypointBehaviour "CARELESS";
|
||||||
|
_wp setWaypointCombatMode "BLUE";
|
||||||
|
_wp setWaypointSpeed "NORMAL";
|
||||||
|
_wp setWaypointCompletionRadius _completion;
|
||||||
|
|
||||||
|
if (_type == "LOITER") then {
|
||||||
|
_wp setWaypointLoiterRadius (_radius max 25);
|
||||||
|
_wp setWaypointLoiterType "CIRCLE_L";
|
||||||
|
};
|
||||||
|
|
||||||
|
true
|
||||||
|
};
|
||||||
|
|
||||||
|
private _appendMissionWaypoint = {
|
||||||
|
params ["_vehicle", "_positionAtl", "_command", "_seq", ["_radius", 80]];
|
||||||
|
|
||||||
|
private _group = [_vehicle] call _uavGroup;
|
||||||
|
if (isNull _group) exitWith {false};
|
||||||
|
|
||||||
|
private _type = switch (_command) do {
|
||||||
|
case 17;
|
||||||
|
case 18;
|
||||||
|
case 19;
|
||||||
|
case 31: {"LOITER"};
|
||||||
|
case 21: {"MOVE"};
|
||||||
|
default {"MOVE"};
|
||||||
|
};
|
||||||
|
|
||||||
|
private _wp = _group addWaypoint [_positionAtl, 0];
|
||||||
|
_wp setWaypointType _type;
|
||||||
|
_wp setWaypointBehaviour "CARELESS";
|
||||||
|
_wp setWaypointCombatMode "BLUE";
|
||||||
|
_wp setWaypointSpeed "NORMAL";
|
||||||
|
_wp setWaypointCompletionRadius 35;
|
||||||
|
|
||||||
|
if (_type == "LOITER") then {
|
||||||
|
_wp setWaypointLoiterRadius (_radius max 25);
|
||||||
|
_wp setWaypointLoiterType "CIRCLE_L";
|
||||||
|
};
|
||||||
|
if (_command == 21) then {
|
||||||
|
_wp setWaypointStatements ["true", "(vehicle this) land 'LAND'"];
|
||||||
|
};
|
||||||
|
|
||||||
|
private _items = _vehicle getVariable ["armatak_uas_mission_items", []];
|
||||||
|
_items pushBack [_seq, _command, _positionAtl];
|
||||||
|
_vehicle setVariable ["armatak_uas_mission_items", _items, true];
|
||||||
|
|
||||||
|
true
|
||||||
|
};
|
||||||
|
|
||||||
|
private _commandName = _payload getOrDefault ["command_name", "UNKNOWN"];
|
||||||
|
private _command = [_payload getOrDefault ["command", "-1"]] call BIS_fnc_parseNumber;
|
||||||
|
private _callsign = [_uav] call armatak_fnc_extract_marker_callsign;
|
||||||
|
|
||||||
|
private _applySpeed = {
|
||||||
|
params ["_speed"];
|
||||||
|
if (_speed <= 0) exitWith {false};
|
||||||
|
_uav limitSpeed _speed;
|
||||||
|
systemChat format ["ATAK SPEED %1m/s %2", round _speed, _callsign];
|
||||||
|
true
|
||||||
|
};
|
||||||
|
|
||||||
|
private _applyMode = {
|
||||||
|
params ["_mode"];
|
||||||
|
|
||||||
|
switch (_mode) do {
|
||||||
|
case 4: {
|
||||||
|
_uav engineOn true;
|
||||||
|
_uav setVariable ["armatak_uas_armed", true, true];
|
||||||
|
_uav setFuel ((fuel _uav) max 0.1);
|
||||||
|
systemChat format ["ATAK GUIDED %1", _callsign];
|
||||||
|
};
|
||||||
|
case 5: {
|
||||||
|
private _pos = getPosATL _uav;
|
||||||
|
[_uav, _pos, "LOITER", 80, 25] call _commandMove;
|
||||||
|
systemChat format ["ATAK LOITER %1", _callsign];
|
||||||
|
};
|
||||||
|
case 6;
|
||||||
|
case 21;
|
||||||
|
case 27: {
|
||||||
|
private _home = _uav getVariable ["armatak_uas_home_atl", getPosATL _uav];
|
||||||
|
_home set [2, ((_home select 2) max 60)];
|
||||||
|
[_uav, _home, "MOVE", 80, 60] call _commandMove;
|
||||||
|
systemChat format ["ATAK RTL %1", _callsign];
|
||||||
|
};
|
||||||
|
case 9: {
|
||||||
|
private _pos = getPosATL _uav;
|
||||||
|
_pos set [2, 0];
|
||||||
|
[_uav, _pos, "MOVE", 30, 20] call _commandMove;
|
||||||
|
_uav flyInHeight 0;
|
||||||
|
systemChat format ["ATAK LAND %1", _callsign];
|
||||||
|
};
|
||||||
|
default {
|
||||||
|
"armatak" callExtension ["log", [["info", format ["Unhandled MAVLINK mode %1 for UAV %2", _mode, _uav]]]];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
private _setHomeFromGeo = {
|
||||||
|
params ["_lat", "_lon", "_alt"];
|
||||||
|
if (_lat == 0 && {_lon == 0}) exitWith {false};
|
||||||
|
private _homeAtl = [_uav, _lat, _lon, _alt] call _geoToAtl;
|
||||||
|
_uav setVariable ["armatak_uas_home_atl", _homeAtl, true];
|
||||||
|
_uav setVariable ["armatak_uas_home_geo", [_lat, _lon, _alt], true];
|
||||||
|
systemChat format ["ATAK HOME %1", _callsign];
|
||||||
|
true
|
||||||
|
};
|
||||||
|
|
||||||
|
switch (_function) do {
|
||||||
|
case "COMMAND_LONG": {
|
||||||
|
switch (_command) do {
|
||||||
|
case 176: {
|
||||||
|
private _mode = ["param2", -1] call _number;
|
||||||
|
if (_mode < 0) then {
|
||||||
|
_mode = ["param1", -1] call _number;
|
||||||
|
};
|
||||||
|
[_mode] call _applyMode;
|
||||||
|
};
|
||||||
|
case 178: {
|
||||||
|
private _speed = ["param2", -1] call _number;
|
||||||
|
if (_speed <= 0) then {
|
||||||
|
_speed = ["param1", -1] call _number;
|
||||||
|
};
|
||||||
|
[_speed] call _applySpeed;
|
||||||
|
};
|
||||||
|
case 179: {
|
||||||
|
private _useCurrent = (["param1", 0] call _number) >= 1;
|
||||||
|
if (_useCurrent) then {
|
||||||
|
private _pos = [_uav] call EFUNC(client,extractClientPosition);
|
||||||
|
private _relAlt = ((getPosATL _uav) select 2) max 0;
|
||||||
|
private _homeAtl = getPosATL _uav;
|
||||||
|
_uav setVariable ["armatak_uas_home_atl", _homeAtl, true];
|
||||||
|
_uav setVariable ["armatak_uas_home_geo", [_pos select 1, _pos select 2, (_pos select 3) - _relAlt], true];
|
||||||
|
systemChat format ["ATAK HOME %1", _callsign];
|
||||||
|
} else {
|
||||||
|
[["param5", 0] call _number, ["param6", 0] call _number, ["param7", 0] call _number] call _setHomeFromGeo;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
case 400: {
|
||||||
|
private _doArm = (["param1", 0] call _number) >= 1;
|
||||||
|
_uav engineOn _doArm;
|
||||||
|
_uav setVariable ["armatak_uas_armed", _doArm, true];
|
||||||
|
if (_doArm) then {
|
||||||
|
_uav setFuel ((fuel _uav) max 0.1);
|
||||||
|
};
|
||||||
|
systemChat format ["ATAK %1 %2", ["DISARM", "ARM"] select _doArm, _callsign];
|
||||||
|
"armatak" callExtension ["log", [["info", format ["Applied MAVLINK ARM=%1 to UAV %2", _doArm, _uav]]]];
|
||||||
|
};
|
||||||
|
case 22: {
|
||||||
|
private _alt = (["param7", 75] call _number) max 10;
|
||||||
|
private _pos = getPosATL _uav;
|
||||||
|
_pos set [2, _alt];
|
||||||
|
[_uav, _pos, "MOVE", 80, 25] call _commandMove;
|
||||||
|
_uav setVelocityModelSpace [0, 15, 8];
|
||||||
|
systemChat format ["ATAK TAKEOFF %1m %2", round _alt, _callsign];
|
||||||
|
};
|
||||||
|
case 21: {
|
||||||
|
[9] call _applyMode;
|
||||||
|
};
|
||||||
|
case 20: {
|
||||||
|
[6] call _applyMode;
|
||||||
|
};
|
||||||
|
case 16: {
|
||||||
|
private _lat = ["param5", 0] call _number;
|
||||||
|
private _lon = ["param6", 0] call _number;
|
||||||
|
private _alt = ["param7", -1] call _number;
|
||||||
|
private _pos = [_uav, _lat, _lon, _alt] call _geoToAtl;
|
||||||
|
[_uav, _pos, "MOVE", 80, 50] call _commandMove;
|
||||||
|
systemChat format ["ATAK MOVE %1", _callsign];
|
||||||
|
};
|
||||||
|
case 17: {
|
||||||
|
private _lat = ["param5", 0] call _number;
|
||||||
|
private _lon = ["param6", 0] call _number;
|
||||||
|
private _alt = ["param7", -1] call _number;
|
||||||
|
private _radius = abs (["param3", 80] call _number);
|
||||||
|
private _pos = [_uav, _lat, _lon, _alt] call _geoToAtl;
|
||||||
|
[_uav, _pos, "LOITER", _radius, 30] call _commandMove;
|
||||||
|
systemChat format ["ATAK LOITER %1", _callsign];
|
||||||
|
};
|
||||||
|
case 43000: {
|
||||||
|
private _speed = ["param2", -1] call _number;
|
||||||
|
if (_speed <= 0) then {
|
||||||
|
_speed = ["param1", -1] call _number;
|
||||||
|
};
|
||||||
|
[_speed] call _applySpeed;
|
||||||
|
};
|
||||||
|
case 43001: {
|
||||||
|
private _alt = ["param1", -1] call _number;
|
||||||
|
if (_alt < 0) then {
|
||||||
|
_alt = ["param7", -1] call _number;
|
||||||
|
};
|
||||||
|
private _pos = getPosATL _uav;
|
||||||
|
_pos set [2, _alt max 10];
|
||||||
|
[_uav, _pos, "MOVE", 80, 25] call _commandMove;
|
||||||
|
systemChat format ["ATAK ALT %1m %2", round (_pos select 2), _callsign];
|
||||||
|
};
|
||||||
|
case 43002: {
|
||||||
|
private _heading = ["param1", -1] call _number;
|
||||||
|
if (_heading >= 0) then {
|
||||||
|
_uav setDir _heading;
|
||||||
|
systemChat format ["ATAK HDG %1 %2", round _heading, _callsign];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
default {
|
||||||
|
"armatak" callExtension ["log", [["info", format ["Unhandled MAVLINK COMMAND_LONG %1 (%2): %3", _command, _commandName, _data]]]];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
case "COMMAND_INT": {
|
||||||
|
private _lat = (["x", 0] call _number) / 1e7;
|
||||||
|
private _lon = (["y", 0] call _number) / 1e7;
|
||||||
|
private _alt = ["z", -1] call _number;
|
||||||
|
|
||||||
|
switch (_command) do {
|
||||||
|
case 16: {
|
||||||
|
private _pos = [_uav, _lat, _lon, _alt] call _geoToAtl;
|
||||||
|
[_uav, _pos, "MOVE", 80, 50] call _commandMove;
|
||||||
|
systemChat format ["ATAK MOVE %1", _callsign];
|
||||||
|
};
|
||||||
|
case 17;
|
||||||
|
case 192: {
|
||||||
|
private _radius = abs (["param3", 80] call _number);
|
||||||
|
private _direction = ["CIRCLE_L", "CIRCLE_R"] select ((["param4", 0] call _number) < 0);
|
||||||
|
private _pos = [_uav, _lat, _lon, _alt] call _geoToAtl;
|
||||||
|
private _type = ["MOVE", "LOITER"] select (_radius > 1);
|
||||||
|
[_uav, _pos, _type, _radius, 30] call _commandMove;
|
||||||
|
if (_type == "LOITER") then {
|
||||||
|
private _group = [_uav] call _uavGroup;
|
||||||
|
private _waypoints = waypoints _group;
|
||||||
|
if (_waypoints isNotEqualTo []) then {
|
||||||
|
(_waypoints select -1) setWaypointLoiterType _direction;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
systemChat format ["ATAK %1 %2", _type, _callsign];
|
||||||
|
};
|
||||||
|
default {
|
||||||
|
"armatak" callExtension ["log", [["info", format ["Unhandled MAVLINK COMMAND_INT %1 (%2): %3", _command, _commandName, _data]]]];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
case "MISSION_COUNT": {
|
||||||
|
private _count = ["count", 0] call _number;
|
||||||
|
[] call _clearUavRoute;
|
||||||
|
systemChat format ["ATAK ROUTE %1 pts %2", round _count, _callsign];
|
||||||
|
"armatak" callExtension ["log", [["info", format ["Receiving MAVLINK mission count=%1 for UAV %2", _count, _uav]]]];
|
||||||
|
};
|
||||||
|
case "MISSION_CLEAR_ALL": {
|
||||||
|
[] call _clearUavRoute;
|
||||||
|
systemChat format ["ATAK ROUTE CLEAR %1", _callsign];
|
||||||
|
};
|
||||||
|
case "MISSION_SET_CURRENT": {
|
||||||
|
private _seq = ["seq", 0] call _number;
|
||||||
|
"armatak" callExtension ["log", [["info", format ["MAVLINK mission set current seq=%1 for UAV %2", _seq, _uav]]]];
|
||||||
|
};
|
||||||
|
case "MISSION_ITEM";
|
||||||
|
case "MISSION_ITEM_INT": {
|
||||||
|
private _seq = ["seq", 0] call _number;
|
||||||
|
private _missionCommand = ["command", -1] call _number;
|
||||||
|
private _lat = ["lat", 0] call _number;
|
||||||
|
private _lon = ["lon", 0] call _number;
|
||||||
|
private _alt = ["alt", -1] call _number;
|
||||||
|
|
||||||
|
if (_lat == 0 && {_lon == 0}) exitWith {
|
||||||
|
"armatak" callExtension ["log", [["warn", format ["Ignoring MAVLINK mission item at zero coordinate: %1", _data]]]];
|
||||||
|
};
|
||||||
|
|
||||||
|
private _pos = [_uav, _lat, _lon, _alt] call _geoToAtl;
|
||||||
|
private _radius = abs (["param3", 80] call _number);
|
||||||
|
[_uav, _pos, _missionCommand, _seq, _radius] call _appendMissionWaypoint;
|
||||||
|
_uav engineOn true;
|
||||||
|
_uav setVariable ["armatak_uas_armed", true, true];
|
||||||
|
_uav setFuel ((fuel _uav) max 0.1);
|
||||||
|
_uav flyInHeight ((_pos select 2) max 10);
|
||||||
|
systemChat format ["ATAK ROUTE WP %1 %2", round _seq, _callsign];
|
||||||
|
"armatak" callExtension ["log", [["info", format ["Added MAVLINK mission item seq=%1 command=%2 posATL=%3 for UAV %4", _seq, _missionCommand, _pos, _uav]]]];
|
||||||
|
};
|
||||||
|
case "SET_HOME_POSITION": {
|
||||||
|
[["lat", 0] call _number, ["lon", 0] call _number, ["alt", 0] call _number] call _setHomeFromGeo;
|
||||||
|
};
|
||||||
|
case "SET_POSITION_TARGET_GLOBAL_INT": {
|
||||||
|
private _lat = ["lat", 0] call _number;
|
||||||
|
private _lon = ["lon", 0] call _number;
|
||||||
|
private _alt = ["alt", -1] call _number;
|
||||||
|
private _pos = [_uav, _lat, _lon, _alt] call _geoToAtl;
|
||||||
|
[_uav, _pos, "MOVE", 80, 40] call _commandMove;
|
||||||
|
systemChat format ["ATAK GUIDED MOVE %1", _callsign];
|
||||||
|
};
|
||||||
|
case "SET_MODE": {
|
||||||
|
private _mode = ["custom_mode", -1] call _number;
|
||||||
|
[_mode] call _applyMode;
|
||||||
|
};
|
||||||
|
case "COMMAND_ACK": {
|
||||||
|
"armatak" callExtension ["log", [["info", format ["Received MAVLINK COMMAND_ACK %1 (%2): %3", _command, _commandName, _data]]]];
|
||||||
|
};
|
||||||
|
case "MANUAL_CONTROL": {
|
||||||
|
"armatak" callExtension ["log", [["info", format ["Received MAVLINK MANUAL_CONTROL: %1", _data]]]];
|
||||||
|
};
|
||||||
|
default {
|
||||||
|
"armatak" callExtension ["log", [["info", format ["Unhandled MAVLINK UDP callback %1: %2", _function, _data]]]];
|
||||||
|
};
|
||||||
|
};
|
||||||
17
addons/uav/functions/fnc_parseMavlinkCallbackData.sqf
Normal file
17
addons/uav/functions/fnc_parseMavlinkCallbackData.sqf
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
#include "..\script_component.hpp"
|
||||||
|
|
||||||
|
params [["_raw", "", [""]]];
|
||||||
|
|
||||||
|
private _pairs = createHashMap;
|
||||||
|
|
||||||
|
{
|
||||||
|
private _entry = _x;
|
||||||
|
private _separatorIndex = _entry find "=";
|
||||||
|
if (_separatorIndex > 0) then {
|
||||||
|
private _key = _entry select [0, _separatorIndex];
|
||||||
|
private _value = _entry select [_separatorIndex + 1];
|
||||||
|
_pairs set [_key, _value];
|
||||||
|
};
|
||||||
|
} forEach (_raw splitString ";");
|
||||||
|
|
||||||
|
_pairs
|
||||||
45
addons/uav/functions/fnc_resolveVideoUri.sqf
Normal file
45
addons/uav/functions/fnc_resolveVideoUri.sqf
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
#include "..\script_component.hpp"
|
||||||
|
|
||||||
|
params [["_uav", objNull, [objNull]]];
|
||||||
|
|
||||||
|
private _defaultVideoUri = "rtsp://undefined:554/fpv";
|
||||||
|
private _activelyControlledUav = if (!isNull player) then {getConnectedUAV player} else {objNull};
|
||||||
|
|
||||||
|
private _normalize = {
|
||||||
|
params ["_rawUrl"];
|
||||||
|
|
||||||
|
private _url = trim _rawUrl;
|
||||||
|
if (_url isEqualTo "") exitWith {""};
|
||||||
|
|
||||||
|
if (_url find "://" >= 0) exitWith {_url};
|
||||||
|
|
||||||
|
if (_url find "/" >= 0) exitWith {
|
||||||
|
format ["rtsp://%1", _url]
|
||||||
|
};
|
||||||
|
|
||||||
|
format ["rtp://%1", _url]
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!isNull _uav) then {
|
||||||
|
private _objectVideoUrl = [_uav] call armatak_fnc_extract_marker_video_url;
|
||||||
|
private _normalizedObjectVideoUrl = [_objectVideoUrl] call _normalize;
|
||||||
|
if (_normalizedObjectVideoUrl isNotEqualTo "") exitWith {
|
||||||
|
_normalizedObjectVideoUrl
|
||||||
|
};
|
||||||
|
|
||||||
|
private _activeSessionVideoUrl = player getVariable [QEGVAR(client,video_feed_url), ""];
|
||||||
|
private _normalizedActiveSessionVideoUrl = [_activeSessionVideoUrl] call _normalize;
|
||||||
|
if (_normalizedActiveSessionVideoUrl isNotEqualTo "") exitWith {
|
||||||
|
_normalizedActiveSessionVideoUrl
|
||||||
|
};
|
||||||
|
|
||||||
|
_defaultVideoUri
|
||||||
|
};
|
||||||
|
|
||||||
|
private _sessionVideoUrl = player getVariable [QEGVAR(client,video_feed_url), ""];
|
||||||
|
private _normalizedSessionVideoUrl = [_sessionVideoUrl] call _normalize;
|
||||||
|
if (_normalizedSessionVideoUrl isNotEqualTo "") exitWith {
|
||||||
|
_normalizedSessionVideoUrl
|
||||||
|
};
|
||||||
|
|
||||||
|
_defaultVideoUri
|
||||||
16
addons/uav/functions/fnc_startMavlinkBroadcast.sqf
Normal file
16
addons/uav/functions/fnc_startMavlinkBroadcast.sqf
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
#include "..\script_component.hpp"
|
||||||
|
|
||||||
|
if (!hasInterface) exitWith {};
|
||||||
|
|
||||||
|
private _existingPfh = player getVariable [QGVAR(mavlinkPFH), -1];
|
||||||
|
if (_existingPfh >= 0) then {
|
||||||
|
[_existingPfh] call CBA_fnc_removePerFrameHandler;
|
||||||
|
};
|
||||||
|
|
||||||
|
player setVariable [QGVAR(broadcastingUav), objNull];
|
||||||
|
|
||||||
|
private _pfh = [{
|
||||||
|
call FUNC(updateMavlinkBroadcast);
|
||||||
|
}, 0.5, []] call CBA_fnc_addPerFrameHandler;
|
||||||
|
|
||||||
|
player setVariable [QGVAR(mavlinkPFH), _pfh];
|
||||||
17
addons/uav/functions/fnc_stopMavlinkBroadcast.sqf
Normal file
17
addons/uav/functions/fnc_stopMavlinkBroadcast.sqf
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
#include "..\script_component.hpp"
|
||||||
|
|
||||||
|
if (!hasInterface) exitWith {};
|
||||||
|
|
||||||
|
private _existingPfh = player getVariable [QGVAR(mavlinkPFH), -1];
|
||||||
|
if (_existingPfh >= 0) then {
|
||||||
|
[_existingPfh] call CBA_fnc_removePerFrameHandler;
|
||||||
|
player setVariable [QGVAR(mavlinkPFH), -1];
|
||||||
|
};
|
||||||
|
|
||||||
|
private _broadcastingUav = player getVariable [QGVAR(broadcastingUav), objNull];
|
||||||
|
if (!isNull _broadcastingUav) then {
|
||||||
|
_broadcastingUav setVariable ["armatak_uav_mavlink_broadcasting", false, true];
|
||||||
|
systemChat "UAV broadcasting stopped";
|
||||||
|
};
|
||||||
|
|
||||||
|
player setVariable [QGVAR(broadcastingUav), objNull];
|
||||||
122
addons/uav/functions/fnc_updateMavlinkBroadcast.sqf
Normal file
122
addons/uav/functions/fnc_updateMavlinkBroadcast.sqf
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
#include "..\script_component.hpp"
|
||||||
|
|
||||||
|
private _broadcastingUav = player getVariable [QGVAR(broadcastingUav), objNull];
|
||||||
|
|
||||||
|
if !(player getVariable [QEGVAR(client,eudConnected), false]) exitWith {
|
||||||
|
if (!isNull _broadcastingUav) then {
|
||||||
|
_broadcastingUav setVariable ["armatak_uav_mavlink_broadcasting", false, true];
|
||||||
|
player setVariable [QGVAR(broadcastingUav), objNull];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
private _uav = getConnectedUAV player;
|
||||||
|
if (isNull _uav) then {
|
||||||
|
_uav = _broadcastingUav;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (isNull _uav || {!alive _uav}) exitWith {
|
||||||
|
if (!isNull _broadcastingUav) then {
|
||||||
|
_broadcastingUav setVariable ["armatak_uav_mavlink_broadcasting", false, true];
|
||||||
|
player setVariable [QGVAR(broadcastingUav), objNull];
|
||||||
|
systemChat "UAV broadcasting stopped";
|
||||||
|
"armatak" callExtension ["log", [["info", "UAV broadcasting stopped because the UAV is no longer available"]]];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
if (_broadcastingUav isNotEqualTo _uav) then {
|
||||||
|
if (!isNull _broadcastingUav) then {
|
||||||
|
_broadcastingUav setVariable ["armatak_uav_mavlink_broadcasting", false, true];
|
||||||
|
};
|
||||||
|
player setVariable [QGVAR(broadcastingUav), _uav];
|
||||||
|
_uav setVariable ["armatak_uav_mavlink_broadcasting", true, true];
|
||||||
|
private _callsign = [_uav] call armatak_fnc_extract_marker_callsign;
|
||||||
|
systemChat format ["Broadcasting UAV %1", _callsign];
|
||||||
|
"armatak" callExtension ["log", [["info", format ["Broadcasting UAV %1 via MAVLink mock to %2", _callsign, player getVariable [QEGVAR(client,mavlink_address), ""]]]]];
|
||||||
|
};
|
||||||
|
|
||||||
|
_uav setVariable ["armatak_uav_mavlink_broadcasting", true, true];
|
||||||
|
|
||||||
|
private _mavlinkAddress = player getVariable [QEGVAR(client,mavlink_address), ""];
|
||||||
|
if (_mavlinkAddress isEqualTo "") exitWith {};
|
||||||
|
|
||||||
|
private _pos = [_uav] call EFUNC(client,extractClientPosition);
|
||||||
|
private _relAlt = ((getPosATL _uav) select 2) max 0;
|
||||||
|
if (isNil {_uav getVariable "armatak_uas_home_atl"}) then {
|
||||||
|
_uav setVariable ["armatak_uas_home_atl", getPosATL _uav, true];
|
||||||
|
_uav setVariable ["armatak_uas_home_geo", [_pos select 1, _pos select 2, (_pos select 3) - _relAlt], true];
|
||||||
|
};
|
||||||
|
private _uuid = [_uav] call armatak_fnc_extract_uuid;
|
||||||
|
private _callsign = [_uav] call armatak_fnc_extract_marker_callsign;
|
||||||
|
private _videoUri = [_uav] call FUNC(resolveVideoUri);
|
||||||
|
private _dir = vectorDir _uav;
|
||||||
|
private _up = vectorUp _uav;
|
||||||
|
private _yaw = getDir _uav;
|
||||||
|
private _pitch = asin (((_dir select 2) max -1) min 1);
|
||||||
|
private _roll = asin (((_up select 0) max -1) min 1);
|
||||||
|
private _uavType = if (_uav isKindOf "Plane") then {1} else {[2, 3] select (_uav isKindOf "Helicopter")};
|
||||||
|
private _armed = _uav getVariable ["armatak_uas_armed", isEngineOn _uav];
|
||||||
|
if !(isEngineOn _uav) then {
|
||||||
|
_armed = false;
|
||||||
|
_uav setVariable ["armatak_uas_armed", false, true];
|
||||||
|
};
|
||||||
|
private _groundSpeed = abs (_pos select 6);
|
||||||
|
private _landed = (_relAlt <= 1.5) && {_groundSpeed <= 0.5};
|
||||||
|
private _batteryRemaining = round ((((fuel _uav) max 0) min 1) * 100);
|
||||||
|
|
||||||
|
private _gimbalRoll = 0;
|
||||||
|
private _gimbalPitch = _pitch;
|
||||||
|
private _gimbalYaw = _yaw;
|
||||||
|
private _hfov = _uav getVariable ["armatak_uas_fov", 60];
|
||||||
|
private _vfov = _uav getVariable ["armatak_uas_vfov", (_hfov * 0.5625)];
|
||||||
|
private _imageLat = _pos select 1;
|
||||||
|
private _imageLon = _pos select 2;
|
||||||
|
private _imageAlt = _pos select 3;
|
||||||
|
private _cameraData = [_uav, "turret"] call armatak_fnc_extract_uas_camera_data;
|
||||||
|
private _uavControl = UAVControl _uav;
|
||||||
|
private _controlledTurretPath = _uavControl param [1, []];
|
||||||
|
private _hasTurretCamera = ((_controlledTurretPath isEqualType []) && {_controlledTurretPath isNotEqualTo []}) || {(allTurrets _uav) isNotEqualTo []};
|
||||||
|
|
||||||
|
if (_cameraData isEqualType [] && {(count _cameraData) >= 6}) then {
|
||||||
|
_gimbalYaw = _cameraData param [0, _yaw];
|
||||||
|
_gimbalPitch = _cameraData param [1, _pitch];
|
||||||
|
_hfov = _cameraData param [2, _hfov];
|
||||||
|
_vfov = _uav getVariable ["armatak_uas_vfov", (_hfov * 0.5625)];
|
||||||
|
|
||||||
|
private _spiGeo = _cameraData param [5, []];
|
||||||
|
if (_spiGeo isEqualType [] && {(count _spiGeo) >= 3}) then {
|
||||||
|
_imageLat = _spiGeo select 0;
|
||||||
|
_imageLon = _spiGeo select 1;
|
||||||
|
_imageAlt = _spiGeo select 2;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
private _systemPayload = [
|
||||||
|
_mavlinkAddress,
|
||||||
|
_uuid,
|
||||||
|
_callsign,
|
||||||
|
_uavType,
|
||||||
|
_pos select 1,
|
||||||
|
_pos select 2,
|
||||||
|
_pos select 3,
|
||||||
|
_relAlt,
|
||||||
|
_pos select 5,
|
||||||
|
_pos select 6,
|
||||||
|
_roll,
|
||||||
|
_pitch,
|
||||||
|
_yaw,
|
||||||
|
parseNumber _armed,
|
||||||
|
parseNumber _landed,
|
||||||
|
_gimbalRoll,
|
||||||
|
_gimbalPitch,
|
||||||
|
_gimbalYaw,
|
||||||
|
_videoUri,
|
||||||
|
_hfov,
|
||||||
|
_vfov,
|
||||||
|
_imageLat,
|
||||||
|
_imageLon,
|
||||||
|
_imageAlt,
|
||||||
|
parseNumber _hasTurretCamera,
|
||||||
|
_batteryRemaining
|
||||||
|
];
|
||||||
|
|
||||||
|
"armatak" callExtension ["uas:send_uas_system", [_systemPayload]];
|
||||||
@@ -1,17 +1,17 @@
|
|||||||
#define COMPONENT video
|
#define COMPONENT uav
|
||||||
#define COMPONENT_BEAUTIFIED Video Streaming
|
#define COMPONENT_BEAUTIFIED UAV
|
||||||
#include "\armatak\armatak\addons\main\script_mod.hpp"
|
#include "\armatak\armatak\addons\main\script_mod.hpp"
|
||||||
|
|
||||||
// #define DEBUG_MODE_FULL
|
// #define DEBUG_MODE_FULL
|
||||||
// #define DISABLE_COMPILE_CACHE
|
// #define DISABLE_COMPILE_CACHE
|
||||||
// #define ENABLE_PERFORMANCE_COUNTERS
|
// #define ENABLE_PERFORMANCE_COUNTERS
|
||||||
|
|
||||||
#ifdef DEBUG_ENABLED_MAIN
|
#ifdef DEBUG_ENABLED_UAV
|
||||||
#define DEBUG_MODE_FULL
|
#define DEBUG_MODE_FULL
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef DEBUG_SETTINGS_MAIN
|
#ifdef DEBUG_SETTINGS_UAV
|
||||||
#define DEBUG_SETTINGS DEBUG_SETTINGS_MAIN
|
#define DEBUG_SETTINGS DEBUG_SETTINGS_UAV
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include "\z\ace\addons\main\script_macros.hpp"
|
#include "\z\ace\addons\main\script_macros.hpp"
|
||||||
@@ -1 +0,0 @@
|
|||||||
armatak\armatak\addons\video
|
|
||||||
@@ -1,73 +0,0 @@
|
|||||||
class CfgVehicles {
|
|
||||||
class Logic;
|
|
||||||
|
|
||||||
class Module_F : Logic
|
|
||||||
{
|
|
||||||
class AttributesBase
|
|
||||||
{
|
|
||||||
class Default;
|
|
||||||
class Edit;
|
|
||||||
class Combo;
|
|
||||||
class Checkbox;
|
|
||||||
class CheckboxNumber;
|
|
||||||
class ModuleDescription;
|
|
||||||
class Units;
|
|
||||||
};
|
|
||||||
|
|
||||||
class ModuleDescription
|
|
||||||
{
|
|
||||||
class AnyBrain;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
class EGVAR(server,moduleBase);
|
|
||||||
class GVAR(videoModule): EGVAR(server,moduleBase) {
|
|
||||||
scope = 2;
|
|
||||||
scopeCurator = 0;
|
|
||||||
displayname = "Video Streaming Handler";
|
|
||||||
icon = "\a3\Modules_F_Curator\Data\iconRadio_ca.paa";
|
|
||||||
category = QEGVAR(main,moduleCategory);
|
|
||||||
function = QFUNC(videoParser);
|
|
||||||
functionPriority = 1;
|
|
||||||
isGlobal = 0;
|
|
||||||
isTriggerActivated = 1;
|
|
||||||
isDisposable = 1;
|
|
||||||
is3den = 0;
|
|
||||||
curatorCanAttach = 0;
|
|
||||||
curatorInfoType = "RscDisplayAttributeModuleNuke";
|
|
||||||
canSetArea = 0;
|
|
||||||
canSetAreaShape = 0;
|
|
||||||
canSetAreaHeight = 0;
|
|
||||||
/*
|
|
||||||
class Attributes: AttributesBase {
|
|
||||||
class GVAR(instanceAddress): Edit {
|
|
||||||
property = QGVAR(instanceAddress);
|
|
||||||
displayname = "MediaMTX Provider Address";
|
|
||||||
tooltip = "MediaMTX Provider Instance Address";
|
|
||||||
typeName = "STRING";
|
|
||||||
defaultValue = "localhost";
|
|
||||||
};
|
|
||||||
class GVAR(instancePort): Edit {
|
|
||||||
property = QGVAR(instancePort);
|
|
||||||
displayname = QUOTE(MediaMTX Provider Port);
|
|
||||||
tooltip = QUOTE(MediaMTX Provider Port for handling video streams);
|
|
||||||
typeName = "STRING";
|
|
||||||
defaultValue = "8554";
|
|
||||||
};
|
|
||||||
class GVAR(instanceAuthUser): Edit {
|
|
||||||
property = QGVAR(instanceAuthUser);
|
|
||||||
displayname = QUOTE(MediaMTX Provider Username);
|
|
||||||
tooltip = QUOTE(MediaMTX Provider Instance Username);
|
|
||||||
typeName = "STRING";
|
|
||||||
defaultValue = "administrator";
|
|
||||||
};
|
|
||||||
class GVAR(instanceAuthPassword): Edit {
|
|
||||||
property = QGVAR(instanceAuthPassword);
|
|
||||||
displayname = QUOTE(MediaMTX Provider Password);
|
|
||||||
tooltip = QUOTE(MediaMTX Provider Instance Password);
|
|
||||||
typeName = "STRING";
|
|
||||||
defaultValue = "password";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
*/
|
|
||||||
};
|
|
||||||
};
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
PREP(videoParser);
|
|
||||||
@@ -1,83 +0,0 @@
|
|||||||
#include "..\script_component.hpp"
|
|
||||||
|
|
||||||
params [
|
|
||||||
["_logic", objNull, [objNull]],
|
|
||||||
["_units", [], [[]]],
|
|
||||||
["_activated", true, [true]]
|
|
||||||
];
|
|
||||||
|
|
||||||
if (isServer) exitWith {
|
|
||||||
private _instance_address = GETVAR(_logic,GVAR(instanceAddress),false);
|
|
||||||
private _instance_port = GETVAR(_logic,GVAR(instancePort),false);
|
|
||||||
private _instance_auth_user = GETVAR(_logic,GVAR(instanceAuthUser),false);
|
|
||||||
private _instance_auth_pass = GETVAR(_logic,GVAR(instanceAuthPassword),false);
|
|
||||||
|
|
||||||
SETMVAR(GVAR(instanceAddress),_instance_address);
|
|
||||||
SETMVAR(GVAR(instancePort),_instance_port);
|
|
||||||
SETMVAR(GVAR(instanceAuthUser),_instance_auth_user);
|
|
||||||
SETMVAR(GVAR(instanceAuthPassword),_instance_auth_pass);
|
|
||||||
|
|
||||||
_startAction = [
|
|
||||||
QGVAR(startStream),
|
|
||||||
"Start Video Feed",
|
|
||||||
"",
|
|
||||||
{
|
|
||||||
_uuid = (_this select 0) call armatak_fnc_extract_uuid;
|
|
||||||
_uuid_short = _uuid select [0, 8];
|
|
||||||
_role = roleDescription (_this select 0);
|
|
||||||
_name = name (_this select 0);
|
|
||||||
|
|
||||||
_role = [_role] call BIS_fnc_filterString;
|
|
||||||
_name = [_name] call BIS_fnc_filterString;
|
|
||||||
|
|
||||||
_stream_path = _name + "_" + _role + "_" + _uuid_short;
|
|
||||||
|
|
||||||
armatak_mediamtx_video_stream_instance_address = GETMVAR(instance_address,false);
|
|
||||||
armatak_mediamtx_video_stream_instance_port = missionNamespace getVariable "instance_port";
|
|
||||||
armatak_mediamtx_video_stream_instance_auth_user = missionNamespace getVariable "instance_auth_user";
|
|
||||||
armatak_mediamtx_video_stream_instance_auth_pass = missionNamespace getVariable "instance_auth_pass";
|
|
||||||
|
|
||||||
"armatak" callExtension ["video_stream:start", [armatak_mediamtx_video_stream_instance_address, armatak_mediamtx_video_stream_instance_port, _stream_path, armatak_mediamtx_video_stream_instance_auth_user, armatak_mediamtx_video_stream_instance_auth_pass]];
|
|
||||||
(_this select 0) setVariable ["armatak_video_feed_is_streaming", true];
|
|
||||||
},
|
|
||||||
{
|
|
||||||
(_this select 0) getVariable "armatak_video_feed_is_streaming" == false
|
|
||||||
}
|
|
||||||
] call ace_interact_menu_fnc_createAction;
|
|
||||||
[
|
|
||||||
"Man",
|
|
||||||
1,
|
|
||||||
["ACE_SelfActions"],
|
|
||||||
_startAction,
|
|
||||||
true
|
|
||||||
] call ace_interact_menu_fnc_addActionToClass;
|
|
||||||
|
|
||||||
_stopAction = [
|
|
||||||
"ArmatakStopStream",
|
|
||||||
"Stop Video Feed",
|
|
||||||
"",
|
|
||||||
{
|
|
||||||
"armatak" callExtension ["video_stream:stop", []];
|
|
||||||
SETVAR(_this select 0,GVAR(isStreaming),false);
|
|
||||||
},
|
|
||||||
{
|
|
||||||
GETVAR((this select 0),GVAR(isStreaming),false)
|
|
||||||
}
|
|
||||||
] call ace_interact_menu_fnc_createAction;
|
|
||||||
[
|
|
||||||
"Man",
|
|
||||||
1,
|
|
||||||
["ACE_SelfActions"],
|
|
||||||
_stopAction,
|
|
||||||
true
|
|
||||||
] call ace_interact_menu_fnc_addActionToClass;
|
|
||||||
if (isMultiplayer) then {
|
|
||||||
{
|
|
||||||
SETVAR(_x,GVAR(isStreaming),false);
|
|
||||||
} forEach playableUnits;
|
|
||||||
} else {
|
|
||||||
SETVAR(player,GVAR(isStreaming),false);
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
true;
|
|
||||||
BIN
media/delta_larp.jpg
Normal file
BIN
media/delta_larp.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 143 KiB |
BIN
media/picture.png
LFS
Normal file
BIN
media/picture.png
LFS
Normal file
Binary file not shown.
@@ -1,5 +1,6 @@
|
|||||||
use uuid::Uuid;
|
use super::video::video_detail_xml;
|
||||||
use chrono::{Duration, SecondsFormat, Utc};
|
use chrono::{Duration, SecondsFormat, Utc};
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
pub struct CursorOverTime {
|
pub struct CursorOverTime {
|
||||||
pub uuid: Option<String>,
|
pub uuid: Option<String>,
|
||||||
@@ -16,6 +17,7 @@ pub struct CursorOverTime {
|
|||||||
pub track_speed: Option<f32>,
|
pub track_speed: Option<f32>,
|
||||||
pub link_uid: Option<String>,
|
pub link_uid: Option<String>,
|
||||||
pub remarker: Option<String>,
|
pub remarker: Option<String>,
|
||||||
|
pub video_url: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CursorOverTime {
|
impl CursorOverTime {
|
||||||
@@ -107,6 +109,12 @@ impl CursorOverTime {
|
|||||||
xml.push_str(format!("<remarks>ARMATAK | {}</remarks>", remark).as_str());
|
xml.push_str(format!("<remarks>ARMATAK | {}</remarks>", remark).as_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(video_url) = &self.video_url {
|
||||||
|
if !video_url.trim().is_empty() {
|
||||||
|
xml.push_str(&video_detail_xml(video_url, uuid, &self.contact_callsign));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
xml.push_str("</detail></event>");
|
xml.push_str("</detail></event>");
|
||||||
|
|
||||||
return xml;
|
return xml;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use arma_rs::{FromArma, FromArmaError};
|
|
||||||
use super::cot::CursorOverTime;
|
use super::cot::CursorOverTime;
|
||||||
|
use arma_rs::{FromArma, FromArmaError};
|
||||||
|
|
||||||
pub struct DigitalPointerPayload {
|
pub struct DigitalPointerPayload {
|
||||||
pub link_uid: String,
|
pub link_uid: String,
|
||||||
@@ -40,6 +40,8 @@ impl DigitalPointerPayload {
|
|||||||
track_speed: None,
|
track_speed: None,
|
||||||
link_uid: Some(self.link_uid.clone()),
|
link_uid: Some(self.link_uid.clone()),
|
||||||
remarker: None,
|
remarker: None,
|
||||||
|
video_url: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
126
src/cot/draws/circle.rs
Normal file
126
src/cot/draws/circle.rs
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
use arma_rs::{FromArma, FromArmaError};
|
||||||
|
|
||||||
|
pub struct CircleCoTPayload {
|
||||||
|
pub uuid: String,
|
||||||
|
pub center_lat: f64,
|
||||||
|
pub center_lon: f64,
|
||||||
|
pub center_hae: f32,
|
||||||
|
pub major: f64,
|
||||||
|
pub minor: f64,
|
||||||
|
pub angle: f32,
|
||||||
|
pub callsign: String,
|
||||||
|
pub creator_uid: String,
|
||||||
|
pub creator_callsign: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromArma for CircleCoTPayload {
|
||||||
|
fn from_arma(data: String) -> Result<Self, FromArmaError> {
|
||||||
|
let (
|
||||||
|
uuid,
|
||||||
|
center_lat,
|
||||||
|
center_lon,
|
||||||
|
center_hae,
|
||||||
|
major,
|
||||||
|
minor,
|
||||||
|
angle,
|
||||||
|
callsign,
|
||||||
|
creator_uid,
|
||||||
|
creator_callsign,
|
||||||
|
) = <(String, f64, f64, f32, f64, f64, f32, String, String, String)>::from_arma(data)?;
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
uuid,
|
||||||
|
center_lat,
|
||||||
|
center_lon,
|
||||||
|
center_hae,
|
||||||
|
major,
|
||||||
|
minor,
|
||||||
|
angle,
|
||||||
|
callsign,
|
||||||
|
creator_uid,
|
||||||
|
creator_callsign,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ShapeCircleCoT {
|
||||||
|
pub uid: String,
|
||||||
|
pub lat: f64,
|
||||||
|
pub lon: f64,
|
||||||
|
pub hae: f32,
|
||||||
|
pub major: f64,
|
||||||
|
pub minor: f64,
|
||||||
|
pub angle: f32,
|
||||||
|
pub callsign: String,
|
||||||
|
pub creator_uid: String,
|
||||||
|
pub creator_callsign: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CircleCoTPayload {
|
||||||
|
pub fn to_cot(&self) -> ShapeCircleCoT {
|
||||||
|
ShapeCircleCoT {
|
||||||
|
uid: self.uuid.clone(),
|
||||||
|
lat: self.center_lat,
|
||||||
|
lon: self.center_lon,
|
||||||
|
hae: self.center_hae,
|
||||||
|
major: self.major,
|
||||||
|
minor: self.minor,
|
||||||
|
angle: self.angle,
|
||||||
|
callsign: self.callsign.clone(),
|
||||||
|
creator_uid: self.creator_uid.clone(),
|
||||||
|
creator_callsign: self.creator_callsign.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ShapeCircleCoT {
|
||||||
|
pub fn to_xml(&self, now: &str, stale: &str) -> String {
|
||||||
|
format!(
|
||||||
|
r#"<event version="2.0" uid="{uid}" type="u-d-c-c"
|
||||||
|
time="{t}" start="{t}" stale="{stale}"
|
||||||
|
how="h-e" access="Undefined">
|
||||||
|
<point lat="{lat}" lon="{lon}" hae="{hae}" ce="10.9" le="9999999.0" />
|
||||||
|
<detail>
|
||||||
|
<shape>
|
||||||
|
<ellipse major="{major}" minor="{minor}" angle="{angle}" />
|
||||||
|
<link uid="{uid}.Style" type="b-x-KmlStyle" relation="p-c">
|
||||||
|
<Style>
|
||||||
|
<LineStyle>
|
||||||
|
<color>ffffffff</color>
|
||||||
|
<width>3.0</width>
|
||||||
|
</LineStyle>
|
||||||
|
<PolyStyle>
|
||||||
|
<color>96ffffff</color>
|
||||||
|
</PolyStyle>
|
||||||
|
</Style>
|
||||||
|
</link>
|
||||||
|
<link uid="{creator_uid}" type="self" relation="p-p-CenterAnchor" />
|
||||||
|
</shape>
|
||||||
|
<__shapeExtras cpvis="true" editable="true" />
|
||||||
|
<remarks />
|
||||||
|
<contact callsign="{callsign}" />
|
||||||
|
<creator uid="{creator_uid}" callsign="{creator_callsign}" time="{t}" type="a-f-G-U-C" />
|
||||||
|
<archive />
|
||||||
|
<labels_on value="true" />
|
||||||
|
<strokeColor value="-1" />
|
||||||
|
<strokeWeight value="3.0" />
|
||||||
|
<strokeStyle value="solid" />
|
||||||
|
<fillColor value="-1761607681" />
|
||||||
|
<precisionlocation altsrc="GPS" geopointsrc="GPS" />
|
||||||
|
</detail>
|
||||||
|
</event>"#,
|
||||||
|
uid = self.uid,
|
||||||
|
t = now,
|
||||||
|
stale = stale,
|
||||||
|
lat = self.lat,
|
||||||
|
lon = self.lon,
|
||||||
|
hae = self.hae,
|
||||||
|
major = self.major,
|
||||||
|
minor = self.minor,
|
||||||
|
angle = self.angle,
|
||||||
|
callsign = self.callsign,
|
||||||
|
creator_uid = self.creator_uid,
|
||||||
|
creator_callsign = self.creator_callsign
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
1
src/cot/draws/mod.rs
Normal file
1
src/cot/draws/mod.rs
Normal file
@@ -0,0 +1 @@
|
|||||||
|
pub mod circle;
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
use arma_rs::{FromArma, FromArmaError};
|
|
||||||
use super::cot::CursorOverTime;
|
use super::cot::CursorOverTime;
|
||||||
|
use arma_rs::{FromArma, FromArmaError};
|
||||||
|
|
||||||
pub struct EudCoTPayload {
|
pub struct EudCoTPayload {
|
||||||
pub uuid: String,
|
pub uuid: String,
|
||||||
@@ -57,6 +57,8 @@ impl EudCoTPayload {
|
|||||||
track_speed: Some(self.track_speed),
|
track_speed: Some(self.track_speed),
|
||||||
link_uid: None,
|
link_uid: None,
|
||||||
remarker: None,
|
remarker: None,
|
||||||
|
video_url: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use arma_rs::{FromArma, FromArmaError};
|
|
||||||
use super::cot::CursorOverTime;
|
use super::cot::CursorOverTime;
|
||||||
|
use arma_rs::{FromArma, FromArmaError};
|
||||||
|
|
||||||
pub struct ExternalPositionPayload {
|
pub struct ExternalPositionPayload {
|
||||||
pub uuid: String,
|
pub uuid: String,
|
||||||
@@ -54,6 +54,8 @@ impl ExternalPositionPayload {
|
|||||||
track_speed: Some(self.track_speed),
|
track_speed: Some(self.track_speed),
|
||||||
link_uid: None,
|
link_uid: None,
|
||||||
remarker: Some(self.remarker.clone()),
|
remarker: Some(self.remarker.clone()),
|
||||||
|
video_url: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
147
src/cot/message.rs
Normal file
147
src/cot/message.rs
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
use arma_rs::{FromArma, FromArmaError};
|
||||||
|
use chrono::{Duration, SecondsFormat, Utc};
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
pub struct MessagePayload {
|
||||||
|
pub sender_callsign: String,
|
||||||
|
pub chatroom: String,
|
||||||
|
pub message_text: String,
|
||||||
|
pub point_lat: f64,
|
||||||
|
pub point_lon: f64,
|
||||||
|
pub point_hae: f32,
|
||||||
|
pub sender_uid: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromArma for MessagePayload {
|
||||||
|
fn from_arma(data: String) -> Result<Self, FromArmaError> {
|
||||||
|
let (sender_callsign, chatroom, message_text, point_lat, point_lon, point_hae, sender_uid) =
|
||||||
|
<(String, String, String, f64, f64, f32, String)>::from_arma(data)?;
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
sender_callsign,
|
||||||
|
chatroom,
|
||||||
|
message_text,
|
||||||
|
point_lat,
|
||||||
|
point_lon,
|
||||||
|
point_hae,
|
||||||
|
sender_uid,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct MessageCot {
|
||||||
|
pub sender_callsign: String,
|
||||||
|
pub chatroom: String,
|
||||||
|
pub message_text: String,
|
||||||
|
pub point_lat: f64,
|
||||||
|
pub point_lon: f64,
|
||||||
|
pub point_hae: f32,
|
||||||
|
pub sender_uid: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MessageCot {
|
||||||
|
pub fn from_payload(p: MessagePayload) -> Self {
|
||||||
|
Self {
|
||||||
|
sender_callsign: p.sender_callsign,
|
||||||
|
chatroom: p.chatroom,
|
||||||
|
message_text: p.message_text,
|
||||||
|
point_lat: p.point_lat,
|
||||||
|
point_lon: p.point_lon,
|
||||||
|
point_hae: p.point_hae,
|
||||||
|
sender_uid: p.sender_uid,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_xml(&self) -> String {
|
||||||
|
let created_time = Utc::now().to_rfc3339_opts(SecondsFormat::Millis, true);
|
||||||
|
let stale_time =
|
||||||
|
(Utc::now() + Duration::days(1)).to_rfc3339_opts(SecondsFormat::Millis, true);
|
||||||
|
|
||||||
|
// MESSAGE ID (random UUID)
|
||||||
|
let message_uuid = Uuid::new_v4().to_string();
|
||||||
|
|
||||||
|
// FULL EVENT UID
|
||||||
|
// format: GeoChat.{sender}.{chatroom}.{uuid}
|
||||||
|
let event_uid = format!(
|
||||||
|
"GeoChat.{}.{}.{}",
|
||||||
|
self.sender_uid,
|
||||||
|
self.chatroom.replace(" ", "_"),
|
||||||
|
message_uuid,
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut xml = String::new();
|
||||||
|
|
||||||
|
xml.push_str("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
|
||||||
|
|
||||||
|
xml.push_str(
|
||||||
|
format!(
|
||||||
|
"<event version=\"2.0\" uid=\"{}\" type=\"b-t-f\" time=\"{}\" start=\"{}\" stale=\"{}\" how=\"h-g-i-g-o\" access=\"Undefined\">",
|
||||||
|
event_uid, created_time, created_time, stale_time
|
||||||
|
)
|
||||||
|
.as_str(),
|
||||||
|
);
|
||||||
|
|
||||||
|
xml.push_str(
|
||||||
|
format!(
|
||||||
|
"<point lat=\"{}\" lon=\"{}\" hae=\"{}\" ce=\"10.3\" le=\"9999999.0\"/>",
|
||||||
|
self.point_lat, self.point_lon, self.point_hae
|
||||||
|
)
|
||||||
|
.as_str(),
|
||||||
|
);
|
||||||
|
|
||||||
|
xml.push_str("<detail>");
|
||||||
|
|
||||||
|
// ========== CHAT OBJECT ==========
|
||||||
|
|
||||||
|
xml.push_str(
|
||||||
|
format!(
|
||||||
|
"<__chat parent=\"RootContactGroup\" groupOwner=\"false\" \
|
||||||
|
messageId=\"{}\" chatroom=\"{}\" id=\"{}\" senderCallsign=\"{}\">",
|
||||||
|
message_uuid, self.chatroom, self.chatroom, self.sender_callsign,
|
||||||
|
)
|
||||||
|
.as_str(),
|
||||||
|
);
|
||||||
|
|
||||||
|
xml.push_str(
|
||||||
|
format!(
|
||||||
|
"<chatgrp uid0=\"{}\" uid1=\"{}\" id=\"{}\" />",
|
||||||
|
self.sender_uid, self.chatroom, self.chatroom
|
||||||
|
)
|
||||||
|
.as_str(),
|
||||||
|
);
|
||||||
|
|
||||||
|
xml.push_str("</__chat>");
|
||||||
|
|
||||||
|
// ========== LINK ELEMENT ==========
|
||||||
|
xml.push_str(
|
||||||
|
format!(
|
||||||
|
"<link uid=\"{}\" type=\"a-f-G-U-C\" relation=\"p-p\" />",
|
||||||
|
self.sender_uid
|
||||||
|
)
|
||||||
|
.as_str(),
|
||||||
|
);
|
||||||
|
|
||||||
|
// ========== SERVER DEST ==========
|
||||||
|
// This is optional — you may remove or customize it
|
||||||
|
xml.push_str(
|
||||||
|
format!(
|
||||||
|
"<__serverdestination destinations=\"0.0.0.0:0:tcp:{}\" />",
|
||||||
|
self.sender_uid
|
||||||
|
)
|
||||||
|
.as_str(),
|
||||||
|
);
|
||||||
|
|
||||||
|
// ========== MESSAGE REMARKS ==========
|
||||||
|
xml.push_str(
|
||||||
|
format!(
|
||||||
|
"<remarks source=\"ARMATAK.{}\" to=\"{}\" time=\"{}\">{}</remarks>",
|
||||||
|
self.sender_uid, self.chatroom, created_time, self.message_text
|
||||||
|
)
|
||||||
|
.as_str(),
|
||||||
|
);
|
||||||
|
|
||||||
|
xml.push_str("</detail></event>");
|
||||||
|
|
||||||
|
xml
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,9 @@
|
|||||||
pub mod cot;
|
pub mod cot;
|
||||||
pub mod digital_pointer;
|
pub mod digital_pointer;
|
||||||
|
pub mod draws;
|
||||||
pub mod eud;
|
pub mod eud;
|
||||||
pub mod gps;
|
pub mod gps;
|
||||||
|
pub mod message;
|
||||||
pub mod nato;
|
pub mod nato;
|
||||||
|
pub mod uas;
|
||||||
|
pub mod video;
|
||||||
|
|||||||
@@ -11,10 +11,40 @@ pub struct MarkerCoTPayload {
|
|||||||
pub contact_callsign: String,
|
pub contact_callsign: String,
|
||||||
pub track_course: i32,
|
pub track_course: i32,
|
||||||
pub track_speed: f32,
|
pub track_speed: f32,
|
||||||
|
pub video_url: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromArma for MarkerCoTPayload {
|
impl FromArma for MarkerCoTPayload {
|
||||||
fn from_arma(data: String) -> Result<MarkerCoTPayload, FromArmaError> {
|
fn from_arma(data: String) -> Result<MarkerCoTPayload, FromArmaError> {
|
||||||
|
if let Ok((
|
||||||
|
uuid,
|
||||||
|
r#type,
|
||||||
|
point_lat,
|
||||||
|
point_lon,
|
||||||
|
point_hae,
|
||||||
|
contact_callsign,
|
||||||
|
track_course,
|
||||||
|
track_speed,
|
||||||
|
video_url,
|
||||||
|
)) = <(String, String, f64, f64, f32, String, i32, f32, String)>::from_arma(data.clone())
|
||||||
|
{
|
||||||
|
return Ok(Self {
|
||||||
|
uuid,
|
||||||
|
r#type,
|
||||||
|
point_lat,
|
||||||
|
point_lon,
|
||||||
|
point_hae,
|
||||||
|
contact_callsign,
|
||||||
|
track_course,
|
||||||
|
track_speed,
|
||||||
|
video_url: if video_url.trim().is_empty() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(video_url)
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
let (
|
let (
|
||||||
uuid,
|
uuid,
|
||||||
r#type,
|
r#type,
|
||||||
@@ -34,6 +64,7 @@ impl FromArma for MarkerCoTPayload {
|
|||||||
contact_callsign,
|
contact_callsign,
|
||||||
track_course,
|
track_course,
|
||||||
track_speed,
|
track_speed,
|
||||||
|
video_url: None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -55,6 +86,7 @@ impl MarkerCoTPayload {
|
|||||||
track_speed: Some(self.track_speed),
|
track_speed: Some(self.track_speed),
|
||||||
link_uid: None,
|
link_uid: None,
|
||||||
remarker: None,
|
remarker: None,
|
||||||
|
video_url: self.video_url.clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
330
src/cot/uas.rs
Normal file
330
src/cot/uas.rs
Normal file
@@ -0,0 +1,330 @@
|
|||||||
|
use super::video::video_detail_xml;
|
||||||
|
use arma_rs::{FromArma, FromArmaError};
|
||||||
|
use chrono::{Duration, SecondsFormat, Utc};
|
||||||
|
|
||||||
|
fn escape_xml(value: &str) -> String {
|
||||||
|
value
|
||||||
|
.replace('&', "&")
|
||||||
|
.replace('"', """)
|
||||||
|
.replace('<', "<")
|
||||||
|
.replace('>', ">")
|
||||||
|
.replace('\'', "'")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_rtsp_url(url: &str) -> Option<(String, String, String)> {
|
||||||
|
let without_proto = url.strip_prefix("rtsp://")?;
|
||||||
|
let slash_pos = without_proto.find('/')?;
|
||||||
|
let host_port = &without_proto[..slash_pos];
|
||||||
|
let path = &without_proto[slash_pos..];
|
||||||
|
let colon_pos = host_port.rfind(':')?;
|
||||||
|
let address = host_port[..colon_pos].to_string();
|
||||||
|
let port = host_port[colon_pos + 1..].to_string();
|
||||||
|
Some((address, port, path.to_string()))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct UasPlatformCoTPayload {
|
||||||
|
pub uid: String,
|
||||||
|
pub cot_type: String,
|
||||||
|
pub callsign: String,
|
||||||
|
pub point_lat: f64,
|
||||||
|
pub point_lon: f64,
|
||||||
|
pub point_hae: f32,
|
||||||
|
pub track_course: i32,
|
||||||
|
pub track_speed: f32,
|
||||||
|
pub sensor_azimuth: i32,
|
||||||
|
pub sensor_elevation: i32,
|
||||||
|
pub sensor_fov: i32,
|
||||||
|
pub sensor_vfov: i32,
|
||||||
|
pub sensor_range: i32,
|
||||||
|
pub attitude_yaw: i32,
|
||||||
|
pub attitude_pitch: f32,
|
||||||
|
pub attitude_roll: f32,
|
||||||
|
pub hal: f32,
|
||||||
|
pub vehicle_type_tag: String,
|
||||||
|
pub is_flying: i32,
|
||||||
|
pub link_uid: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromArma for UasPlatformCoTPayload {
|
||||||
|
fn from_arma(data: String) -> Result<UasPlatformCoTPayload, FromArmaError> {
|
||||||
|
let (
|
||||||
|
uid,
|
||||||
|
cot_type,
|
||||||
|
callsign,
|
||||||
|
point_lat,
|
||||||
|
point_lon,
|
||||||
|
point_hae,
|
||||||
|
track_course,
|
||||||
|
track_speed,
|
||||||
|
sensor_azimuth,
|
||||||
|
sensor_elevation,
|
||||||
|
sensor_fov,
|
||||||
|
sensor_vfov,
|
||||||
|
sensor_range,
|
||||||
|
attitude_yaw,
|
||||||
|
attitude_pitch,
|
||||||
|
attitude_roll,
|
||||||
|
hal,
|
||||||
|
vehicle_type_tag,
|
||||||
|
is_flying,
|
||||||
|
link_uid,
|
||||||
|
) = <(
|
||||||
|
String,
|
||||||
|
String,
|
||||||
|
String,
|
||||||
|
f64,
|
||||||
|
f64,
|
||||||
|
f32,
|
||||||
|
i32,
|
||||||
|
f32,
|
||||||
|
i32,
|
||||||
|
i32,
|
||||||
|
i32,
|
||||||
|
i32,
|
||||||
|
i32,
|
||||||
|
i32,
|
||||||
|
f32,
|
||||||
|
f32,
|
||||||
|
f32,
|
||||||
|
String,
|
||||||
|
i32,
|
||||||
|
String,
|
||||||
|
)>::from_arma(data)?;
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
uid,
|
||||||
|
cot_type,
|
||||||
|
callsign,
|
||||||
|
point_lat,
|
||||||
|
point_lon,
|
||||||
|
point_hae,
|
||||||
|
track_course,
|
||||||
|
track_speed,
|
||||||
|
sensor_azimuth,
|
||||||
|
sensor_elevation,
|
||||||
|
sensor_fov,
|
||||||
|
sensor_vfov,
|
||||||
|
sensor_range,
|
||||||
|
attitude_yaw,
|
||||||
|
attitude_pitch,
|
||||||
|
attitude_roll,
|
||||||
|
hal,
|
||||||
|
vehicle_type_tag,
|
||||||
|
is_flying,
|
||||||
|
link_uid,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UasPlatformCoTPayload {
|
||||||
|
pub fn to_xml(&self) -> String {
|
||||||
|
let uid = escape_xml(&self.uid);
|
||||||
|
let cot_type = escape_xml(&self.cot_type);
|
||||||
|
let callsign = escape_xml(&self.callsign);
|
||||||
|
let link_uid = escape_xml(&self.link_uid);
|
||||||
|
let (vehicle_type_tag, video_url) =
|
||||||
|
match self.vehicle_type_tag.split_once("|armatak_video_url=") {
|
||||||
|
Some((vehicle_type_tag, video_url)) => (
|
||||||
|
escape_xml(vehicle_type_tag),
|
||||||
|
Some(escape_xml(video_url.trim())).filter(|value| !value.is_empty()),
|
||||||
|
),
|
||||||
|
None => (escape_xml(&self.vehicle_type_tag), None),
|
||||||
|
};
|
||||||
|
let now = Utc::now().to_rfc3339_opts(SecondsFormat::Millis, true);
|
||||||
|
let stale =
|
||||||
|
(Utc::now() + Duration::milliseconds(3500)).to_rfc3339_opts(SecondsFormat::Millis, true);
|
||||||
|
|
||||||
|
let mut xml = String::new();
|
||||||
|
xml.push_str("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>");
|
||||||
|
xml.push_str(&format!(
|
||||||
|
"<event version=\"2.0\" uid=\"{uid}\" type=\"{cot_type}\" time=\"{now}\" start=\"{now}\" stale=\"{stale}\" how=\"m-g\" access=\"Undefined\">",
|
||||||
|
cot_type = cot_type,
|
||||||
|
uid = uid,
|
||||||
|
now = now,
|
||||||
|
stale = stale,
|
||||||
|
));
|
||||||
|
xml.push_str(&format!(
|
||||||
|
"<point lat=\"{lat}\" lon=\"{lon}\" hae=\"{hae}\" ce=\"9999999.0\" le=\"9999999.0\"/>",
|
||||||
|
lat = self.point_lat,
|
||||||
|
lon = self.point_lon,
|
||||||
|
hae = self.point_hae,
|
||||||
|
));
|
||||||
|
xml.push_str("<detail>");
|
||||||
|
xml.push_str("<_uastool extendedCot=\"true\" activeRoute=\"false\"/>");
|
||||||
|
xml.push_str(&format!(
|
||||||
|
"<track course=\"{}\" slope=\"0.0\" speed=\"{}\"/>",
|
||||||
|
self.track_course,
|
||||||
|
self.track_speed,
|
||||||
|
));
|
||||||
|
xml.push_str(&format!(
|
||||||
|
"<sensor elevation=\"{}\" vfov=\"{}\" north=\"0.0\" roll=\"0.0\" range=\"{}\" azimuth=\"{}\" fov=\"{}\" type=\"r-e\" version=\"0.6\"/>",
|
||||||
|
self.sensor_elevation,
|
||||||
|
self.sensor_vfov,
|
||||||
|
self.sensor_range,
|
||||||
|
self.sensor_azimuth,
|
||||||
|
self.sensor_fov,
|
||||||
|
));
|
||||||
|
xml.push_str(&format!(
|
||||||
|
"<spatial><attitude roll=\"{}\" pitch=\"{}\" yaw=\"{}\"/><spin roll=\"0.0\" pitch=\"0.0\" yaw=\"0.0\"/></spatial>",
|
||||||
|
self.attitude_roll,
|
||||||
|
self.attitude_pitch,
|
||||||
|
self.attitude_yaw,
|
||||||
|
));
|
||||||
|
xml.push_str(&format!(
|
||||||
|
"<vehicle goHomeBatteryPercent=\"-2147483648\" hal=\"{}\" flightTimeRemaining=\"-2147483648\" typeTag=\"{}\" batteryRemainingCapacity=\"-2147483648\" isFlying=\"{}\" flightTime=\"-2147483648\" type=\"Generic\" batteryMaxCapacity=\"-2147483648\"/>",
|
||||||
|
self.hal,
|
||||||
|
vehicle_type_tag,
|
||||||
|
if self.is_flying != 0 { "true" } else { "false" },
|
||||||
|
));
|
||||||
|
xml.push_str("<_radio rssi=\"-2147483648\" gps=\"false\"/>");
|
||||||
|
xml.push_str(&format!("<contact callsign=\"{}\"/>", callsign));
|
||||||
|
xml.push_str("<waypointCollection></waypointCollection>");
|
||||||
|
xml.push_str(&format!("<_route sender=\"{}\"/>", link_uid));
|
||||||
|
xml.push_str("<commandedData climbRate=\"0.0\"/>");
|
||||||
|
if let Some(video_url) = video_url {
|
||||||
|
xml.push_str(&video_detail_xml(&video_url, &self.uid, &self.callsign));
|
||||||
|
} else {
|
||||||
|
xml.push_str("<__video></__video>");
|
||||||
|
}
|
||||||
|
xml.push_str(&format!("<link uid=\"{}\" type=\"a-f-G-U-C\" relation=\"p-p\" />", link_uid));
|
||||||
|
xml.push_str("</detail></event>");
|
||||||
|
xml
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct UasVideoCoTPayload {
|
||||||
|
pub uid: String,
|
||||||
|
pub callsign: String,
|
||||||
|
pub video_url: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromArma for UasVideoCoTPayload {
|
||||||
|
fn from_arma(data: String) -> Result<UasVideoCoTPayload, FromArmaError> {
|
||||||
|
let (uid, callsign, video_url) = <(String, String, String)>::from_arma(data)?;
|
||||||
|
Ok(Self {
|
||||||
|
uid,
|
||||||
|
callsign,
|
||||||
|
video_url,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UasVideoCoTPayload {
|
||||||
|
pub fn to_xml(&self) -> String {
|
||||||
|
let (address, port, path) = match parse_rtsp_url(&self.video_url) {
|
||||||
|
Some(parts) => parts,
|
||||||
|
None => {
|
||||||
|
log::warn!(
|
||||||
|
"UasVideoCoTPayload: could not parse RTSP URL: {}",
|
||||||
|
self.video_url
|
||||||
|
);
|
||||||
|
return String::new();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let callsign = escape_xml(&self.callsign);
|
||||||
|
let uid = escape_xml(&self.uid);
|
||||||
|
let address = escape_xml(&address);
|
||||||
|
let path = escape_xml(&path);
|
||||||
|
|
||||||
|
let now = Utc::now().to_rfc3339_opts(SecondsFormat::Millis, true);
|
||||||
|
let stale =
|
||||||
|
(Utc::now() + Duration::seconds(3600)).to_rfc3339_opts(SecondsFormat::Millis, true);
|
||||||
|
|
||||||
|
let mut xml = String::new();
|
||||||
|
xml.push_str("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>");
|
||||||
|
xml.push_str(&format!(
|
||||||
|
"<event type=\"b-i-v\" version=\"2.0\" how=\"m-g\" uid=\"{uid}\" time=\"{now}\" start=\"{now}\" stale=\"{stale}\">",
|
||||||
|
uid = uid,
|
||||||
|
now = now,
|
||||||
|
stale = stale
|
||||||
|
));
|
||||||
|
xml.push_str(
|
||||||
|
"<point lat=\"0\" lon=\"0\" hae=\"9999999.0\" ce=\"9999999.0\" le=\"9999999.0\"/>",
|
||||||
|
);
|
||||||
|
xml.push_str("<detail>");
|
||||||
|
xml.push_str("<__video>");
|
||||||
|
xml.push_str(&format!(
|
||||||
|
"<ConnectionEntry protocol=\"rtsp\" path=\"{path}\" address=\"{address}\" port=\"{port}\" uid=\"{uid}\" alias=\"{callsign}\" roverPort=\"-1\" rtspReliable=\"0\" ignoreEmbeddedKLV=\"False\" networkTimeout=\"0\" bufferTime=\"-1\"/>",
|
||||||
|
path = path,
|
||||||
|
address = address,
|
||||||
|
port = port,
|
||||||
|
uid = uid,
|
||||||
|
callsign = callsign,
|
||||||
|
));
|
||||||
|
xml.push_str("</__video>");
|
||||||
|
xml.push_str(&format!("<contact callsign=\"{}\"/>", callsign));
|
||||||
|
xml.push_str("</detail>");
|
||||||
|
xml.push_str("</event>");
|
||||||
|
xml
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct UasSensorCoTPayload {
|
||||||
|
pub uid: String,
|
||||||
|
pub video_uid: String,
|
||||||
|
pub callsign: String,
|
||||||
|
pub point_lat: f64,
|
||||||
|
pub point_lon: f64,
|
||||||
|
pub point_hae: f32,
|
||||||
|
pub azimuth: i32,
|
||||||
|
pub fov: i32,
|
||||||
|
pub range: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromArma for UasSensorCoTPayload {
|
||||||
|
fn from_arma(data: String) -> Result<UasSensorCoTPayload, FromArmaError> {
|
||||||
|
let (uid, video_uid, callsign, point_lat, point_lon, point_hae, azimuth, fov, range) =
|
||||||
|
<(String, String, String, f64, f64, f32, i32, i32, i32)>::from_arma(data)?;
|
||||||
|
Ok(Self {
|
||||||
|
uid,
|
||||||
|
video_uid,
|
||||||
|
callsign,
|
||||||
|
point_lat,
|
||||||
|
point_lon,
|
||||||
|
point_hae,
|
||||||
|
azimuth,
|
||||||
|
fov,
|
||||||
|
range,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UasSensorCoTPayload {
|
||||||
|
pub fn to_xml(&self) -> String {
|
||||||
|
let uid = escape_xml(&self.uid);
|
||||||
|
let video_uid = escape_xml(&self.video_uid);
|
||||||
|
let callsign = escape_xml(&self.callsign);
|
||||||
|
let now = Utc::now().to_rfc3339_opts(SecondsFormat::Millis, true);
|
||||||
|
let stale =
|
||||||
|
(Utc::now() + Duration::seconds(60)).to_rfc3339_opts(SecondsFormat::Millis, true);
|
||||||
|
|
||||||
|
let mut xml = String::new();
|
||||||
|
xml.push_str("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>");
|
||||||
|
xml.push_str(&format!(
|
||||||
|
"<event type=\"b-m-p-s-p-loc\" version=\"2.0\" how=\"h-g-i-g-o\" uid=\"{uid}\" time=\"{now}\" start=\"{now}\" stale=\"{stale}\">",
|
||||||
|
uid = uid,
|
||||||
|
now = now,
|
||||||
|
stale = stale,
|
||||||
|
));
|
||||||
|
xml.push_str(&format!(
|
||||||
|
"<point lat=\"{lat}\" lon=\"{lon}\" hae=\"{hae}\" ce=\"9999999.0\" le=\"9999999.0\"/>",
|
||||||
|
lat = self.point_lat,
|
||||||
|
lon = self.point_lon,
|
||||||
|
hae = self.point_hae,
|
||||||
|
));
|
||||||
|
xml.push_str("<detail>");
|
||||||
|
xml.push_str(&format!(
|
||||||
|
"<sensor fov=\"{fov}\" fovRed=\"1\" fovGreen=\"1\" fovBlue=\"1\" fovAlpha=\"0.5372549\" displayMagneticReference=\"0\" range=\"{range}\" azimuth=\"{az}\"/>",
|
||||||
|
fov = self.fov,
|
||||||
|
range = self.range,
|
||||||
|
az = self.azimuth,
|
||||||
|
));
|
||||||
|
xml.push_str(&format!("<__video uid=\"{}\"/>", video_uid));
|
||||||
|
xml.push_str(&format!("<contact callsign=\"{}\"/>", callsign));
|
||||||
|
xml.push_str("</detail>");
|
||||||
|
xml.push_str("</event>");
|
||||||
|
xml
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
53
src/cot/video.rs
Normal file
53
src/cot/video.rs
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
fn escape_xml_attribute(value: &str) -> String {
|
||||||
|
value
|
||||||
|
.replace('&', "&")
|
||||||
|
.replace('"', """)
|
||||||
|
.replace('<', "<")
|
||||||
|
.replace('>', ">")
|
||||||
|
.replace('\'', "'")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_video_url(url: &str) -> Option<(String, String, String, String)> {
|
||||||
|
let (protocol, rest) = url.trim().split_once("://")?;
|
||||||
|
let (authority, path) = match rest.split_once('/') {
|
||||||
|
Some((authority, path)) => (authority, format!("/{}", path)),
|
||||||
|
None => (rest, String::new()),
|
||||||
|
};
|
||||||
|
let host_port = authority.rsplit_once('@').map_or(authority, |(_, host_port)| host_port);
|
||||||
|
let (address, port) = host_port.rsplit_once(':')?;
|
||||||
|
|
||||||
|
if protocol.is_empty() || address.is_empty() || port.is_empty() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
Some((
|
||||||
|
protocol.to_ascii_lowercase(),
|
||||||
|
address.to_string(),
|
||||||
|
port.to_string(),
|
||||||
|
path,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn video_detail_xml(video_url: &str, uid: &str, callsign: &str) -> String {
|
||||||
|
let trimmed_url = video_url.trim();
|
||||||
|
if trimmed_url.is_empty() {
|
||||||
|
return "<__video></__video>".to_string();
|
||||||
|
}
|
||||||
|
|
||||||
|
let Some((protocol, address, port, path)) = parse_video_url(trimmed_url) else {
|
||||||
|
return format!(
|
||||||
|
"<__video url=\"{}\"/>",
|
||||||
|
escape_xml_attribute(trimmed_url)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
format!(
|
||||||
|
"<__video><ConnectionEntry protocol=\"{}\" path=\"{}\" address=\"{}\" port=\"{}\" uid=\"{}\" alias=\"{}\" roverPort=\"-1\" rtspReliable=\"0\" ignoreEmbeddedKLV=\"False\" networkTimeout=\"0\" bufferTime=\"-1\"/></__video>",
|
||||||
|
escape_xml_attribute(&protocol),
|
||||||
|
escape_xml_attribute(&path),
|
||||||
|
escape_xml_attribute(&address),
|
||||||
|
escape_xml_attribute(&port),
|
||||||
|
escape_xml_attribute(uid),
|
||||||
|
escape_xml_attribute(callsign),
|
||||||
|
)
|
||||||
|
}
|
||||||
60
src/lib.rs
60
src/lib.rs
@@ -1,8 +1,11 @@
|
|||||||
use arma_rs::{arma, Extension, Group};
|
use arma_rs::{arma, Extension, Group};
|
||||||
|
use rustls::crypto::aws_lc_rs;
|
||||||
|
mod uas;
|
||||||
|
mod mdns;
|
||||||
mod structs;
|
mod structs;
|
||||||
|
mod tcp;
|
||||||
mod tests;
|
mod tests;
|
||||||
mod udp_socket;
|
mod udp_socket;
|
||||||
mod tcp_socket;
|
|
||||||
mod video_stream;
|
mod video_stream;
|
||||||
|
|
||||||
mod cot;
|
mod cot;
|
||||||
@@ -31,32 +34,69 @@ pub fn init() -> Extension {
|
|||||||
|
|
||||||
log4rs::init_config(config).unwrap();
|
log4rs::init_config(config).unwrap();
|
||||||
|
|
||||||
|
let _ = aws_lc_rs::default_provider().install_default();
|
||||||
|
log::info!("Initialized rustls aws-lc crypto provider.");
|
||||||
|
|
||||||
Extension::build()
|
Extension::build()
|
||||||
.command("local_ip", utils::address::get_local_address)
|
.command("local_ip", utils::address::get_local_address)
|
||||||
.command("uuid", utils::uuid::get_uuid)
|
.command("uuid", utils::uuid::get_uuid)
|
||||||
.command("log", utils::log::log_info)
|
.command("log", utils::log::log_info)
|
||||||
.group("udp_socket",
|
.group(
|
||||||
|
"uas",
|
||||||
|
Group::new()
|
||||||
|
.command("start_endpoint", uas::start_endpoint)
|
||||||
|
.command("stop_endpoint", uas::stop_endpoint)
|
||||||
|
.command("send_uas_telemetry", uas::send_uas_telemetry)
|
||||||
|
.command("send_uas_system", uas::send_uas_system),
|
||||||
|
)
|
||||||
|
.group(
|
||||||
|
"mdns",
|
||||||
|
Group::new()
|
||||||
|
.command("start_uas_advertisement", mdns::start_uas_advertisement)
|
||||||
|
.command("stop", mdns::stop),
|
||||||
|
)
|
||||||
|
.group(
|
||||||
|
"udp_socket",
|
||||||
Group::new()
|
Group::new()
|
||||||
.command("start", udp_socket::start)
|
.command("start", udp_socket::start)
|
||||||
.command("send_payload", udp_socket::send_payload)
|
.command("send_payload", udp_socket::send_payload)
|
||||||
.command("send_gps_cot", udp_socket::send_gps_cot)
|
.command("send_gps_cot", udp_socket::send_gps_cot)
|
||||||
.command("stop", udp_socket::stop)
|
.command("stop", udp_socket::stop),
|
||||||
)
|
)
|
||||||
.group(
|
.group(
|
||||||
"tcp_socket",
|
"tcp_socket",
|
||||||
Group::new()
|
Group::new()
|
||||||
.command("start", tcp_socket::start)
|
.command("start", tcp::start)
|
||||||
.command("send_payload", tcp_socket::send_payload)
|
.command("start_mtls", tcp::start_mtls)
|
||||||
.command("send_eud_cot", tcp_socket::send_eud_cot)
|
.command("start_enroll_mtls", tcp::start_enroll_mtls)
|
||||||
.command("send_marker_cot", tcp_socket::send_marker_cot)
|
.command("stop", tcp::stop)
|
||||||
.command("send_digital_pointer_cot", tcp_socket::send_digital_pointer_cot)
|
.command("send_payload", tcp::send_payload)
|
||||||
.command("stop", tcp_socket::stop)
|
.group(
|
||||||
|
"cot",
|
||||||
|
Group::new()
|
||||||
|
.command("eud", tcp::cot::send_eud_cot)
|
||||||
|
.command("marker", tcp::cot::send_marker_cot)
|
||||||
|
.command("digital_pointer", tcp::cot::send_digital_pointer_cot)
|
||||||
|
.command("chat", tcp::cot::send_message_cot)
|
||||||
|
.command("uas_platform", tcp::cot::send_uas_platform_cot)
|
||||||
|
.command("uas_video", tcp::cot::send_uas_video_cot)
|
||||||
|
.command("uas_sensor", tcp::cot::send_uas_sensor_cot),
|
||||||
|
)
|
||||||
|
.group(
|
||||||
|
"draw",
|
||||||
|
Group::new()
|
||||||
|
.command("circle", tcp::draw::send_circle_cot)
|
||||||
|
.command("ellipse", tcp::draw::send_ellipse_cot)
|
||||||
|
.command("rectangle", tcp::draw::send_rectangle_cot)
|
||||||
|
.command("free", tcp::draw::send_freedraw_cot)
|
||||||
|
.command("vector", tcp::draw::send_vectordraw_cot),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
.group(
|
.group(
|
||||||
"video_stream",
|
"video_stream",
|
||||||
Group::new()
|
Group::new()
|
||||||
.command("start", video_stream::start_stream)
|
.command("start", video_stream::start_stream)
|
||||||
.command("stop", video_stream::stop_stream)
|
.command("stop", video_stream::stop_stream),
|
||||||
)
|
)
|
||||||
.finish()
|
.finish()
|
||||||
}
|
}
|
||||||
|
|||||||
207
src/mdns.rs
Normal file
207
src/mdns.rs
Normal file
@@ -0,0 +1,207 @@
|
|||||||
|
use arma_rs::Context;
|
||||||
|
use lazy_static::lazy_static;
|
||||||
|
use log::info;
|
||||||
|
use std::net::{Ipv4Addr, SocketAddrV4, UdpSocket};
|
||||||
|
use std::sync::mpsc::{self, Receiver, Sender};
|
||||||
|
use std::sync::Mutex;
|
||||||
|
use std::thread;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
static ref MDNS_CTRL: Mutex<Option<Sender<()>>> = Mutex::new(None);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn detect_local_ipv4() -> Result<Ipv4Addr, String> {
|
||||||
|
let socket = UdpSocket::bind("0.0.0.0:0").map_err(|e| e.to_string())?;
|
||||||
|
socket.connect("8.8.8.8:80").map_err(|e| e.to_string())?;
|
||||||
|
match socket.local_addr().map_err(|e| e.to_string())? {
|
||||||
|
std::net::SocketAddr::V4(addr) => Ok(*addr.ip()),
|
||||||
|
std::net::SocketAddr::V6(_) => Err("Local address is not IPv4".to_string()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sanitize_label(value: &str, fallback: &str) -> String {
|
||||||
|
let mut sanitized = value
|
||||||
|
.chars()
|
||||||
|
.map(|c| if c.is_ascii_alphanumeric() || c == '-' { c } else { '-' })
|
||||||
|
.collect::<String>()
|
||||||
|
.trim_matches('-')
|
||||||
|
.to_string();
|
||||||
|
|
||||||
|
if sanitized.is_empty() {
|
||||||
|
sanitized = fallback.to_string();
|
||||||
|
}
|
||||||
|
|
||||||
|
if sanitized.len() > 63 {
|
||||||
|
sanitized.truncate(63);
|
||||||
|
}
|
||||||
|
|
||||||
|
sanitized
|
||||||
|
}
|
||||||
|
|
||||||
|
fn encode_name(name: &str) -> Vec<u8> {
|
||||||
|
let mut encoded = Vec::new();
|
||||||
|
for label in name.split('.') {
|
||||||
|
let bytes = label.as_bytes();
|
||||||
|
encoded.push(bytes.len() as u8);
|
||||||
|
encoded.extend_from_slice(bytes);
|
||||||
|
}
|
||||||
|
encoded.push(0);
|
||||||
|
encoded
|
||||||
|
}
|
||||||
|
|
||||||
|
fn push_u16(buf: &mut Vec<u8>, value: u16) {
|
||||||
|
buf.extend_from_slice(&value.to_be_bytes());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn push_u32(buf: &mut Vec<u8>, value: u32) {
|
||||||
|
buf.extend_from_slice(&value.to_be_bytes());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn push_record(buf: &mut Vec<u8>, name: &str, rr_type: u16, rr_class: u16, ttl: u32, rdata: &[u8]) {
|
||||||
|
buf.extend_from_slice(&encode_name(name));
|
||||||
|
push_u16(buf, rr_type);
|
||||||
|
push_u16(buf, rr_class);
|
||||||
|
push_u32(buf, ttl);
|
||||||
|
push_u16(buf, rdata.len() as u16);
|
||||||
|
buf.extend_from_slice(rdata);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_mdns_packet(instance_name: &str, host_name: &str, ip: Ipv4Addr, port: u16, video_uri: &str) -> Vec<u8> {
|
||||||
|
let service_type = "_mavlink._udp.local";
|
||||||
|
let instance_fqdn = format!("{}.{}", instance_name, service_type);
|
||||||
|
let host_fqdn = format!("{}.local", host_name);
|
||||||
|
|
||||||
|
let mut packet = Vec::new();
|
||||||
|
push_u16(&mut packet, 0);
|
||||||
|
push_u16(&mut packet, 0x8400);
|
||||||
|
push_u16(&mut packet, 0);
|
||||||
|
push_u16(&mut packet, 4);
|
||||||
|
push_u16(&mut packet, 0);
|
||||||
|
push_u16(&mut packet, 0);
|
||||||
|
|
||||||
|
let ptr_rdata = encode_name(&instance_fqdn);
|
||||||
|
push_record(&mut packet, service_type, 12, 0x0001, 120, &ptr_rdata);
|
||||||
|
|
||||||
|
let mut srv_rdata = Vec::new();
|
||||||
|
push_u16(&mut srv_rdata, 0);
|
||||||
|
push_u16(&mut srv_rdata, 0);
|
||||||
|
push_u16(&mut srv_rdata, port);
|
||||||
|
srv_rdata.extend_from_slice(&encode_name(&host_fqdn));
|
||||||
|
push_record(&mut packet, &instance_fqdn, 33, 0x8001, 120, &srv_rdata);
|
||||||
|
|
||||||
|
let txt_value = format!("uri={}", video_uri);
|
||||||
|
let txt_bytes = txt_value.as_bytes();
|
||||||
|
let mut txt_rdata = Vec::new();
|
||||||
|
txt_rdata.push(txt_bytes.len() as u8);
|
||||||
|
txt_rdata.extend_from_slice(txt_bytes);
|
||||||
|
push_record(&mut packet, &instance_fqdn, 16, 0x8001, 120, &txt_rdata);
|
||||||
|
|
||||||
|
let a_rdata = ip.octets();
|
||||||
|
push_record(&mut packet, &host_fqdn, 1, 0x8001, 120, &a_rdata);
|
||||||
|
|
||||||
|
packet
|
||||||
|
}
|
||||||
|
|
||||||
|
fn stop_existing() {
|
||||||
|
if let Ok(mut lock) = MDNS_CTRL.lock() {
|
||||||
|
if let Some(tx) = lock.take() {
|
||||||
|
let _ = tx.send(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn start_uas_advertisement(
|
||||||
|
ctx: Context,
|
||||||
|
instance_name: String,
|
||||||
|
mavlink_port: i32,
|
||||||
|
video_uri: String,
|
||||||
|
) -> &'static str {
|
||||||
|
stop_existing();
|
||||||
|
|
||||||
|
let local_ip = match detect_local_ipv4() {
|
||||||
|
Ok(ip) => ip,
|
||||||
|
Err(error) => {
|
||||||
|
let _ = ctx.callback_data("MDNS ERROR", "Failed to determine local IPv4", error.clone());
|
||||||
|
return "mdns local IPv4 error";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let port = mavlink_port.clamp(1, 65535) as u16;
|
||||||
|
let safe_instance = sanitize_label(&instance_name, "ArmaTAK-UAS");
|
||||||
|
let host_label = sanitize_label(
|
||||||
|
&format!("armatak-{}", safe_instance.to_lowercase()),
|
||||||
|
"armatak-uas-host",
|
||||||
|
);
|
||||||
|
let packet = build_mdns_packet(&safe_instance, &host_label, local_ip, port, &video_uri);
|
||||||
|
let callback_video_uri = video_uri.clone();
|
||||||
|
let multicast_addr = SocketAddrV4::new(Ipv4Addr::new(224, 0, 0, 251), 5353);
|
||||||
|
|
||||||
|
let (stop_tx, stop_rx): (Sender<()>, Receiver<()>) = mpsc::channel();
|
||||||
|
if let Ok(mut lock) = MDNS_CTRL.lock() {
|
||||||
|
*lock = Some(stop_tx);
|
||||||
|
}
|
||||||
|
|
||||||
|
thread::spawn(move || {
|
||||||
|
let socket = match UdpSocket::bind("0.0.0.0:0") {
|
||||||
|
Ok(socket) => socket,
|
||||||
|
Err(error) => {
|
||||||
|
info!("mDNS failed to bind UDP socket: {}", error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let _ = socket.set_multicast_ttl_v4(255);
|
||||||
|
let _ = socket.set_multicast_loop_v4(true);
|
||||||
|
|
||||||
|
info!(
|
||||||
|
"Starting mDNS UAS advertisement instance={} host={} ip={} port={} video_uri={}",
|
||||||
|
safe_instance, host_label, local_ip, port, video_uri
|
||||||
|
);
|
||||||
|
|
||||||
|
loop {
|
||||||
|
match socket.send_to(&packet, multicast_addr) {
|
||||||
|
Ok(size) => info!("Sent mDNS UAS advertisement ({} bytes) to {}", size, multicast_addr),
|
||||||
|
Err(error) => info!("Failed sending mDNS UAS advertisement: {}", error),
|
||||||
|
}
|
||||||
|
|
||||||
|
match stop_rx.recv_timeout(Duration::from_secs(5)) {
|
||||||
|
Ok(_) => break,
|
||||||
|
Err(mpsc::RecvTimeoutError::Timeout) => {}
|
||||||
|
Err(_) => break,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
info!("Stopped mDNS UAS advertisement for instance={}", safe_instance);
|
||||||
|
});
|
||||||
|
|
||||||
|
let _ = ctx.callback_data(
|
||||||
|
"MDNS",
|
||||||
|
"UAS advertisement started",
|
||||||
|
format!("{}:{} | {}", local_ip, port, callback_video_uri),
|
||||||
|
);
|
||||||
|
|
||||||
|
"starting mdns uas advertisement"
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn stop(ctx: Context) -> &'static str {
|
||||||
|
let had_running = match MDNS_CTRL.lock() {
|
||||||
|
Ok(mut lock) => {
|
||||||
|
if let Some(tx) = lock.take() {
|
||||||
|
let _ = tx.send(());
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(_) => false,
|
||||||
|
};
|
||||||
|
|
||||||
|
if had_running {
|
||||||
|
let _ = ctx.callback_null("MDNS", "UAS advertisement stopped");
|
||||||
|
"stopping mdns advertisement"
|
||||||
|
} else {
|
||||||
|
let _ = ctx.callback_null("MDNS ERROR", "No mDNS advertisement is running");
|
||||||
|
"no mdns advertisement running"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,9 +10,6 @@ pub struct LogPayload {
|
|||||||
impl FromArma for LogPayload {
|
impl FromArma for LogPayload {
|
||||||
fn from_arma(data: String) -> Result<LogPayload, FromArmaError> {
|
fn from_arma(data: String) -> Result<LogPayload, FromArmaError> {
|
||||||
let (status, message) = <(String, String)>::from_arma(data)?;
|
let (status, message) = <(String, String)>::from_arma(data)?;
|
||||||
Ok(Self {
|
Ok(Self { status, message })
|
||||||
status,
|
|
||||||
message
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
280
src/tcp/client.rs
Normal file
280
src/tcp/client.rs
Normal file
@@ -0,0 +1,280 @@
|
|||||||
|
use arma_rs::Context;
|
||||||
|
use log::{info, warn};
|
||||||
|
use std::collections::VecDeque;
|
||||||
|
use std::panic::{self, AssertUnwindSafe};
|
||||||
|
use std::sync::mpsc::{self, Receiver, RecvTimeoutError, Sender, TryRecvError};
|
||||||
|
use std::thread;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use super::config::ConnectionConfig;
|
||||||
|
use super::transport::{connect_stream, TransportStream};
|
||||||
|
use super::TCP_CLIENT;
|
||||||
|
|
||||||
|
const CONNECT_POLL_INTERVAL: Duration = Duration::from_millis(200);
|
||||||
|
const MAX_PENDING_MESSAGES: usize = 128;
|
||||||
|
|
||||||
|
pub enum TcpCommand {
|
||||||
|
SendMessage(String, Context),
|
||||||
|
Stop,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct TcpClient {
|
||||||
|
pub(crate) tx: Sender<TcpCommand>,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum ConnectionState {
|
||||||
|
Connecting,
|
||||||
|
Connected,
|
||||||
|
Failed(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
enum ConnectEvent {
|
||||||
|
Connected(TransportStream),
|
||||||
|
Failed(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
fn describe_panic_payload(payload: Box<dyn std::any::Any + Send>) -> String {
|
||||||
|
if let Some(message) = payload.downcast_ref::<&str>() {
|
||||||
|
(*message).to_string()
|
||||||
|
} else if let Some(message) = payload.downcast_ref::<String>() {
|
||||||
|
message.clone()
|
||||||
|
} else {
|
||||||
|
"unknown panic payload".to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn log_message_preview(message: &str) -> String {
|
||||||
|
message.chars().take(96).collect::<String>()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn send_over_stream(
|
||||||
|
stream: &mut TransportStream,
|
||||||
|
context: &Context,
|
||||||
|
message: String,
|
||||||
|
) -> Result<(), String> {
|
||||||
|
let message_len = message.len();
|
||||||
|
info!("Sending TCP payload ({} bytes)", message_len);
|
||||||
|
stream
|
||||||
|
.write_message(message.as_bytes())
|
||||||
|
.map_err(|e| {
|
||||||
|
let message = e.to_string();
|
||||||
|
let _ = context.callback_data(
|
||||||
|
"TCP SOCKET ERROR",
|
||||||
|
"TAK Socket disconnected",
|
||||||
|
message.clone(),
|
||||||
|
);
|
||||||
|
message
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn flush_pending_messages(
|
||||||
|
connection: &mut Option<TransportStream>,
|
||||||
|
pending_messages: &mut VecDeque<(String, Context)>,
|
||||||
|
state: &mut ConnectionState,
|
||||||
|
) {
|
||||||
|
if pending_messages.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let Some(stream) = connection.as_mut() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
info!(
|
||||||
|
"Flushing {} queued TCP payload(s) after connection became active",
|
||||||
|
pending_messages.len()
|
||||||
|
);
|
||||||
|
|
||||||
|
while let Some((message, context)) = pending_messages.pop_front() {
|
||||||
|
if let Err(error) = send_over_stream(stream, &context, message) {
|
||||||
|
info!("Failed to send queued message: {}", error);
|
||||||
|
*state = ConnectionState::Failed(error);
|
||||||
|
*connection = None;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn poll_connect_event(
|
||||||
|
connect_rx: &Receiver<ConnectEvent>,
|
||||||
|
connection: &mut Option<TransportStream>,
|
||||||
|
state: &mut ConnectionState,
|
||||||
|
pending_messages: &mut VecDeque<(String, Context)>,
|
||||||
|
ctx: &Context,
|
||||||
|
connection_message: &str,
|
||||||
|
target: &str,
|
||||||
|
) {
|
||||||
|
loop {
|
||||||
|
match connect_rx.try_recv() {
|
||||||
|
Ok(ConnectEvent::Connected(stream)) => {
|
||||||
|
info!("TCP connection established successfully: {}", target);
|
||||||
|
let _ = ctx.callback_data("TCP SOCKET", connection_message, target.to_string());
|
||||||
|
*connection = Some(stream);
|
||||||
|
*state = ConnectionState::Connected;
|
||||||
|
flush_pending_messages(connection, pending_messages, state);
|
||||||
|
}
|
||||||
|
Ok(ConnectEvent::Failed(error)) => {
|
||||||
|
info!("Failed to connect to TCP server: {}", error);
|
||||||
|
let _ = ctx.callback_data(
|
||||||
|
"TCP SOCKET ERROR",
|
||||||
|
"TAK Socket connection failed",
|
||||||
|
error.clone(),
|
||||||
|
);
|
||||||
|
*state = ConnectionState::Failed(error);
|
||||||
|
}
|
||||||
|
Err(TryRecvError::Empty) => break,
|
||||||
|
Err(TryRecvError::Disconnected) => break,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TcpClient {
|
||||||
|
pub fn start(&self, config: ConnectionConfig, rx: Receiver<TcpCommand>, ctx: Context) {
|
||||||
|
if let Some(ref client) = *TCP_CLIENT.lock().unwrap() {
|
||||||
|
info!("Existing TCP client detected; stopping previous instance before restart.");
|
||||||
|
client.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
thread::spawn(move || {
|
||||||
|
let mut running = true;
|
||||||
|
let connection_message = config.connected_message();
|
||||||
|
let config_description = config.describe();
|
||||||
|
let target = config.target();
|
||||||
|
let mut state = ConnectionState::Connecting;
|
||||||
|
let mut connection: Option<TransportStream> = None;
|
||||||
|
let mut pending_messages: VecDeque<(String, Context)> = VecDeque::new();
|
||||||
|
let (connect_tx, connect_rx) = mpsc::channel();
|
||||||
|
|
||||||
|
info!("TCP worker thread started with config: {}", config_description);
|
||||||
|
|
||||||
|
let tcp_thread = thread::spawn(move || {
|
||||||
|
let connect_result = panic::catch_unwind(AssertUnwindSafe(|| connect_stream(&config)));
|
||||||
|
|
||||||
|
match connect_result {
|
||||||
|
Ok(Ok(stream)) => {
|
||||||
|
let _ = connect_tx.send(ConnectEvent::Connected(stream));
|
||||||
|
}
|
||||||
|
Ok(Err(error)) => {
|
||||||
|
let _ = connect_tx.send(ConnectEvent::Failed(error));
|
||||||
|
}
|
||||||
|
Err(payload) => {
|
||||||
|
let message = format!(
|
||||||
|
"TCP connection worker panicked: {}",
|
||||||
|
describe_panic_payload(payload)
|
||||||
|
);
|
||||||
|
let _ = connect_tx.send(ConnectEvent::Failed(message));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
while running {
|
||||||
|
poll_connect_event(
|
||||||
|
&connect_rx,
|
||||||
|
&mut connection,
|
||||||
|
&mut state,
|
||||||
|
&mut pending_messages,
|
||||||
|
&ctx,
|
||||||
|
connection_message,
|
||||||
|
&target,
|
||||||
|
);
|
||||||
|
|
||||||
|
match rx.recv_timeout(CONNECT_POLL_INTERVAL) {
|
||||||
|
Ok(TcpCommand::SendMessage(message, context)) => {
|
||||||
|
let message_len = message.len();
|
||||||
|
match &mut state {
|
||||||
|
ConnectionState::Connected => {
|
||||||
|
if let Some(stream) = connection.as_mut() {
|
||||||
|
if let Err(error) = send_over_stream(stream, &context, message) {
|
||||||
|
info!("Failed to send message: {}", error);
|
||||||
|
state = ConnectionState::Failed(error);
|
||||||
|
connection = None;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
warn!(
|
||||||
|
"Connection state said connected, but no socket was present; queuing payload."
|
||||||
|
);
|
||||||
|
pending_messages.push_back((message, context));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ConnectionState::Connecting => {
|
||||||
|
if pending_messages.len() >= MAX_PENDING_MESSAGES {
|
||||||
|
let preview = log_message_preview(&message);
|
||||||
|
warn!(
|
||||||
|
"Dropping TCP payload because connection is still pending and queue is full ({} bytes, preview={:?})",
|
||||||
|
message_len, preview
|
||||||
|
);
|
||||||
|
let _ = context.callback_data(
|
||||||
|
"TCP SOCKET ERROR",
|
||||||
|
"TAK Socket is still connecting",
|
||||||
|
format!(
|
||||||
|
"queue full while connecting; dropped payload ({} bytes, preview={:?})",
|
||||||
|
message_len, preview
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
info!(
|
||||||
|
"Queueing TCP payload while connection is pending ({} bytes, queued={})",
|
||||||
|
message_len,
|
||||||
|
pending_messages.len() + 1
|
||||||
|
);
|
||||||
|
pending_messages.push_back((message, context));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ConnectionState::Failed(error) => {
|
||||||
|
let preview = log_message_preview(&message);
|
||||||
|
warn!(
|
||||||
|
"Dropping TCP payload because connection is in failed state ({} bytes, preview={:?}, error={})",
|
||||||
|
message_len, preview, error
|
||||||
|
);
|
||||||
|
let _ = context.callback_data(
|
||||||
|
"TCP SOCKET ERROR",
|
||||||
|
"TAK Socket is not connected",
|
||||||
|
error.clone(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(TcpCommand::Stop) => {
|
||||||
|
running = false;
|
||||||
|
info!("Stopping TCP client.");
|
||||||
|
}
|
||||||
|
Err(RecvTimeoutError::Timeout) => {}
|
||||||
|
Err(RecvTimeoutError::Disconnected) => {
|
||||||
|
warn!("TCP command channel disconnected.");
|
||||||
|
running = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
info!("Waiting for TCP connection thread to finish.");
|
||||||
|
match tcp_thread.join() {
|
||||||
|
Ok(()) => info!("TCP connection thread joined successfully."),
|
||||||
|
Err(payload) => warn!(
|
||||||
|
"TCP connection thread join reported a panic: {}",
|
||||||
|
describe_panic_payload(payload)
|
||||||
|
),
|
||||||
|
}
|
||||||
|
info!("TCP worker thread finished.");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn send_payload(&self, context: Context, payload: String) {
|
||||||
|
let tx = self.tx.clone();
|
||||||
|
thread::spawn(move || {
|
||||||
|
info!("Dispatching queued TCP payload command.");
|
||||||
|
if let Err(error) = tx.send(TcpCommand::SendMessage(payload, context)) {
|
||||||
|
warn!("Failed to dispatch TCP payload command: {}", error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn stop(&self) {
|
||||||
|
let tx = self.tx.clone();
|
||||||
|
thread::spawn(move || {
|
||||||
|
info!("Dispatching TCP stop command.");
|
||||||
|
if let Err(error) = tx.send(TcpCommand::Stop) {
|
||||||
|
warn!("Failed to dispatch TCP stop command: {}", error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
64
src/tcp/config.rs
Normal file
64
src/tcp/config.rs
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
pub enum ConnectionConfig {
|
||||||
|
Plain {
|
||||||
|
address: String,
|
||||||
|
},
|
||||||
|
Mtls {
|
||||||
|
address: String,
|
||||||
|
server_name: String,
|
||||||
|
ca_cert_path: String,
|
||||||
|
client_cert_path: String,
|
||||||
|
client_key_path: String,
|
||||||
|
},
|
||||||
|
EnrollMtls {
|
||||||
|
host: String,
|
||||||
|
server_name: String,
|
||||||
|
enroll_port: String,
|
||||||
|
username: String,
|
||||||
|
password: String,
|
||||||
|
client_uid: String,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ConnectionConfig {
|
||||||
|
pub fn connected_message(&self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
Self::Plain { .. } => "Connected to TCP Server",
|
||||||
|
Self::Mtls { .. } => "Connected to TAK Server via mTLS",
|
||||||
|
Self::EnrollMtls { .. } => "Connected to TAK Server via enrolled mTLS certificate",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn target(&self) -> String {
|
||||||
|
match self {
|
||||||
|
Self::Plain { address } | Self::Mtls { address, .. } => address.clone(),
|
||||||
|
Self::EnrollMtls { host, .. } => host.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn describe(&self) -> String {
|
||||||
|
match self {
|
||||||
|
Self::Plain { address } => format!("plain tcp -> {}", address),
|
||||||
|
Self::Mtls {
|
||||||
|
address,
|
||||||
|
server_name,
|
||||||
|
ca_cert_path,
|
||||||
|
client_cert_path,
|
||||||
|
client_key_path,
|
||||||
|
} => format!(
|
||||||
|
"manual mtls -> {} (server_name={}, ca={}, cert={}, key={})",
|
||||||
|
address, server_name, ca_cert_path, client_cert_path, client_key_path
|
||||||
|
),
|
||||||
|
Self::EnrollMtls {
|
||||||
|
host,
|
||||||
|
server_name,
|
||||||
|
enroll_port,
|
||||||
|
username,
|
||||||
|
client_uid,
|
||||||
|
..
|
||||||
|
} => format!(
|
||||||
|
"enroll mtls -> host={} enroll_port={} server_name={} username={} client_uid={}",
|
||||||
|
host, enroll_port, server_name, username, client_uid
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
73
src/tcp/cot.rs
Normal file
73
src/tcp/cot.rs
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
use arma_rs::Context;
|
||||||
|
|
||||||
|
use crate::{cot, tcp::send_payload};
|
||||||
|
|
||||||
|
pub fn send_eud_cot(ctx: Context, cursor_over_time: cot::eud::EudCoTPayload) -> &'static str {
|
||||||
|
let payload = cursor_over_time.to_cot().convert_to_xml();
|
||||||
|
send_payload(ctx, payload);
|
||||||
|
|
||||||
|
"Sending End User Device Cursor Over Time to TCP server"
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn send_marker_cot(
|
||||||
|
ctx: Context,
|
||||||
|
cursor_over_time: cot::nato::MarkerCoTPayload,
|
||||||
|
) -> &'static str {
|
||||||
|
let payload = cursor_over_time.to_cot().convert_to_xml();
|
||||||
|
send_payload(ctx, payload);
|
||||||
|
|
||||||
|
"Sending Marker Cursor Over Time to TCP server"
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn send_digital_pointer_cot(
|
||||||
|
ctx: Context,
|
||||||
|
cursor_over_time: cot::digital_pointer::DigitalPointerPayload,
|
||||||
|
) -> &'static str {
|
||||||
|
let payload = cursor_over_time.to_cot().convert_to_xml();
|
||||||
|
send_payload(ctx, payload);
|
||||||
|
|
||||||
|
"Sending Digital Pointer Cursor Over Time to TCP server"
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn send_message_cot(
|
||||||
|
ctx: Context,
|
||||||
|
message_payload: cot::message::MessagePayload,
|
||||||
|
) -> &'static str {
|
||||||
|
let message_cot = cot::message::MessageCot::from_payload(message_payload);
|
||||||
|
let payload = message_cot.to_xml();
|
||||||
|
send_payload(ctx, payload);
|
||||||
|
|
||||||
|
"Sending Message CoT to TCP server"
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn send_uas_platform_cot(
|
||||||
|
ctx: Context,
|
||||||
|
payload: cot::uas::UasPlatformCoTPayload,
|
||||||
|
) -> &'static str {
|
||||||
|
let xml = payload.to_xml();
|
||||||
|
send_payload(ctx, xml);
|
||||||
|
|
||||||
|
"Sending UAS Platform main CoT to TCP server"
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn send_uas_video_cot(
|
||||||
|
ctx: Context,
|
||||||
|
payload: cot::uas::UasVideoCoTPayload,
|
||||||
|
) -> &'static str {
|
||||||
|
let xml = payload.to_xml();
|
||||||
|
if !xml.is_empty() {
|
||||||
|
send_payload(ctx, xml);
|
||||||
|
}
|
||||||
|
|
||||||
|
"Sending UAS Video (b-i-v) CoT to TCP server"
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn send_uas_sensor_cot(
|
||||||
|
ctx: Context,
|
||||||
|
payload: cot::uas::UasSensorCoTPayload,
|
||||||
|
) -> &'static str {
|
||||||
|
let xml = payload.to_xml();
|
||||||
|
send_payload(ctx, xml);
|
||||||
|
|
||||||
|
"Sending UAS Sensor (b-m-p-s-p-loc) CoT to TCP server"
|
||||||
|
}
|
||||||
37
src/tcp/draw.rs
Normal file
37
src/tcp/draw.rs
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
use arma_rs::Context;
|
||||||
|
|
||||||
|
use crate::{cot, tcp::send_payload};
|
||||||
|
|
||||||
|
pub fn send_circle_cot(
|
||||||
|
ctx: Context,
|
||||||
|
circle_payload: cot::draws::circle::CircleCoTPayload,
|
||||||
|
) -> &'static str {
|
||||||
|
let shape_circle_cot = circle_payload.to_cot();
|
||||||
|
let now = chrono::Utc::now().to_rfc3339_opts(chrono::SecondsFormat::Millis, true);
|
||||||
|
let stale = (chrono::Utc::now() + chrono::Duration::days(1))
|
||||||
|
.to_rfc3339_opts(chrono::SecondsFormat::Millis, true);
|
||||||
|
let payload = shape_circle_cot.to_xml(&now, &stale);
|
||||||
|
send_payload(ctx, payload);
|
||||||
|
|
||||||
|
"Sending Circle CoT to TCP server"
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn send_ellipse_cot(ctx: Context) -> &'static str {
|
||||||
|
let _ = ctx;
|
||||||
|
"Not implemented: send_ellipse_cot"
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn send_rectangle_cot(ctx: Context) -> &'static str {
|
||||||
|
let _ = ctx;
|
||||||
|
"Not implemented: send_ellipse_cot"
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn send_freedraw_cot(ctx: Context) -> &'static str {
|
||||||
|
let _ = ctx;
|
||||||
|
"Not implemented: send_ellipse_cot"
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn send_vectordraw_cot(ctx: Context) -> &'static str {
|
||||||
|
let _ = ctx;
|
||||||
|
"Not implemented: send_ellipse_cot"
|
||||||
|
}
|
||||||
107
src/tcp/mod.rs
Normal file
107
src/tcp/mod.rs
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
use arma_rs::Context;
|
||||||
|
use lazy_static::lazy_static;
|
||||||
|
use log::info;
|
||||||
|
use std::sync::mpsc::{self, Receiver, Sender};
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
|
mod client;
|
||||||
|
mod config;
|
||||||
|
mod tls;
|
||||||
|
mod transport;
|
||||||
|
|
||||||
|
pub mod cot;
|
||||||
|
pub mod draw;
|
||||||
|
|
||||||
|
use client::{TcpClient, TcpCommand};
|
||||||
|
use config::ConnectionConfig;
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
static ref TCP_CLIENT: Arc<Mutex<Option<TcpClient>>> = Arc::new(Mutex::new(None));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn start_with_config(ctx: Context, config: ConnectionConfig) {
|
||||||
|
info!("Starting TCP client with config: {}", config.describe());
|
||||||
|
let (tx, rx): (Sender<TcpCommand>, Receiver<TcpCommand>) = mpsc::channel();
|
||||||
|
|
||||||
|
let client = TcpClient { tx };
|
||||||
|
client.start(config, rx, ctx);
|
||||||
|
|
||||||
|
let mut client_guard = TCP_CLIENT.lock().unwrap();
|
||||||
|
*client_guard = Some(client);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn start(ctx: Context, address: String) -> &'static str {
|
||||||
|
start_with_config(ctx, ConnectionConfig::Plain { address });
|
||||||
|
|
||||||
|
"Starting TCP Client"
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn start_mtls(
|
||||||
|
ctx: Context,
|
||||||
|
address: String,
|
||||||
|
server_name: String,
|
||||||
|
ca_cert_path: String,
|
||||||
|
client_cert_path: String,
|
||||||
|
client_key_path: String,
|
||||||
|
) -> &'static str {
|
||||||
|
start_with_config(
|
||||||
|
ctx,
|
||||||
|
ConnectionConfig::Mtls {
|
||||||
|
address,
|
||||||
|
server_name,
|
||||||
|
ca_cert_path,
|
||||||
|
client_cert_path,
|
||||||
|
client_key_path,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
"Starting mTLS TCP Client"
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn start_enroll_mtls(
|
||||||
|
ctx: Context,
|
||||||
|
host: String,
|
||||||
|
server_name: String,
|
||||||
|
enroll_port: String,
|
||||||
|
username: String,
|
||||||
|
password: String,
|
||||||
|
client_uid: String,
|
||||||
|
) -> &'static str {
|
||||||
|
start_with_config(
|
||||||
|
ctx,
|
||||||
|
ConnectionConfig::EnrollMtls {
|
||||||
|
host,
|
||||||
|
server_name,
|
||||||
|
enroll_port,
|
||||||
|
username,
|
||||||
|
password,
|
||||||
|
client_uid,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
"Starting enrolled mTLS TCP Client"
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn send_payload(ctx: Context, payload: String) -> &'static str {
|
||||||
|
if let Some(ref client) = *TCP_CLIENT.lock().unwrap() {
|
||||||
|
info!("Queueing TCP payload ({} bytes)", payload.len());
|
||||||
|
client.send_payload(ctx, payload);
|
||||||
|
} else {
|
||||||
|
let _ = ctx.callback_null("TCP SOCKET ERROR", "TCP Client is not running");
|
||||||
|
info!("TCP client is not running.");
|
||||||
|
}
|
||||||
|
|
||||||
|
"Sending payload to TCP server"
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn stop(ctx: Context) -> &'static str {
|
||||||
|
if let Some(ref client) = *TCP_CLIENT.lock().unwrap() {
|
||||||
|
info!("Stopping TCP client via extension command.");
|
||||||
|
client.stop();
|
||||||
|
let _ = ctx.callback_null("TCP SOCKET", "TCP client stopped");
|
||||||
|
} else {
|
||||||
|
let _ = ctx.callback_null("TCP SOCKET ERROR", "TCP client is not running");
|
||||||
|
}
|
||||||
|
|
||||||
|
"Stopping TCP Client"
|
||||||
|
}
|
||||||
221
src/tcp/tls/connector.rs
Normal file
221
src/tcp/tls/connector.rs
Normal file
@@ -0,0 +1,221 @@
|
|||||||
|
use log::info;
|
||||||
|
use rustls::pki_types::{CertificateDer, PrivateKeyDer, ServerName};
|
||||||
|
use rustls::{ClientConfig, ClientConnection, RootCertStore, StreamOwned};
|
||||||
|
use rustls_pemfile::{certs, private_key};
|
||||||
|
use std::fs::File;
|
||||||
|
use std::io::BufReader;
|
||||||
|
use std::io::Cursor;
|
||||||
|
use std::net::{SocketAddr, TcpStream, ToSocketAddrs};
|
||||||
|
use std::time::Duration;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use crate::tcp::transport::TransportStream;
|
||||||
|
|
||||||
|
const TCP_CONNECT_TIMEOUT: Duration = Duration::from_secs(10);
|
||||||
|
const SOCKET_IO_TIMEOUT: Duration = Duration::from_secs(10);
|
||||||
|
|
||||||
|
fn load_certificates(path: &str) -> Result<Vec<CertificateDer<'static>>, String> {
|
||||||
|
let file = File::open(path).map_err(|e| format!("failed to open cert file {}: {}", path, e))?;
|
||||||
|
let mut reader = BufReader::new(file);
|
||||||
|
|
||||||
|
certs(&mut reader)
|
||||||
|
.collect::<Result<Vec<_>, _>>()
|
||||||
|
.map_err(|e| format!("failed to read certs from {}: {}", path, e))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load_private_key(path: &str) -> Result<PrivateKeyDer<'static>, String> {
|
||||||
|
let file = File::open(path).map_err(|e| format!("failed to open key file {}: {}", path, e))?;
|
||||||
|
let mut reader = BufReader::new(file);
|
||||||
|
|
||||||
|
private_key(&mut reader)
|
||||||
|
.map_err(|e| format!("failed to read private key from {}: {}", path, e))?
|
||||||
|
.ok_or_else(|| format!("no supported private key found in {}", path))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load_certificates_from_pem(pem: &str) -> Result<Vec<CertificateDer<'static>>, String> {
|
||||||
|
let mut reader = Cursor::new(pem.as_bytes());
|
||||||
|
|
||||||
|
certs(&mut reader)
|
||||||
|
.collect::<Result<Vec<_>, _>>()
|
||||||
|
.map_err(|e| format!("failed to read certs from PEM payload: {}", e))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load_private_key_from_pem(pem: &str) -> Result<PrivateKeyDer<'static>, String> {
|
||||||
|
let mut reader = Cursor::new(pem.as_bytes());
|
||||||
|
|
||||||
|
private_key(&mut reader)
|
||||||
|
.map_err(|e| format!("failed to read private key from PEM payload: {}", e))?
|
||||||
|
.ok_or_else(|| "no supported private key found in PEM payload".to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn infer_server_name(address: &str) -> &str {
|
||||||
|
address
|
||||||
|
.trim()
|
||||||
|
.trim_start_matches('[')
|
||||||
|
.split(']')
|
||||||
|
.next()
|
||||||
|
.unwrap_or(address)
|
||||||
|
.split(':')
|
||||||
|
.next()
|
||||||
|
.unwrap_or(address)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resolve_address(address: &str) -> Result<SocketAddr, String> {
|
||||||
|
address
|
||||||
|
.to_socket_addrs()
|
||||||
|
.map_err(|e| format!("failed to resolve {}: {}", address, e))?
|
||||||
|
.next()
|
||||||
|
.ok_or_else(|| format!("failed to resolve {}: no socket addresses returned", address))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn connect_tcp(address: &str) -> Result<TcpStream, String> {
|
||||||
|
let socket_addr = resolve_address(address)?;
|
||||||
|
info!(
|
||||||
|
"Opening TCP connection to {} (resolved={}) with timeout {:?}",
|
||||||
|
address, socket_addr, TCP_CONNECT_TIMEOUT
|
||||||
|
);
|
||||||
|
|
||||||
|
let tcp_stream = TcpStream::connect_timeout(&socket_addr, TCP_CONNECT_TIMEOUT)
|
||||||
|
.map_err(|e| format!("failed to connect to {}: {}", address, e))?;
|
||||||
|
|
||||||
|
tcp_stream
|
||||||
|
.set_read_timeout(Some(SOCKET_IO_TIMEOUT))
|
||||||
|
.map_err(|e| format!("failed to set read timeout on {}: {}", address, e))?;
|
||||||
|
tcp_stream
|
||||||
|
.set_write_timeout(Some(SOCKET_IO_TIMEOUT))
|
||||||
|
.map_err(|e| format!("failed to set write timeout on {}: {}", address, e))?;
|
||||||
|
|
||||||
|
Ok(tcp_stream)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn connect_mtls(
|
||||||
|
address: &str,
|
||||||
|
server_name: &str,
|
||||||
|
ca_cert_path: &str,
|
||||||
|
client_cert_path: &str,
|
||||||
|
client_key_path: &str,
|
||||||
|
) -> Result<TransportStream, String> {
|
||||||
|
info!(
|
||||||
|
"Connecting mTLS from file paths to {} using server_name={}",
|
||||||
|
address, server_name
|
||||||
|
);
|
||||||
|
let mut root_store = RootCertStore::empty();
|
||||||
|
let ca_certificates = load_certificates(ca_cert_path)?;
|
||||||
|
info!(
|
||||||
|
"Loaded {} CA certificate(s) from {}",
|
||||||
|
ca_certificates.len(),
|
||||||
|
ca_cert_path
|
||||||
|
);
|
||||||
|
for certificate in ca_certificates {
|
||||||
|
root_store
|
||||||
|
.add(certificate)
|
||||||
|
.map_err(|e| format!("failed to add CA certificate from {}: {}", ca_cert_path, e))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let client_certificates = load_certificates(client_cert_path)?;
|
||||||
|
info!(
|
||||||
|
"Loaded {} client certificate(s) from {}",
|
||||||
|
client_certificates.len(),
|
||||||
|
client_cert_path
|
||||||
|
);
|
||||||
|
let client_key = load_private_key(client_key_path)?;
|
||||||
|
info!("Loaded client private key from {}", client_key_path);
|
||||||
|
|
||||||
|
let tls_config = ClientConfig::builder()
|
||||||
|
.with_root_certificates(root_store)
|
||||||
|
.with_client_auth_cert(client_certificates, client_key)
|
||||||
|
.map_err(|e| format!("failed to configure mTLS client: {}", e))?;
|
||||||
|
info!("Constructed rustls client config for {}", address);
|
||||||
|
|
||||||
|
let tcp_stream = connect_tcp(address)?;
|
||||||
|
let resolved_server_name = if server_name.trim().is_empty() {
|
||||||
|
infer_server_name(address).to_string()
|
||||||
|
} else {
|
||||||
|
server_name.trim().to_string()
|
||||||
|
};
|
||||||
|
|
||||||
|
let server_name = ServerName::try_from(resolved_server_name.clone())
|
||||||
|
.map_err(|_| format!("invalid TLS server name: {}", resolved_server_name))?;
|
||||||
|
let mut tls_stream = StreamOwned::new(
|
||||||
|
ClientConnection::new(Arc::new(tls_config), server_name)
|
||||||
|
.map_err(|e| format!("failed to create TLS client: {}", e))?,
|
||||||
|
tcp_stream,
|
||||||
|
);
|
||||||
|
|
||||||
|
info!("Starting mTLS handshake for {}", address);
|
||||||
|
while tls_stream.conn.is_handshaking() {
|
||||||
|
tls_stream
|
||||||
|
.conn
|
||||||
|
.complete_io(&mut tls_stream.sock)
|
||||||
|
.map_err(|e| format!("TLS handshake failed: {}", e))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
info!("mTLS handshake completed successfully for {}", address);
|
||||||
|
|
||||||
|
Ok(TransportStream::Mtls(tls_stream))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn connect_mtls_from_pem(
|
||||||
|
address: &str,
|
||||||
|
server_name: &str,
|
||||||
|
ca_cert_pem: &str,
|
||||||
|
client_cert_pem: &str,
|
||||||
|
client_key_pem: &str,
|
||||||
|
) -> Result<TransportStream, String> {
|
||||||
|
info!(
|
||||||
|
"Connecting mTLS from in-memory PEM payloads to {} using server_name={}",
|
||||||
|
address, server_name
|
||||||
|
);
|
||||||
|
let mut root_store = RootCertStore::empty();
|
||||||
|
let ca_certificates = load_certificates_from_pem(ca_cert_pem)?;
|
||||||
|
info!(
|
||||||
|
"Loaded {} CA certificate(s) from enrollment payload",
|
||||||
|
ca_certificates.len()
|
||||||
|
);
|
||||||
|
for certificate in ca_certificates {
|
||||||
|
root_store
|
||||||
|
.add(certificate)
|
||||||
|
.map_err(|e| format!("failed to add CA certificate from PEM payload: {}", e))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let client_certificates = load_certificates_from_pem(client_cert_pem)?;
|
||||||
|
info!(
|
||||||
|
"Loaded {} client certificate(s) from enrollment payload",
|
||||||
|
client_certificates.len()
|
||||||
|
);
|
||||||
|
let client_key = load_private_key_from_pem(client_key_pem)?;
|
||||||
|
info!("Loaded client private key from enrollment payload");
|
||||||
|
|
||||||
|
let tls_config = ClientConfig::builder()
|
||||||
|
.with_root_certificates(root_store)
|
||||||
|
.with_client_auth_cert(client_certificates, client_key)
|
||||||
|
.map_err(|e| format!("failed to configure mTLS client: {}", e))?;
|
||||||
|
info!("Constructed rustls client config for {}", address);
|
||||||
|
|
||||||
|
let tcp_stream = connect_tcp(address)?;
|
||||||
|
let resolved_server_name = if server_name.trim().is_empty() {
|
||||||
|
infer_server_name(address).to_string()
|
||||||
|
} else {
|
||||||
|
server_name.trim().to_string()
|
||||||
|
};
|
||||||
|
|
||||||
|
let server_name = ServerName::try_from(resolved_server_name.clone())
|
||||||
|
.map_err(|_| format!("invalid TLS server name: {}", resolved_server_name))?;
|
||||||
|
let mut tls_stream = StreamOwned::new(
|
||||||
|
ClientConnection::new(Arc::new(tls_config), server_name)
|
||||||
|
.map_err(|e| format!("failed to create TLS client: {}", e))?,
|
||||||
|
tcp_stream,
|
||||||
|
);
|
||||||
|
|
||||||
|
info!("Starting mTLS handshake for {}", address);
|
||||||
|
while tls_stream.conn.is_handshaking() {
|
||||||
|
tls_stream
|
||||||
|
.conn
|
||||||
|
.complete_io(&mut tls_stream.sock)
|
||||||
|
.map_err(|e| format!("TLS handshake failed: {}", e))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
info!("mTLS handshake completed successfully for {}", address);
|
||||||
|
|
||||||
|
Ok(TransportStream::Mtls(tls_stream))
|
||||||
|
}
|
||||||
228
src/tcp/tls/enrollment.rs
Normal file
228
src/tcp/tls/enrollment.rs
Normal file
@@ -0,0 +1,228 @@
|
|||||||
|
use rcgen::{CertificateParams, DistinguishedName, DnType, KeyPair, PKCS_RSA_SHA256};
|
||||||
|
use log::info;
|
||||||
|
use reqwest::blocking::Client;
|
||||||
|
use serde::Deserialize;
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
use super::connector::connect_mtls_from_pem;
|
||||||
|
use crate::tcp::transport::TransportStream;
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct EnrollmentResponse {
|
||||||
|
#[serde(rename = "signedCert")]
|
||||||
|
signed_cert: String,
|
||||||
|
ca0: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct EnrollmentConfig {
|
||||||
|
server_port: String,
|
||||||
|
enroll_path: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn extract_tag_value(xml: &str, tag_name: &str) -> Option<String> {
|
||||||
|
let open_tag = format!("<{}>", tag_name);
|
||||||
|
let close_tag = format!("</{}>", tag_name);
|
||||||
|
let start = xml.find(&open_tag)? + open_tag.len();
|
||||||
|
let end = xml[start..].find(&close_tag)? + start;
|
||||||
|
Some(xml[start..end].trim().to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn wrap_pem_body(base64_body: &str, begin: &str, end: &str) -> String {
|
||||||
|
let mut wrapped = String::new();
|
||||||
|
let normalized = base64_body.trim().replace(['\r', '\n'], "");
|
||||||
|
|
||||||
|
wrapped.push_str(begin);
|
||||||
|
wrapped.push('\n');
|
||||||
|
for chunk in normalized.as_bytes().chunks(64) {
|
||||||
|
wrapped.push_str(std::str::from_utf8(chunk).unwrap_or_default());
|
||||||
|
wrapped.push('\n');
|
||||||
|
}
|
||||||
|
wrapped.push_str(end);
|
||||||
|
wrapped.push('\n');
|
||||||
|
wrapped
|
||||||
|
}
|
||||||
|
|
||||||
|
fn enrollment_http_client() -> Result<Client, String> {
|
||||||
|
Client::builder()
|
||||||
|
.danger_accept_invalid_certs(true)
|
||||||
|
.build()
|
||||||
|
.map_err(|e| format!("failed to build enrollment HTTP client: {}", e))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn response_error_details(response: reqwest::blocking::Response) -> String {
|
||||||
|
let status = response.status();
|
||||||
|
match response.text() {
|
||||||
|
Ok(body) => {
|
||||||
|
let trimmed = body.trim();
|
||||||
|
if trimmed.is_empty() {
|
||||||
|
status.to_string()
|
||||||
|
} else {
|
||||||
|
format!("{}: {}", status, trimmed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(_) => status.to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fetch_enrollment_config(host: &str, enroll_port: &str) -> Result<EnrollmentConfig, String> {
|
||||||
|
let url = format!(
|
||||||
|
"https://{}:{}/Marti/api/tls/config",
|
||||||
|
host.trim(),
|
||||||
|
enroll_port.trim()
|
||||||
|
);
|
||||||
|
info!("Fetching TAK enrollment config from {}", url);
|
||||||
|
|
||||||
|
let response = enrollment_http_client()?
|
||||||
|
.get(&url)
|
||||||
|
.send()
|
||||||
|
.map_err(|e| format!("failed to fetch {}: {}", url, e))?;
|
||||||
|
|
||||||
|
if !response.status().is_success() {
|
||||||
|
return Err(format!(
|
||||||
|
"failed to fetch {}: {}",
|
||||||
|
url,
|
||||||
|
response_error_details(response)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let response_text = response
|
||||||
|
.text()
|
||||||
|
.map_err(|e| format!("failed to read config response from {}: {}", url, e))?;
|
||||||
|
|
||||||
|
let server_port = extract_tag_value(&response_text, "serverPort")
|
||||||
|
.ok_or_else(|| "missing serverPort in /Marti/api/tls/config response".to_string())?;
|
||||||
|
let enroll_path = extract_tag_value(&response_text, "enrollPath")
|
||||||
|
.ok_or_else(|| "missing enrollPath in /Marti/api/tls/config response".to_string())?;
|
||||||
|
|
||||||
|
info!(
|
||||||
|
"Enrollment config received: server_port={} enroll_path={}",
|
||||||
|
server_port, enroll_path
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(EnrollmentConfig {
|
||||||
|
server_port,
|
||||||
|
enroll_path,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn enroll_client_certificate(
|
||||||
|
host: &str,
|
||||||
|
enroll_port: &str,
|
||||||
|
enroll_path: &str,
|
||||||
|
username: &str,
|
||||||
|
password: &str,
|
||||||
|
client_uid: &str,
|
||||||
|
) -> Result<(String, String, String), String> {
|
||||||
|
info!(
|
||||||
|
"Generating RSA client keypair and CSR for enrolled TAK client {}",
|
||||||
|
client_uid
|
||||||
|
);
|
||||||
|
let key_pair = KeyPair::generate_for(&PKCS_RSA_SHA256)
|
||||||
|
.map_err(|e| format!("failed to generate client keypair: {}", e))?;
|
||||||
|
|
||||||
|
let mut distinguished_name = DistinguishedName::new();
|
||||||
|
distinguished_name.push(DnType::CommonName, client_uid);
|
||||||
|
distinguished_name.push(DnType::OrganizationName, "ArmaTAK");
|
||||||
|
distinguished_name.push(DnType::OrganizationalUnitName, "ArmaTAK Session");
|
||||||
|
|
||||||
|
let mut params = CertificateParams::new(vec![])
|
||||||
|
.map_err(|e| format!("failed to create CSR params: {}", e))?;
|
||||||
|
params.distinguished_name = distinguished_name;
|
||||||
|
|
||||||
|
let csr = params
|
||||||
|
.serialize_request(&key_pair)
|
||||||
|
.map_err(|e| format!("failed to generate CSR: {}", e))?;
|
||||||
|
let csr_der = csr.der().as_ref().to_vec();
|
||||||
|
|
||||||
|
let url = format!(
|
||||||
|
"https://{}:{}{}?clientUid={}",
|
||||||
|
host.trim(),
|
||||||
|
enroll_port.trim(),
|
||||||
|
enroll_path.trim(),
|
||||||
|
client_uid.trim()
|
||||||
|
);
|
||||||
|
info!(
|
||||||
|
"Submitting client certificate enrollment request for {} to {}",
|
||||||
|
client_uid, url
|
||||||
|
);
|
||||||
|
|
||||||
|
let response = enrollment_http_client()?
|
||||||
|
.post(&url)
|
||||||
|
.basic_auth(username.trim(), Some(password.to_string()))
|
||||||
|
.header("Accept", "application/json")
|
||||||
|
.header("Content-Type", "application/pkcs10")
|
||||||
|
.body(csr_der)
|
||||||
|
.send()
|
||||||
|
.map_err(|e| format!("failed to enroll client certificate at {}: {}", url, e))?;
|
||||||
|
|
||||||
|
if !response.status().is_success() {
|
||||||
|
return Err(format!(
|
||||||
|
"failed to enroll client certificate at {}: {}",
|
||||||
|
url,
|
||||||
|
response_error_details(response)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let enrollment: EnrollmentResponse = response
|
||||||
|
.json()
|
||||||
|
.map_err(|e| format!("failed to parse enrollment response: {}", e))?;
|
||||||
|
info!(
|
||||||
|
"Enrollment response parsed successfully for {} (signed_cert_len={}, ca_len={})",
|
||||||
|
client_uid,
|
||||||
|
enrollment.signed_cert.len(),
|
||||||
|
enrollment.ca0.len()
|
||||||
|
);
|
||||||
|
|
||||||
|
let cert_pem = wrap_pem_body(
|
||||||
|
&enrollment.signed_cert,
|
||||||
|
"-----BEGIN CERTIFICATE-----",
|
||||||
|
"-----END CERTIFICATE-----",
|
||||||
|
);
|
||||||
|
let key_pem = key_pair.serialize_pem();
|
||||||
|
|
||||||
|
Ok((enrollment.ca0, cert_pem, key_pem))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn enroll_and_connect(
|
||||||
|
host: &str,
|
||||||
|
server_name: &str,
|
||||||
|
enroll_port: &str,
|
||||||
|
username: &str,
|
||||||
|
password: &str,
|
||||||
|
client_uid: &str,
|
||||||
|
) -> Result<TransportStream, String> {
|
||||||
|
let normalized_client_uid = if client_uid.trim().is_empty() {
|
||||||
|
format!("armatak-{}", Uuid::new_v4())
|
||||||
|
} else {
|
||||||
|
client_uid.trim().to_string()
|
||||||
|
};
|
||||||
|
info!(
|
||||||
|
"Starting enroll_and_connect for host={} enroll_port={} server_name={} client_uid={}",
|
||||||
|
host,
|
||||||
|
enroll_port,
|
||||||
|
server_name,
|
||||||
|
normalized_client_uid
|
||||||
|
);
|
||||||
|
|
||||||
|
let enrollment_config = fetch_enrollment_config(host, enroll_port)?;
|
||||||
|
let (ca_cert_pem, client_cert_pem, client_key_pem) = enroll_client_certificate(
|
||||||
|
host,
|
||||||
|
enroll_port,
|
||||||
|
&enrollment_config.enroll_path,
|
||||||
|
username,
|
||||||
|
password,
|
||||||
|
&normalized_client_uid,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
connect_mtls_from_pem(
|
||||||
|
&format!("{}:{}", host.trim(), enrollment_config.server_port.trim()),
|
||||||
|
if server_name.trim().is_empty() {
|
||||||
|
host.trim()
|
||||||
|
} else {
|
||||||
|
server_name.trim()
|
||||||
|
},
|
||||||
|
&ca_cert_pem,
|
||||||
|
&client_cert_pem,
|
||||||
|
&client_key_pem,
|
||||||
|
)
|
||||||
|
}
|
||||||
5
src/tcp/tls/mod.rs
Normal file
5
src/tcp/tls/mod.rs
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
mod connector;
|
||||||
|
mod enrollment;
|
||||||
|
|
||||||
|
pub use connector::connect_mtls;
|
||||||
|
pub use enrollment::enroll_and_connect;
|
||||||
90
src/tcp/transport.rs
Normal file
90
src/tcp/transport.rs
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
use log::info;
|
||||||
|
use rustls::{ClientConnection, StreamOwned};
|
||||||
|
use std::io::Write;
|
||||||
|
use std::net::{SocketAddr, TcpStream, ToSocketAddrs};
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use super::config::ConnectionConfig;
|
||||||
|
use super::tls::{connect_mtls, enroll_and_connect};
|
||||||
|
|
||||||
|
const TCP_CONNECT_TIMEOUT: Duration = Duration::from_secs(10);
|
||||||
|
const SOCKET_IO_TIMEOUT: Duration = Duration::from_secs(10);
|
||||||
|
|
||||||
|
pub enum TransportStream {
|
||||||
|
Plain(TcpStream),
|
||||||
|
Mtls(StreamOwned<ClientConnection, TcpStream>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TransportStream {
|
||||||
|
pub fn write_message(&mut self, message: &[u8]) -> Result<(), std::io::Error> {
|
||||||
|
match self {
|
||||||
|
Self::Plain(stream) => {
|
||||||
|
stream.write_all(message)?;
|
||||||
|
stream.flush()
|
||||||
|
}
|
||||||
|
Self::Mtls(stream) => {
|
||||||
|
stream.write_all(message)?;
|
||||||
|
stream.flush()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn connect_plain(address: &str) -> Result<TransportStream, String> {
|
||||||
|
let socket_addr: SocketAddr = address
|
||||||
|
.to_socket_addrs()
|
||||||
|
.map_err(|e| format!("failed to resolve {}: {}", address, e))?
|
||||||
|
.next()
|
||||||
|
.ok_or_else(|| format!("failed to resolve {}: no socket addresses returned", address))?;
|
||||||
|
|
||||||
|
info!(
|
||||||
|
"Opening plain TCP connection to {} (resolved={}) with timeout {:?}",
|
||||||
|
address, socket_addr, TCP_CONNECT_TIMEOUT
|
||||||
|
);
|
||||||
|
|
||||||
|
let stream = TcpStream::connect_timeout(&socket_addr, TCP_CONNECT_TIMEOUT)
|
||||||
|
.map_err(|e| format!("failed to connect to {}: {}", address, e))?;
|
||||||
|
stream
|
||||||
|
.set_read_timeout(Some(SOCKET_IO_TIMEOUT))
|
||||||
|
.map_err(|e| format!("failed to set read timeout on {}: {}", address, e))?;
|
||||||
|
stream
|
||||||
|
.set_write_timeout(Some(SOCKET_IO_TIMEOUT))
|
||||||
|
.map_err(|e| format!("failed to set write timeout on {}: {}", address, e))?;
|
||||||
|
|
||||||
|
Ok(TransportStream::Plain(stream))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn connect_stream(config: &ConnectionConfig) -> Result<TransportStream, String> {
|
||||||
|
info!("connect_stream invoked for {}", config.describe());
|
||||||
|
match config {
|
||||||
|
ConnectionConfig::Plain { address } => connect_plain(address),
|
||||||
|
ConnectionConfig::Mtls {
|
||||||
|
address,
|
||||||
|
server_name,
|
||||||
|
ca_cert_path,
|
||||||
|
client_cert_path,
|
||||||
|
client_key_path,
|
||||||
|
} => connect_mtls(
|
||||||
|
address,
|
||||||
|
server_name,
|
||||||
|
ca_cert_path,
|
||||||
|
client_cert_path,
|
||||||
|
client_key_path,
|
||||||
|
),
|
||||||
|
ConnectionConfig::EnrollMtls {
|
||||||
|
host,
|
||||||
|
server_name,
|
||||||
|
enroll_port,
|
||||||
|
username,
|
||||||
|
password,
|
||||||
|
client_uid,
|
||||||
|
} => enroll_and_connect(
|
||||||
|
host,
|
||||||
|
server_name,
|
||||||
|
enroll_port,
|
||||||
|
username,
|
||||||
|
password,
|
||||||
|
client_uid,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,156 +0,0 @@
|
|||||||
use arma_rs::Context;
|
|
||||||
use lazy_static::lazy_static;
|
|
||||||
use log::info;
|
|
||||||
use std::io::Write;
|
|
||||||
use std::net::TcpStream;
|
|
||||||
use std::sync::mpsc::{self, Receiver, Sender};
|
|
||||||
use std::sync::{Arc, Mutex};
|
|
||||||
use std::thread;
|
|
||||||
|
|
||||||
use crate::cot;
|
|
||||||
|
|
||||||
pub enum TcpCommand {
|
|
||||||
SendMessage(String, Context),
|
|
||||||
Stop,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct TcpClient {
|
|
||||||
pub(crate) tx: Sender<TcpCommand>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TcpClient {
|
|
||||||
pub fn start(&self, address: String, rx: Receiver<TcpCommand>, ctx: Context) {
|
|
||||||
if let Some(ref client) = *TCP_CLIENT.lock().unwrap() {
|
|
||||||
client.stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
let connection = Arc::new(Mutex::new(None));
|
|
||||||
let connection_clone = Arc::clone(&connection);
|
|
||||||
|
|
||||||
thread::spawn(move || {
|
|
||||||
let mut running = true;
|
|
||||||
|
|
||||||
let tcp_thread = thread::spawn(move || match TcpStream::connect(&address) {
|
|
||||||
Ok(stream) => {
|
|
||||||
let _ = ctx.callback_data("TCP SOCKET", "Connected to TCP Server", address);
|
|
||||||
*connection_clone.lock().unwrap() = Some(stream);
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
let _ = ctx.callback_data(
|
|
||||||
"TCP SOCKET ERROR",
|
|
||||||
"TAK Socket connection failed",
|
|
||||||
e.to_string(),
|
|
||||||
);
|
|
||||||
info!("Failed to connect to TCP server: {}", e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
while running {
|
|
||||||
match rx.recv() {
|
|
||||||
Ok(TcpCommand::SendMessage(message, context)) => {
|
|
||||||
if let Some(mut stream) = connection.lock().unwrap().as_ref() {
|
|
||||||
if let Err(e) = stream.write_all(message.as_bytes()) {
|
|
||||||
info!("Failed to send message: {}", e);
|
|
||||||
|
|
||||||
let _ = context.callback_data(
|
|
||||||
"TCP SOCKET ERROR",
|
|
||||||
"TAK Socket disconnected",
|
|
||||||
e.to_string(),
|
|
||||||
);
|
|
||||||
|
|
||||||
running = false;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let _ = context.callback_null(
|
|
||||||
"TCP SOCKET ERROR",
|
|
||||||
"TAK Socket is not active",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(TcpCommand::Stop) => {
|
|
||||||
running = false;
|
|
||||||
info!("Stopping TCP client.");
|
|
||||||
}
|
|
||||||
Err(error) => {
|
|
||||||
info!("Error receiving command: {}", error.to_string());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
tcp_thread.join().unwrap();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn send_payload(&self, context: Context, payload: String) {
|
|
||||||
let tx = self.tx.clone();
|
|
||||||
thread::spawn(move || {
|
|
||||||
tx.send(TcpCommand::SendMessage(payload, context)).unwrap();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn stop(&self) {
|
|
||||||
let tx = self.tx.clone();
|
|
||||||
thread::spawn(move || {
|
|
||||||
tx.send(TcpCommand::Stop).unwrap();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
lazy_static! {
|
|
||||||
static ref TCP_CLIENT: Arc<Mutex<Option<TcpClient>>> = Arc::new(Mutex::new(None));
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn start(ctx: Context, address: String) -> &'static str {
|
|
||||||
let (tx, rx): (Sender<TcpCommand>, Receiver<TcpCommand>) = mpsc::channel();
|
|
||||||
|
|
||||||
let client = TcpClient { tx };
|
|
||||||
client.start(address, rx, ctx);
|
|
||||||
|
|
||||||
let mut client_guard = TCP_CLIENT.lock().unwrap();
|
|
||||||
*client_guard = Some(client);
|
|
||||||
|
|
||||||
"Starting TCP Client"
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn send_payload(ctx: Context, payload: String) -> &'static str {
|
|
||||||
if let Some(ref client) = *TCP_CLIENT.lock().unwrap() {
|
|
||||||
client.send_payload(ctx, payload);
|
|
||||||
} else {
|
|
||||||
let _ = ctx.callback_null("TCP SOCKET ERROR", "TCP Client is not running");
|
|
||||||
info!("TCP client is not running.");
|
|
||||||
}
|
|
||||||
|
|
||||||
"Sending payload to TCP server"
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn send_eud_cot(ctx: Context, cursor_over_time: cot::eud::EudCoTPayload) -> &'static str {
|
|
||||||
let payload = cursor_over_time.to_cot().convert_to_xml();
|
|
||||||
send_payload(ctx, payload);
|
|
||||||
|
|
||||||
"Sending End User Device Cursor Over Time to TCP server"
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn send_marker_cot(ctx: Context, cursor_over_time: cot::nato::MarkerCoTPayload) -> &'static str {
|
|
||||||
let payload = cursor_over_time.to_cot().convert_to_xml();
|
|
||||||
send_payload(ctx, payload);
|
|
||||||
|
|
||||||
"Sending Marker Cursor Over Time to TCP server"
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn send_digital_pointer_cot(ctx: Context, cursor_over_time: cot::digital_pointer::DigitalPointerPayload) -> &'static str {
|
|
||||||
let payload = cursor_over_time.to_cot().convert_to_xml();
|
|
||||||
send_payload(ctx, payload);
|
|
||||||
|
|
||||||
"Sending Digital Pointer Cursor Over Time to TCP server"
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn stop(ctx: Context) -> &'static str {
|
|
||||||
if let Some(ref client) = *TCP_CLIENT.lock().unwrap() {
|
|
||||||
client.stop();
|
|
||||||
let _ = ctx.callback_null("TCP SOCKET", "TCP client stopped");
|
|
||||||
} else {
|
|
||||||
let _ = ctx.callback_null("TCP SOCKET ERROR", "TCP client is not running");
|
|
||||||
}
|
|
||||||
|
|
||||||
"Stopping TCP Client"
|
|
||||||
}
|
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::init;
|
use crate::init;
|
||||||
use uuid::Uuid;
|
|
||||||
use std::vec;
|
use std::vec;
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn uuid_output_is_uuid4_identifier() {
|
fn uuid_output_is_uuid4_identifier() {
|
||||||
@@ -17,9 +17,9 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn uuid_output_throws_if_passed_args() {
|
fn uuid_output_throws_if_passed_args() {
|
||||||
let extension = init().testing();
|
let extension = init().testing();
|
||||||
let args: Vec<String> = vec![1.to_string(),2.to_string()];
|
let args: Vec<String> = vec![1.to_string(), 2.to_string()];
|
||||||
let (output, _) = extension.call("uuid", Some(args));
|
let (output, _) = extension.call("uuid", Some(args));
|
||||||
|
|
||||||
assert_eq!(output,"")
|
assert_eq!(output, "")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
1021
src/uas/callbacks.rs
Normal file
1021
src/uas/callbacks.rs
Normal file
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user