Refacted UDP Socket config and created UAS addon

This commit is contained in:
2026-05-07 03:53:19 -03:00
parent 52edf94b17
commit 6376b7acf0
9 changed files with 225 additions and 45 deletions

View File

@@ -12,7 +12,7 @@ class armatak_udp_socket_start_dialog {
x = "0.386562 * safezoneW + safezoneX"; x = "0.386562 * safezoneW + safezoneX";
y = "0.357 * safezoneH + safezoneY"; y = "0.357 * safezoneH + safezoneY";
w = "0.216563 * safezoneW"; w = "0.216563 * safezoneW";
h = "0.319 * safezoneH"; h = "0.418 * safezoneH";
colorBackground[]={0,0,0,0.45}; colorBackground[]={0,0,0,0.45};
}; };
}; };
@@ -44,6 +44,16 @@ class armatak_udp_socket_start_dialog {
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_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 = "EUD's Address"; text = "EUD's Address";
@@ -68,12 +78,20 @@ class armatak_udp_socket_start_dialog {
w = "0.20625 * safezoneW"; w = "0.20625 * safezoneW";
h = "0.033 * safezoneH"; 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";
h = "0.033 * safezoneH";
};
class armatak_gui_module_udp_socket_dialog_address_button_cancel: RscButton { class armatak_gui_module_udp_socket_dialog_address_button_cancel: RscButton {
idc = 16965; idc = 16965;
text = "Cancel"; text = "Cancel";
action = "closeDialog 2;"; action = "closeDialog 2;";
x = "0.551563 * safezoneW + safezoneX"; x = "0.551563 * safezoneW + safezoneX";
y = "0.632 * safezoneH + safezoneY"; y = "0.709 * safezoneH + safezoneY";
w = "0.0464063 * safezoneW"; w = "0.0464063 * safezoneW";
h = "0.055 * safezoneH"; h = "0.055 * safezoneH";
}; };
@@ -82,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.632 * safezoneH + safezoneY"; y = "0.709 * safezoneH + safezoneY";
w = "0.0464063 * safezoneW"; w = "0.0464063 * safezoneW";
h = "0.055 * safezoneH"; h = "0.055 * safezoneH";
}; };

View File

@@ -14,19 +14,24 @@ disableSerialization;
private _eud_address = ctrlText 16961; private _eud_address = ctrlText 16961;
private _gnss_port = ctrlText 16962; private _gnss_port = ctrlText 16962;
private _mavlink_port = ctrlText 16967; private _mavlink_port = ctrlText 16967;
private _video_feed_url = ctrlText 16969;
private _udp_socket_fulladdress = _eud_address + ":" + _gnss_port; private _udp_socket_fulladdress = _eud_address + ":" + _gnss_port;
private _mavlink_address = _eud_address + ":" + _mavlink_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(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]; private _mdnsInstanceName = format ["ArmaTAK-%1", name player];
"armatak" callExtension ["mdns:start_uas_advertisement", [_mdnsInstanceName, parseNumber _mavlink_port, "rtp://0.0.0.0:5600"]]; "armatak" callExtension ["mdns:start_uas_advertisement", [_mdnsInstanceName, parseNumber _mavlink_port, _advertised_video_uri]];
"armatak" callExtension ["log", [["info", format ["Client UDP socket started for %1 and MAVLink target set to %2", _udp_socket_fulladdress, _mavlink_address]]]]; "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); call EFUNC(uav,startMavlinkBroadcast);

View File

