Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dev tak #176

Merged
merged 2 commits into from
Dec 11, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion common/ros_packages/airstack_msgs/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -8,6 +8,7 @@ endif()
find_package(ament_cmake REQUIRED)
find_package(std_msgs REQUIRED)
find_package(geometry_msgs REQUIRED)
find_package(sensor_msgs REQUIRED)
find_package(diagnostic_msgs REQUIRED)
find_package(rosidl_default_generators REQUIRED)

@@ -22,10 +23,11 @@ rosidl_generate_interfaces(${PROJECT_NAME}
"msg/BeliefMapData.msg"
"msg/SearchPrior.msg"
"msg/TaskAssignment.msg"
"msg/query/TextQueryResponse.msg"
"srv/RobotCommand.srv"
"srv/TrajectoryMode.srv"
"srv/TakeoffLandingCommand.srv"
DEPENDENCIES std_msgs geometry_msgs diagnostic_msgs
DEPENDENCIES std_msgs geometry_msgs sensor_msgs diagnostic_msgs
)

if(BUILD_TESTING)
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# This message type is used for the response of a text query from a robot semantic query

std_msgs/Header header # Header for the message, usually contains timestamp and frame_id
string tag_name # The tag associated with the query or response
sensor_msgs/NavSatFix[] geofence # Array of GPS fixes (NavSatFix) representing the geofence
5 changes: 3 additions & 2 deletions common/ros_packages/airstack_msgs/package.xml
Original file line number Diff line number Diff line change
@@ -12,11 +12,12 @@
<depend>std_msgs</depend>
<depend>geometry_msgs</depend>
<depend>diagnostic_msgs</depend>

<depend>sensor_msgs</depend>

<buildtool_depend>rosidl_default_generators</buildtool_depend>
<exec_depend>rosidl_default_runtime</exec_depend>
<member_of_group>rosidl_interface_packages</member_of_group>

<test_depend>ament_lint_auto</test_depend>
<test_depend>ament_lint_common</test_depend>

6 changes: 6 additions & 0 deletions ground_control_station/docker/.env
Original file line number Diff line number Diff line change
@@ -20,6 +20,8 @@ TAK_SUBSCRIBER_FILEPATH=src/ros2tak_tools/scripts/tak_subscriber.py
MQTT_USERNAME= # Enter the MQTT username
MQTT_PASSWORD= # Enter the MQTT password

# Chat interface configuration -----------------------------------------
AI_AGENT_NAME=aerolens.ai (BOT)

# GSTREAMER TO ROS NODES -----------------------------------------------

@@ -30,3 +32,7 @@ CAMERA1_ROS_TOPIC=/view1/image_raw
# CAMERA 2
CAMERA2_STREAM_IP=rtsp://
CAMERA2_ROS_TOPIC=/view2/image_raw


# ROS VARIABLES -------------------------------------------------------
ROS_DOMAIN_ID=200
37 changes: 37 additions & 0 deletions ground_control_station/docker/Dockerfile.ros2casevac_agent
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Use the official ROS 2 Humble base image
FROM ros:humble

ARG ROS_WS_DIR

# Set working directory
WORKDIR ${ROS_WS_DIR}

# Update and install necessary dependencies
RUN apt-get update && apt-get install -y \
python3-pip \
ros-humble-rosbag2 \
ros-humble-rosbag2-storage-mcap \
python3-colcon-common-extensions \
&& rm -rf /var/lib/apt/lists/*

# Upgrade pip and install Python dependencies
RUN pip3 install --upgrade pip \
&& pip3 install setuptools==57.5.0 pytak pyyaml \
&& pip3 install paho-mqtt

# Source ROS 2 setup file to ensure environment variables are set
# You may want to add this to ENTRYPOINT or CMD if you're working interactively
RUN echo "source /opt/ros/humble/setup.bash" >> ~/.bashrc

# COPY the agent code into the container
COPY ground_control_station/ros_ws/src/ros2tak_tools ${ROS_WS_DIR}/src/ros2tak_tools
COPY common/ros_packages/straps_msgs ${ROS_WS_DIR}/src/straps_msgs

# Build the ROS 2 workspace
RUN /bin/bash -c "source /opt/ros/humble/setup.bash && \
colcon build --symlink-install --packages-select straps_msgs && \
colcon build --symlink-install"

# Source the workspace setup file to ensure environment variables are set
RUN echo "source ${ROS_WS_DIR}/install/setup.bash" >> ~/.bashrc

182 changes: 182 additions & 0 deletions ground_control_station/docker/docker-compose.yaml
Original file line number Diff line number Diff line change
@@ -47,3 +47,185 @@ services:
- ../../common/ros_packages:/root/ros_ws/src/common:rw # common ROS packages
- ../ros_ws:/root/ros_ws:rw # gcs-specific ROS packages
- ../../common/ros_packages/fastdds.xml:/root/ros_ws/fastdds.xml:rw # fastdds.xml


####################### GSTREAMER TO ROS TOPICS ######################
# gst-ros-bridge-topic1:
# container_name: "${PROJECT_NAME}-gst_ros_bridge1"
# image: "${PROJECT_NAME}/gcs/gst-ros-bridge"
# build:
# context: .
# dockerfile: Dockerfile.gst-ros-bridge
# command: >
# /bin/bash -c 'source /ros_ws/install/setup.bash && gst-launch-1.0 --gst-plugin-path=/ros_ws/install/gst_bridge/lib/gst_bridge/
# rtspsrc location="${CAMERA1_STREAM_IP}" latency=0 !
# rtph265depay ! h265parse ! avdec_h265 ! videoconvert !
# rosimagesink ros-topic="${CAMERA1_ROS_TOPIC}"'
# environment:
# - DISPLAY=${DISPLAY}
# - DOCKER_BUILDKIT=0
# - CAMERA1_STREAM_IP=${CAMERA1_STREAM_IP}
# - CAMERA1_ROS_TOPIC=${CAMERA1_ROS_TOPIC}
# volumes:
# - /tmp/.X11-unix:/tmp/.X11-unix
# network_mode: host

####################### ROS2TAK TOOLS ######################
############### MQTT for the GCS
mqtt:
container_name: "mqtt"
image: eclipse-mosquitto:2.0.20
restart: always
volumes:
- ../ros_ws/src/ros2tak_tools/mosquitto/config:/mosquitto/config
- ../ros_ws/src/ros2tak_tools/mosquitto/data:/mosquitto/data
- ../ros_ws/src/ros2tak_tools/mosquitto/log:/mosquitto/log
env_file:
- .env
ports:
- "1883:1883"
healthcheck:
test: [ "CMD", "mosquitto_pub", "-h", "localhost", "-t", "healthcheck", "-m", "ping", "-u", "${MQTT_USERNAME}", "-P", "${MQTT_PASSWORD}" ]
interval: 5s
timeout: 3s
retries: 2
start_period: 5s
# network_mode: host
networks:
- airstack_network
################## ROS2COT_AGENT
ros2cot_agent:
build:
context: ../
dockerfile: docker/Dockerfile.ros2tak_tools
args:
- ROS_WS_DIR=${ROS_WS_DIR}
image: "${PROJECT_NAME}/gcs/ros2cot_agent"
container_name: "${PROJECT_NAME}-ros2cot_agent"
stdin_open: true
tty: true
restart: unless-stopped
environment:
- ROS_DOMAIN_ID=${ROS_DOMAIN_ID}
depends_on:
mqtt:
condition: service_healthy
# network_mode: host
networks:
- airstack_network
command: [ "/bin/bash", "-c", "source /opt/ros/humble/setup.bash && source $ROS_WS_DIR/install/setup.bash && ./install/ros2tak_tools/bin/ros2cot_agent --config $ROS_WS_DIR/$ROS2TAK_TOOLS_CONFIG_DIR/$ROS2TAK_TOOLS_CONFIG_FILENAME" ]

# ################### TAK_PUBLISHER
tak_publisher:
build:
context: ../
dockerfile: docker/Dockerfile.tak_publisher
args:
- ROS_WS_DIR=${ROS_WS_DIR}
image: "${PROJECT_NAME}/gcs/tak_publisher"
container_name: "${PROJECT_NAME}-tak_publisher"
stdin_open: true
tty: true
restart: unless-stopped
depends_on:
mqtt:
condition: service_healthy
# network_mode: host
networks:
- airstack_network
volumes:
- ../ros_ws/src/ros2tak_tools/:${ROS_WS_DIR}/src/ros2tak_tools/
command: [ "python3", "$TAK_PUBLISHER_FILEPATH", "--config", "$ROS2TAK_TOOLS_CONFIG_DIR/$ROS2TAK_TOOLS_CONFIG_FILENAME" ]
################### TAK_SUBSCRIBER
tak_subscriber:
build:
context: ../
dockerfile: docker/Dockerfile.tak_subscriber
args:
- ROS_WS_DIR=${ROS_WS_DIR}
image: "${PROJECT_NAME}/gcs/tak_subscriber"
container_name: "${PROJECT_NAME}-tak_subscriber"
stdin_open: true
tty: true
restart: unless-stopped
depends_on:
mqtt:
condition: service_healthy
networks:
- airstack_network
volumes:
- ../ros_ws/src/ros2tak_tools/:${ROS_WS_DIR}/src/ros2tak_tools/
command: [ "python3", "$TAK_SUBSCRIBER_FILEPATH", "--config", "$ROS2TAK_TOOLS_CONFIG_DIR/$ROS2TAK_TOOLS_CONFIG_FILENAME" ]

################## ROS2COT_AGENT
cot2planner_agent:
build:
context: ../../
dockerfile: ground_control_station/docker/Dockerfile.TAK
args:
- ROS_WS_DIR=${ROS_WS_DIR}
image: "${PROJECT_NAME}/gcs/cot2planner_agent"
container_name: "${PROJECT_NAME}-cot2planner_agent"
stdin_open: true
tty: true
restart: unless-stopped
depends_on:
mqtt:
condition: service_healthy
networks:
- airstack_network
command: [ "/bin/bash", "-c", "source /opt/ros/humble/setup.bash && source $ROS_WS_DIR/install/setup.bash && ./install/ros2tak_tools/bin/cot2planner_agent --config $ROS_WS_DIR/$ROS2TAK_TOOLS_CONFIG_DIR/$ROS2TAK_TOOLS_CONFIG_FILENAME" ]

# ################## ROS2COT_AGENT
ros2casevac_agent:
build:
context: ../../
dockerfile: ground_control_station/docker/Dockerfile.ros2casevac_agent
args:
- ROS_WS_DIR=${ROS_WS_DIR}
image: "${PROJECT_NAME}/gcs/ros2casevac_agent"
container_name: "${PROJECT_NAME}-ros2casevac_agent"
stdin_open: true
tty: true
restart: unless-stopped
depends_on:
mqtt:
condition: service_healthy
environment:
- ROS_WS_DIR=${ROS_WS_DIR}
- ROS_DOMAIN_ID=${ROS_DOMAIN_ID}
working_dir: $ROS_WS_DIR
# network_mode: host
networks:
- airstack_network
command: [ "/bin/bash", "-c", "source /opt/ros/humble/setup.bash && source $ROS_WS_DIR/install/setup.bash && ./install/ros2tak_tools/bin/ros2casevac_agent --config $ROS_WS_DIR/$ROS2TAK_TOOLS_CONFIG_DIR/$ROS2TAK_TOOLS_CONFIG_FILENAME" ]

# ################## GCS_AI_AGENT
gcs_ai_agent:
build:
context: ../../
dockerfile: ground_control_station/docker/Dockerfile.TAK
args:
- ROS_WS_DIR=${ROS_WS_DIR}
image: "${PROJECT_NAME}/gcs/gcs_ai_agent"
container_name: "${PROJECT_NAME}-gcs_ai_agent"
stdin_open: true
tty: true
restart: unless-stopped
environment:
- ROS_DOMAIN_ID=${ROS_DOMAIN_ID}
- AI_AGENT_NAME=${AI_AGENT_NAME}
- PROJECT_NAME=${PROJECT_NAME}
depends_on:
mqtt:
condition: service_healthy
# network_mode: host
networks:
- airstack_network
command: [ "/bin/bash", "-c", "source /opt/ros/humble/setup.bash && source $ROS_WS_DIR/install/setup.bash && ./install/ros2tak_tools/bin/chat2ros_agent --config $ROS_WS_DIR/$ROS2TAK_TOOLS_CONFIG_DIR/$ROS2TAK_TOOLS_CONFIG_FILENAME" ]


########### NETWORKS ###########
networks:
airstack_network:
driver: bridge
57 changes: 37 additions & 20 deletions ground_control_station/ros_ws/src/ros2tak_tools/config/config.yaml
Original file line number Diff line number Diff line change
@@ -72,29 +72,46 @@ services:
# TAK_Subscriber (below) service has more information on the ROS topics.
topic_name: planner_events # Topic name at MQTT for the subscriber service that sends COT messages subscribed from the TAK server.

chat2ros_agent:
mqtt_subcribe_topic: dsta-operator # Topic name at MQTT for the subscriber service that sends COT messages subscribed from the TAK server.
ros_query_text_topic: '/query/text' # ROS Topic name to publish the chat queries.
ros_query_response_topic: '/query/response' # ROS Topic name to publish the chat responses.
filter_name: dsta-operator

ros2casevac_agent:
# this service is used to generate ROS messages from HOSTIP:PORT to ROS topics.
# TAK_Subscriber (below) service has more information on the ROS topics.
topic_name: to_tak # MQTT topic name to send the COT messages to.
ros_casualty_meta_topic_name: '/casualty/meta' # ROS Topic name to publish the casevac messages.
ros_casualty_image_topic_name: '/casualty/image' # ROS message type for the casevac messages.

subscriber:
tak_subscriber:
# this service is used to subscribe to CoT messages from TAK server and send them to HOSTIP:PORT.
filter_messages: # Type of messages to subscribe to. Options:
- name: 'target'
# ROS Topic name to publish the target messages. Use {n} as a placeholder for the robot number.
# If provided, the topic name will be formatted with the robot number extracted from the message name.
ros_topic_name: '/target{n}/gps/gt'
ros_msg_type: NavSatFix # ROS message type for the target messages.
mqtt_topic_name: target_from_tak # Topic name at MQTT for the subscriber service that sends COT messages subscribed from the TAK server.
- name: 'iphone'
# ROS Topic name to publish the target messages. Use {n} as a placeholder for the robot number.
# If provided, the topic name will be formatted with the robot number extracted from the message name.
ros_topic_name: '/iphone{n}/gps/gt'
ros_msg_type: NavSatFix # ROS message type for the target messages.
mqtt_topic_name: iphone_from_tak # Topic name at MQTT for the subscriber service that sends COT messages subscribed from the TAK server.
- name: 'base'
ros_topic_name: '/basestation/gps'
ros_msg_type: NavSatFix # ROS message type for the target messages.
mqtt_topic_name: base_from_tak # Topic name at MQTT for the subscriber service that sends COT messages subscribed from the TAK server.
- name: 'planner'
ros_topic_name: '/planner/planconfig' # ROS Topic name to publish the shapes messages.
ros_msg_type: MarkerArray # ROS message type for the shapes messages.
mqtt_topic_name: planner_events # Topic name at MQTT for the subscriber service that sends COT messages subscribed from the TAK server.
- name: 'target'
# ROS Topic name to publish the target messages. Use {n} as a placeholder for the robot number.
# If provided, the topic name will be formatted with the robot number extracted from the message name.
ros_topic_name: '/target{n}/gps/gt'
ros_msg_type: NavSatFix # ROS message type for the target messages.
mqtt_topic_name: target_from_tak # Topic name at MQTT for the subscriber service that sends COT messages subscribed from the TAK server.
- name: 'iphone'
# ROS Topic name to publish the target messages. Use {n} as a placeholder for the robot number.
# If provided, the topic name will be formatted with the robot number extracted from the message name.
ros_topic_name: '/iphone{n}/gps/gt'
ros_msg_type: NavSatFix # ROS message type for the target messages.
mqtt_topic_name: iphone_from_tak # Topic name at MQTT for the subscriber service that sends COT messages subscribed from the TAK server.
- name: 'base'
ros_topic_name: '/basestation/gps'
ros_msg_type: NavSatFix # ROS message type for the target messages.
mqtt_topic_name: base_from_tak # Topic name at MQTT for the subscriber service that sends COT messages subscribed from the TAK server.
- name: 'planner'
ros_topic_name: '/planner/planconfig' # ROS Topic name to publish the shapes messages.
ros_msg_type: MarkerArray # ROS message type for the shapes messages.
mqtt_topic_name: dsta-operator # Topic name at MQTT for the subscriber service that sends COT messages subscribed from the TAK server.
- name: 'dsta-operator'
ros_topic_name: 'NA' # ROS Topic name to publish the shapes messages.
ros_msg_type: NA # ROS message type for the shapes messages.
mqtt_topic_name: dsta-operator # Topic name at MQTT for the subscriber service that sends COT messages subscribed from the TAK server.
# target: Target messages
# planner: Planner messages
25 changes: 0 additions & 25 deletions ground_control_station/ros_ws/src/ros2tak_tools/package.xml

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
Metadata-Version: 2.1
Name: ros2tak_tools
Version: 0.0.0
Summary: TODO: Package description
Maintainer: mission-operator
Maintainer-email: mission-operator@todo.todo
License: TODO: License declaration
Requires-Dist: setuptools
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
README.md
package.xml
setup.py
resource/ros2tak_tools
ros2tak_tools/Casualty.py
ros2tak_tools/__init__.py
ros2tak_tools/chat2ros_agent.py
ros2tak_tools/cot2planner_agent.py
ros2tak_tools/ros2casevac_agent.py
ros2tak_tools/ros2cot_agent.py
ros2tak_tools.egg-info/PKG-INFO
ros2tak_tools.egg-info/SOURCES.txt
ros2tak_tools.egg-info/dependency_links.txt
ros2tak_tools.egg-info/entry_points.txt
ros2tak_tools.egg-info/requires.txt
ros2tak_tools.egg-info/top_level.txt
ros2tak_tools.egg-info/zip-safe
tak_helper/__init__.py
tak_helper/create_cot_msgs.py
tak_helper/tak_publisher.py
tak_helper/tak_subscriber.py
test/test_copyright.py
test/test_flake8.py
test/test_pep257.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[console_scripts]
chat2ros_agent = ros2tak_tools.chat2ros_agent:main
cot2planner_agent = ros2tak_tools.cot2planner_agent:main
cot2ros_agent = ros2tak_tools.cot2ros_agent:main
ros2casevac_agent = ros2tak_tools.ros2casevac_agent:main
ros2cot_agent = ros2tak_tools.ros2cot_agent:main
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
setuptools
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
ros2tak_tools
tak_helper
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -118,7 +118,7 @@ def __init__(self, config_file):
self.mqtt_client.on_message = self._on_mqtt_message

# Connect to MQTT broker and start loop
self.mqtt_client.connect(self.mqtt_broker, self.mqtt_port)
self.mqtt_client.connect(self.mqtt_broker, self.mqtt_port, keepalive=65535)
self.mqtt_client.subscribe(self.mqtt_topicname) # Subscribe to the dynamic planner topic
self.mqtt_client.loop_start()

@@ -248,7 +248,7 @@ def main(args=None):
import argparse
parser = argparse.ArgumentParser(description="COT to Planner")
parser.add_argument('--config', type=str, required=True, help='Path to the config YAML file.')
args, unknown = parser.parse_known_args()
args = parser.parse_args()

cot2planner = Cot2Planner(args.config)
rclpy.spin(cot2planner)

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -64,12 +64,12 @@ def create_COT_pos_event(uuid, latitude, longitude, altitude):
"""Create a CoT event based on the GPS data."""
root = ET.Element("event")
root.set("version", "2.0")
root.set("type", "a-h-A-M-A")
root.set("type", "a-f-G")
root.set("uid", uuid) # Use topic name as UID for identification
root.set("how", "m-g")
root.set("time", pytak.cot_time())
root.set("start", pytak.cot_time())
root.set("stale", pytak.cot_time(60))
root.set("stale", pytak.cot_time(3600))

pt_attr = {
"lat": str(latitude),
@@ -110,7 +110,7 @@ def __init__(self, config):
self.mqtt_client.username_pw_set(self.mqtt_username, self.mqtt_pwd)
try:
print(f"Trying to connect to {self.mqtt_broker}:{self.mqtt_port}")
self.mqtt_client.connect(self.mqtt_broker, self.mqtt_port)
self.mqtt_client.connect(self.mqtt_broker, self.mqtt_port, keepalive=65535)
self.get_logger().info(
f"Connected to MQTT ({self.mqtt_broker}:{self.mqtt_port})"
)
@@ -130,7 +130,7 @@ def __init__(self, config):
subscriber = self.create_subscription(
NavSatFix,
topic_name,
lambda msg, topic_name=topic_name: self.gps_callback(msg, topic_name),
lambda msg, topic_name=topic_name: self.gps_callback(msg, f"/robot{i}"),
10, # QoS depth
)
self.subscribers.append(subscriber)
@@ -192,8 +192,8 @@ def main(args=None):
)

# Parse the arguments
input_args, unknown = parser.parse_known_args()
input_args = parser.parse_args()

# Load configuration
config = load_config(input_args.config)

Original file line number Diff line number Diff line change
@@ -51,7 +51,7 @@ def __init__(self, tx_queue, config, event_loop):
# Connect to MQTT broker and subscribe to topic
try:
print(f"Connecting to {self.mqtt_broker}:{self.mqtt_port}")
self.mqtt_client.connect(self.mqtt_broker, self.mqtt_port)
self.mqtt_client.connect(self.mqtt_broker, self.mqtt_port, keepalive=65535)
self.mqtt_client.subscribe(self.mqtt_topicname)
print(f"Connected and subscribed to MQTT topic '{self.mqtt_topicname}' on broker {self.mqtt_broker}:{self.mqtt_port}")
except Exception as e:
@@ -80,7 +80,7 @@ async def run(self, number_of_iterations=-1):
self.mqtt_client.loop_stop()
print("MQTT loop stopped.")

async def async_main(config):
async def main(config):
loop = asyncio.get_running_loop() # Capture the main event loop
clitool = pytak.CLITool(config["mycottool"])
await clitool.setup()
@@ -92,12 +92,9 @@ def run_main_in_process(config):
loop.run_until_complete(main(config))

if __name__ == "__main__":
main()

def main():
parser = argparse.ArgumentParser(description="TAK Publisher Script")
parser.add_argument('--config', type=str, required=True, help='Path to the config YAML file.')
args, unknown = parser.parse_known_args()
args = parser.parse_args()

# Load the YAML configuration
with open(args.config, 'r') as file:
@@ -143,4 +140,4 @@ def main():
process.start()

print("Main() is now running in a separate process.")
process.join()
process.join()
Original file line number Diff line number Diff line change
@@ -69,7 +69,7 @@ def __init__(self, rx_queue, config, filter_names2topic):
# Connect to MQTT broker and subscribe to topic
try:
self._logger.info(f"Connecting to {self.mqtt_broker}:{self.mqtt_port}")
self.mqtt_client.connect(self.mqtt_broker, self.mqtt_port)
self.mqtt_client.connect(self.mqtt_broker, self.mqtt_port, keepalive=65535)
self._logger.info(f"Connected and subscribed to MQTT on broker {self.mqtt_broker}:{self.mqtt_port}")
except Exception as e:
self._logger.error(f"Failed to connect or subscribe to MQTT: {e}")
@@ -135,18 +135,17 @@ async def send_to_mqtt(self, data, mqtt_topic):
except:
self._logger.info(f"Failed to publish.")


async def run(self): # pylint: disable=arguments-differ
"""Read from the receive queue, put data onto handler."""
while True:
data = await self.queue.get() # Get received CoT from rx_queue
await self.handle_data(data)


async def async_main():
async def main():
parser = argparse.ArgumentParser(description="TAK Subscriber Script")
parser.add_argument('--config', type=str, required=True, help='Path to the config YAML file.')
args, unknown = parser.parse_known_args()
args = parser.parse_args()

# Load the YAML configuration
with open(args.config, 'r') as file:
@@ -197,8 +196,6 @@ async def async_main():
# Start all tasks.
await clitool.run()

def main():
asyncio.run(async_main())

if __name__ == "__main__":
main()
asyncio.run(main())
4 changes: 0 additions & 4 deletions ground_control_station/ros_ws/src/ros2tak_tools/setup.cfg

This file was deleted.

8 changes: 3 additions & 5 deletions ground_control_station/ros_ws/src/ros2tak_tools/setup.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
from setuptools import find_packages, setup
import glob

package_name = 'ros2tak_tools'

@@ -11,7 +10,6 @@
('share/ament_index/resource_index/packages',
['resource/' + package_name]),
('share/' + package_name, ['package.xml']),
('share/' + package_name + '/config/', glob.glob('config/*')),
],
install_requires=['setuptools'],
zip_safe=True,
@@ -24,9 +22,9 @@
'console_scripts': [
'ros2cot_agent = ros2tak_tools.ros2cot_agent:main',
'cot2ros_agent = ros2tak_tools.cot2ros_agent:main',
'cot2planner_agent = ros2tak_tools.cot2planner_agent:main',
'tak_subscriber = ros2tak_tools.tak_subscriber:main',
'tak_publisher = ros2tak_tools.tak_publisher:main',
'cot2planner_agent = ros2tak_tools.cot2planner_agent:main',
'ros2casevac_agent = ros2tak_tools.ros2casevac_agent:main',
'chat2ros_agent = ros2tak_tools.chat2ros_agent:main',
],
},
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,284 @@
import xml.etree.ElementTree as ET
import pytak
from datetime import datetime
import uuid
from typing import List, Tuple

def create_gps_COT(uuid: str, cot_type : str, latitude : str, longitude: str, altitude : str):
"""Create a CoT event based on the GPS data."""
root = ET.Element("event")
root.set("version", "2.0")
root.set("type", cot_type)
root.set("uid", uuid) # Use topic name as UID for identification
root.set("how", "m-g")
root.set("time", pytak.cot_time())
root.set("start", pytak.cot_time())
root.set("stale", pytak.cot_time(3600))

pt_attr = {
"lat": str(latitude),
"lon": str(longitude),
"hae": str(altitude),
"ce": "10",
"le": "10",
}

ET.SubElement(root, "point", attrib=pt_attr)

return ET.tostring(root, encoding="utf-8")

def create_casevac_COT(uuid, casualty_id, gps, zap_num, mechanism, injury, signs_symptoms, treatments, physician_name):
# Create root event element
event = ET.Element(
"event",
{
"version": "2.0",
"uid": casualty_id,
"type": "b-r-f-h-c",
"how": "h-g-i-g-o",
"time": pytak.cot_time(),
"start": pytak.cot_time(),
"stale": pytak.cot_time(3600),
},
)

# Create point element
point = ET.SubElement(
event,
"point",
{
"lat": f"{gps.latitude}",
"lon": f"{gps.longitude}",
"hae": "9999999.0",
"ce": "9999999.0",
"le": "9999999.0",
},
)

# Create detail element
detail = ET.SubElement(event, "detail")

# Add contact element
contact = ET.SubElement(detail, "contact", {"callsign": casualty_id})

# Add link element
link = ET.SubElement(
detail,
"link",
{
"type": "a-f-G-U-C-I",
"uid": "S-1-5-21-942292099-3747883346-3641641706-1000",
"parent_callsign": casualty_id,
"relation": "p-p",
"production_time": pytak.cot_time(),
},
)

# Add archive and status elements
ET.SubElement(detail, "archive")
ET.SubElement(detail, "status", {"readiness": "false"})
ET.SubElement(detail, "remarks")

# Create _medevac_ element with nested zMistsMap and zMist
medevac = ET.SubElement(
detail,
"_medevac_",
{
"title": casualty_id.upper(),
"casevac": "false",
"freq": "0.0",
"equipment_none": "true",
"security": "0",
"hlz_marking": "3",
"terrain_none": "true",
"obstacles": "None",
"medline_remarks": "",
"zone_prot_selection": "0",
},
)

zMistsMap = ET.SubElement(medevac, "zMistsMap")
zMist = ET.SubElement(
zMistsMap,
"zMist",
{
"z": zap_num,
"m": mechanism,
"i": injury,
"s": signs_symptoms,
"t": treatments,
"title": physician_name,
},
)

# Add _flow-tags_ element
flow_tags = ET.SubElement(
detail,
"_flow-tags_",
{
"TAK-Server-f6edbf55ccfa4af1b4cfa2d7f177ea67": f"chiron-tak_subscriber: {datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%SZ')}"
},
)

# Convert to a string
return ET.tostring(event, encoding="utf-8").decode("utf-8")

def create_chat_COT(uuid, callsign: str, message: str) -> str:

# Create root event element
event = ET.Element(
"event",
{
"version": "2.0",
"uid": f"GeoChat.{uuid}",
"type": "b-t-f",
"how": "h-g-i-g-o",
"time": pytak.cot_time(),
"start": pytak.cot_time(),
"stale": pytak.cot_time(3600),
}
)

# Create point element
point = ET.SubElement(
event,
"point",
{
"lat": "0.0",
"lon": "0.0",
"hae": "9999999.0",
"ce": "9999999.0",
"le": "9999999.0",
},
)

# Create detail element
detail = ET.SubElement(event, "detail")

# Create __chat element
chat = ET.SubElement(
detail,
"__chat",
{
"id": "All Chat Rooms",
"chatroom": "All Chat Rooms",
"senderCallsign": callsign,
"groupOwner": "false",
}
)

# Add chatgrp element
chatgrp = ET.SubElement(
chat,
"chatgrp",
{
"id": "All Chat Rooms",
"uid0": uuid,
"uid1": "All Chat Rooms",
},
)

# Add link element
link = ET.SubElement(
detail,
"link",
{
"uid": uuid,
"type": "a-f-G-U-C-I",
"relation": "p-p",
},
)

# Add remarks element
remarks = ET.SubElement(
detail,
"remarks",
{
"source": f"BAO.F.AIRLAB_CLI_MANAGER_{uuid}",
"sourceID": uuid,
"to": "All Chat Rooms",
"time": datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%S.%fZ"),
}
)
remarks.text = message

# Add _flow-tags_ element
flow_tags = ET.SubElement(
detail,
"_flow-tags_",
{
"TAK-Server-f6edbf55ccfa4af1b4cfa2d7f177ea67": datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%SZ"),
},
)

# Convert to a string
return ET.tostring(event, encoding="utf-8").decode("utf-8")

from datetime import datetime, timedelta

def create_polygon_COT(
uuid: str,
callsign: str,
gps_coordinates: List[Tuple[float, float]],
fill_color: int = 16777215,
) -> bytes:
"""
Creates a CoT message for a polygon using GPS coordinates.
Args:
gps_coordinates (list of tuples): List of (latitude, longitude) coordinates.
uuid (str): Unique identifier for the CoT event.
callsign (str): Callsign for the contact.
fill_color (int, optional): Fill color value. Default is 16777215.
fill_color = (Red (0-255) << 16) + (Green (0-255) << 8) + Blue (0-255)
Returns:
bytes: CoT message as a UTF-8 encoded XML byte string.
"""
if not gps_coordinates:
raise ValueError("GPS coordinates list cannot be empty.")

# Ensure the polygon is closed
if gps_coordinates[0] != gps_coordinates[-1]:
gps_coordinates.append(gps_coordinates[0])

# Create the root element
root = ET.Element("event", {
"version": "2.0",
"uid": uuid,
"type": "u-d-f",
"how": "h-e",
"time": pytak.cot_time(),
"start": pytak.cot_time(),
"stale": pytak.cot_time(3600),
})

# Add the point element
first_point = gps_coordinates[0]
ET.SubElement(root, "point", {
"lat": str(first_point[0]),
"lon": str(first_point[1]),
"hae": "9999999.0",
"ce": "9999999.0",
"le": "9999999.0",
})

# Add the detail element
detail = ET.SubElement(root, "detail")
ET.SubElement(detail, "contact", {"callsign": callsign})
ET.SubElement(detail, "strokeColor", {"value": "-1"})
ET.SubElement(detail, "fillColor", {"value": str(fill_color)})
ET.SubElement(detail, "remarks")
ET.SubElement(detail, "height", {"value": "0.00"})
ET.SubElement(detail, "height_unit", {"value": "4"})
ET.SubElement(detail, "archive")

# Add link elements for each GPS point
for lat, lon in gps_coordinates:
ET.SubElement(detail, "link", {"point": f"{lat},{lon}"})

ET.SubElement(detail, "archive")

# Generate the XML string
return ET.tostring(root, encoding="utf-8", xml_declaration=True)