@@ -80,7 +80,7 @@ class Cfg3den {
}; };
class armatak_attribute_marker_video_url { class armatak_attribute_marker_video_url {
displayName = "Video Feed URL"; displayName = "Video Feed URL";
tooltip = "Video feed URL injected into __video on TAK"; 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"; property = "armatak_attribute_marker_video_url";
control = "Edit"; control = "Edit";
expression = "_this setVariable ['armatak_attribute_marker_video_url',_value]"; expression = "_this setVariable ['armatak_attribute_marker_video_url',_value]";

View File

@@ -14,6 +14,7 @@ addMissionEventHandler ["ExtensionCallback", {
case "EUD Disconnected": { case "EUD Disconnected": {
SETVAR(player,EGVAR(client,eudConnected),false); SETVAR(player,EGVAR(client,eudConnected),false);
call EFUNC(uav,stopMavlinkBroadcast); call EFUNC(uav,stopMavlinkBroadcast);
"armatak" callExtension ["uas:stop_endpoint", []];
"armatak" callExtension ["mdns:stop", []]; "armatak" callExtension ["mdns:stop", []];
}; };
default {}; default {};
@@ -28,15 +29,45 @@ 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); call EFUNC(uav,stopMavlinkBroadcast);
"armatak" callExtension ["uas:stop_endpoint", []];
"armatak" callExtension ["mdns:stop", []]; "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); call EFUNC(uav,stopMavlinkBroadcast);
"armatak" callExtension ["uas:stop_endpoint", []];
"armatak" callExtension ["mdns:stop", []]; "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 "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);
}; };

View File

@@ -1,3 +1,6 @@
PREP(startMavlinkBroadcast); PREP(startMavlinkBroadcast);
PREP(stopMavlinkBroadcast); PREP(stopMavlinkBroadcast);
PREP(updateMavlinkBroadcast); PREP(updateMavlinkBroadcast);
PREP(resolveVideoUri);
PREP(handleMavlinkCallback);
PREP(parseMavlinkCallbackData);

View File

@@ -0,0 +1,63 @@
#include "..\script_component.hpp"
params ["_function", ["_data", "", [""]]];
if (!hasInterface) exitWith {};
private _payload = [_data] call FUNC(parseMavlinkCallbackData);
private _uav = getConnectedUAV player;
if (isNull _uav) exitWith {
"armatak" callExtension ["log", [["warn", format ["Ignoring MAVLINK UDP callback %1 because no UAV is connected: %2", _function, _data]]]];
};
private _command = parseNumber (_payload getOrDefault ["command", "-1"]);
private _commandName = _payload getOrDefault ["command_name", "UNKNOWN"];
switch (_function) do {
case "COMMAND_LONG": {
switch (_command) do {
case 400: {
private _armValue = parseNumber (_payload getOrDefault ["param1", "0"]);
private _doArm = _armValue >= 1;
_uav engineOn _doArm;
if (_doArm) then {
_uav setFuel ((fuel _uav) max 0.1);
};
systemChat format ["ATAK %1 %2", ["DISARM", "ARM"] select _doArm, [_uav] call armatak_fnc_extract_marker_callsign];
"armatak" callExtension ["log", [["info", format ["Applied MAVLINK COMMAND_LONG %1 (%2) to UAV %3", _command, _commandName, _uav]]]];
};
case 22: {
_uav engineOn true;
_uav setFuel ((fuel _uav) max 0.1);
_uav flyInHeight 75;
if (_uav isKindOf "Helicopter" || {_uav isKindOf "VTOL_Base_F"} || {_uav isKindOf "Quadbike_01_F"}) then {
private _velocity = velocityModelSpace _uav;
_uav setVelocityModelSpace [
_velocity select 0,
(_velocity select 1) max 15,
((_velocity select 2) max 0) + 8
];
};
systemChat format ["ATAK TAKEOFF %1", [_uav] call armatak_fnc_extract_marker_callsign];
"armatak" callExtension ["log", [["info", format ["Applied MAVLINK TAKEOFF to UAV %1", _uav]]]];
};
default {
"armatak" callExtension ["log", [["info", format ["Unhandled MAVLINK COMMAND_LONG %1 (%2): %3", _command, _commandName, _data]]]];
};
};
};
case "COMMAND_INT": {
"armatak" callExtension ["log", [["info", format ["Received MAVLINK COMMAND_INT %1 (%2): %3", _command, _commandName, _data]]]];
};
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]]]];
};
};

View 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

View File

@@ -0,0 +1,37 @@
#include "..\script_component.hpp"
params [["_uav", objNull, [objNull]]];
private _defaultVideoUri = "rtsp://irontak.com: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 && {_activelyControlledUav isEqualTo _uav}) then {
private _objectVideoUrl = [_uav] call armatak_fnc_extract_marker_video_url;
private _normalizedObjectVideoUrl = [_objectVideoUrl] call _normalize;
if (_normalizedObjectVideoUrl isNotEqualTo "") exitWith {
_normalizedObjectVideoUrl
};
};
private _sessionVideoUrl = player getVariable [QEGVAR(client,video_feed_url), ""];
private _normalizedSessionVideoUrl = [_sessionVideoUrl] call _normalize;
if (_normalizedSessionVideoUrl isNotEqualTo "") exitWith {
_normalizedSessionVideoUrl
};
_defaultVideoUri

View File

@@ -37,6 +37,9 @@ private _mavlinkAddress = player getVariable [QEGVAR(client,mavlink_address), ""
if (_mavlinkAddress isEqualTo "") exitWith {}; if (_mavlinkAddress isEqualTo "") exitWith {};
private _pos = [_uav] call EFUNC(client,extractClientPosition); private _pos = [_uav] call EFUNC(client,extractClientPosition);
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 _dir = vectorDir _uav;
private _up = vectorUp _uav; private _up = vectorUp _uav;
private _yaw = getDir _uav; private _yaw = getDir _uav;
@@ -45,24 +48,14 @@ private _roll = asin (((_up select 0) max -1) min 1);
private _relAlt = ((getPosATL _uav) select 2) max 0; private _relAlt = ((getPosATL _uav) select 2) max 0;
private _uavType = if (_uav isKindOf "Plane") then {1} else {[2, 3] select (_uav isKindOf "Helicopter")}; private _uavType = if (_uav isKindOf "Plane") then {1} else {[2, 3] select (_uav isKindOf "Helicopter")};
private _bodyPayload = [ private _gimbalRoll = 0;
_mavlinkAddress, private _gimbalPitch = _pitch;
1, private _gimbalYaw = _yaw;
1, private _hfov = _uav getVariable ["armatak_uas_fov", 60];
_uavType, private _vfov = _uav getVariable ["armatak_uas_vfov", (_hfov * 0.5625)];
_pos select 1, private _imageLat = _pos select 1;
_pos select 2, private _imageLon = _pos select 2;
_pos select 3, private _imageAlt = _pos select 3;
_relAlt,
_pos select 5,
_pos select 6,
_roll,
_pitch,
_yaw,
parseNumber isEngineOn _uav
];
"armatak" callExtension ["mavlink_mock:send_uas_telemetry", [_bodyPayload]];
private _laser = laserTarget _uav; private _laser = laserTarget _uav;
if (!isNull _laser) then { if (!isNull _laser) then {
@@ -84,27 +77,40 @@ if (!isNull _laser) then {
private _camYaw = ((_dirX atan2 _dirY) + 360) mod 360; private _camYaw = ((_dirX atan2 _dirY) + 360) mod 360;
private _camPitch = _dirZ atan2 (_horizontalMag max 0.001); private _camPitch = _dirZ atan2 (_horizontalMag max 0.001);
_gimbalPitch = _camPitch;
_gimbalYaw = _camYaw;
private _spiAgl = ASLToAGL _spiASL; private _imageGeo = [_targetWorld select 0, _targetWorld select 1, _targetAslZ] call EFUNC(client,convertClientLocation);
private _camRelAlt = (_spiAgl select 2) max 0; _imageLat = _imageGeo select 0;
_imageLon = _imageGeo select 1;
_imageAlt = _imageGeo select 2;
};
};
private _camPayload = [ private _systemPayload = [
_mavlinkAddress, _mavlinkAddress,
2, _uuid,
1, _callsign,
_uavType, _uavType,
_pos select 1, _pos select 1,
_pos select 2, _pos select 2,
_pos select 3, _pos select 3,
_camRelAlt, _relAlt,
_camYaw, _pos select 5,
0, _pos select 6,
0, _roll,
_camPitch, _pitch,
_camYaw, _yaw,
1 parseNumber isEngineOn _uav,
_gimbalRoll,
_gimbalPitch,
_gimbalYaw,
_videoUri,
_hfov,
_vfov,
_imageLat,
_imageLon,
_imageAlt
]; ];
"armatak" callExtension ["mavlink_mock:send_uas_telemetry", [_camPayload]]; "armatak" callExtension ["uas:send_uas_system", [_systemPayload]];
};
};