diff --git a/README.md b/README.md index 91403e2..06fd2ac 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,7 @@ Snap for OAK-x cameras customized for Husarion robots | `husarion-depthai.start` | Start the `husarion-depthai.daemon` service | | `husarion-depthai.stop` | Stop the `husarion-depthai.daemon` service | | `husarion-depthai` | Start the application in the foreground (run in the current terminal). Remember to stop the daemon first | +| `husarion-depthai.image-view` | Preview the image from the camera | ## Setup FFMPEG diff --git a/demo/rviz.launch.py b/demo/rviz.launch.py index c1f202b..e00ea00 100644 --- a/demo/rviz.launch.py +++ b/demo/rviz.launch.py @@ -27,8 +27,8 @@ def launch_setup(context, *args, **kwargs): name="republish", arguments=[ "ffmpeg", - # "in/ffmpeg:=camera/color/image_raw/ffmpeg", - "in/ffmpeg:=abc/xyz/rgb/image_raw/ffmpeg", + "in/ffmpeg:=/image_raw/ffmpeg", + # "in/ffmpeg:=abc/xyz/rgb/image_raw/ffmpeg", "raw", "out:=camera/color/image_uncompressed", ], diff --git a/snap/hooks/configure b/snap/hooks/configure index b19934b..a9dc5dc 100755 --- a/snap/hooks/configure +++ b/snap/hooks/configure @@ -1,4 +1,4 @@ -#!/bin/sh -e +#!/bin/bash -e # The configure hook is called every time one the following actions happen: # - initial snap installation @@ -6,29 +6,54 @@ # - whenever the user runs snap set|unset to change a configuration option # Define a function to log and echo messages -log_and_echo() { - local message="$1" - # Log the message with logger - logger -t "${SNAP_NAME}" "configure hook: $message" - # Echo the message to standard error - echo >&2 "$message" -} - -log() { - local message="$1" - # Log the message with logger - logger -t "${SNAP_NAME}" "configure hook: $message" -} +source $SNAP/usr/bin/utils.sh -$SNAP/usr/bin/configure_hook_ros.sh +# # Define the top-level key and the list of valid keys +VALID_DRIVER_KEYS=( + "name" + "parent-frame" + "camera-model" + "cam-pos-x" + "cam-pos-y" + "cam-pos-z" + "cam-roll" + "cam-pitch" + "cam-yaw" + "params-file" + "ffmpeg-image-transport" +) -DEPTHAI_PARAMS_FILE="$(snapctl get driver.params-file)" +VALID_FFMPEG_IMAGE_TRANSPORT_KEYS=( + "encoding" + "preset" + "tune" +) -# Check if the file exists -if [ ! -f "$DEPTHAI_PARAMS_FILE" ]; then - log_and_echo "$DEPTHAI_PARAMS_FILE does not exist." - exit 1 -fi +# common + +validate_keys "driver" VALID_DRIVER_KEYS[@] +validate_keys "driver.ffmpeg-image-transport" VALID_FFMPEG_IMAGE_TRANSPORT_KEYS[@] + +# validate driver.name +validate_regex "driver.name" '^[a-z_-]{1,10}$' "Possible values are text with a maximum length of 10 characters, containing only lowercase letters (a-z), "-" or '_'." + +# validate driver.parent-frame +validate_regex "driver.parent-frame" '^[a-z_-]{1,40}$' "Possible values are text with a maximum length of 40 characters, containing only lowercase letters (a-z), "-" or '_'." + +# validate driver.camera-model +VALID_CAMERA_MODEL_OPTIONS=("OAK-D" "OAK-D-LITE") +validate_option "driver.camera-model" VALID_CAMERA_MODEL_OPTIONS[@] + +# validate driver.cam-x +validate_float "driver.cam-pos-x" +validate_float "driver.cam-pos-y" +validate_float "driver.cam-pos-z" +validate_float "driver.cam-roll" +validate_float "driver.cam-pitch" +validate_float "driver.cam-yaw" + +# validate driver.params-file +validate_path "driver.params-file" export LD_LIBRARY_PATH=$SNAP/usr/lib/$(uname -m)-linux-gnu/pulseaudio:$SNAP/usr/lib/$(uname -m)-linux-gnu/blas:$SNAP/usr/lib/$(uname -m)-linux-gnu/lapack:$LD_LIBRARY_PATH @@ -39,8 +64,8 @@ if [ -n "$FFMPEG_ENCODING" ]; then # Run ffmpeg -codecs and check if the codec is available if ! ffmpeg -encoders 2>/dev/null | awk '{print $2}' | grep -q "$FFMPEG_ENCODING"; then log_and_echo "Error: Codec $FFMPEG_ENCODING is not available:" - # log_and_echo "Available codecs:" - # ffmpeg -encoders 2>/dev/null | awk '/^ V/ {print $0}' + log_and_echo "Find available codecs here: $SNAP_COMMON/ffmpeg_codecs.txt" + ffmpeg -encoders 2>/dev/null | awk '/^ V/ {print $0}' > $SNAP_COMMON/ffmpeg_codecs.txt exit 1 fi @@ -60,6 +85,8 @@ else yq -i './**.ros__parameters.ffmpeg_image_transport = {}' $SNAP_COMMON/ffmpeg_params.yaml fi +$SNAP/usr/bin/configure_hook_ros.sh + # restart services with new ROS 2 config for service in daemon; do if snapctl services ${SNAP_NAME}.${service} | grep -qw active; then diff --git a/snap/hooks/connect-plug-ros-humble-ros-base b/snap/hooks/connect-plug-ros-humble-ros-base index fbc9bc6..39d5c18 100755 --- a/snap/hooks/connect-plug-ros-humble-ros-base +++ b/snap/hooks/connect-plug-ros-humble-ros-base @@ -1,8 +1,8 @@ -#!/bin/sh -e +#!/bin/bash -e # now we can start the service -# if snapctl services ${SNAP_NAME}.daemon | grep -q inactive; then -# snapctl start --enable ${SNAP_NAME}.daemon 2>&1 || true +# if snapctl services ${SNAP_NAME}.${SNAP_NAME} | grep -q inactive; then +# snapctl start --enable ${SNAP_NAME}.${SNAP_NAME} 2>&1 || true # fi logger -t ${SNAP_NAME} "Plug 'ros-humble-ros-base' connected" \ No newline at end of file diff --git a/snap/hooks/disconnect-plug-ros-humble-ros-base b/snap/hooks/disconnect-plug-ros-humble-ros-base index 35cc834..bc2c60d 100755 --- a/snap/hooks/disconnect-plug-ros-humble-ros-base +++ b/snap/hooks/disconnect-plug-ros-humble-ros-base @@ -1,4 +1,4 @@ -#!/bin/sh -e +#!/bin/bash -e logger -t ${SNAP_NAME} "Plug 'ros-humble-ros-base' disconnected" snapctl stop --disable ${SNAP_NAME}.daemon 2>&1 || true diff --git a/snap/hooks/install b/snap/hooks/install index 9967502..505bfb9 100755 --- a/snap/hooks/install +++ b/snap/hooks/install @@ -22,14 +22,13 @@ snapctl set driver.params-file=$SNAP_COMMON/depthai_params.yaml snapctl set driver.ffmpeg-image-transport.encoding=libx264 snapctl set driver.ffmpeg-image-transport.preset=ultrafast snapctl set driver.ffmpeg-image-transport.tune=zerolatency -snapctl set driver.namespace! if ! snapctl is-connected raw-usb; then log "Plug 'raw-usb' isn't connected, please run:" log "sudo snap connect ${SNAP_NAME}:raw-usb" fi -# copy joy params +# copy params cp -r $SNAP/usr/share/husarion-depthai/config/*.yaml ${SNAP_COMMON}/ # # # copy meshes to shared folder diff --git a/snap/local/launcher.sh b/snap/local/launcher.sh index 613b28d..9509c92 100755 --- a/snap/local/launcher.sh +++ b/snap/local/launcher.sh @@ -28,8 +28,6 @@ OPTIONS="\ cam-pitch \ cam-yaw \ params-file \ - namespace \ - device-namespace \ " LAUNCH_OPTIONS="" diff --git a/snap/local/ros_common/check_daemon_running.sh b/snap/local/local-ros/check_daemon_running.sh similarity index 53% rename from snap/local/ros_common/check_daemon_running.sh rename to snap/local/local-ros/check_daemon_running.sh index 3f7b8d4..cbe1590 100755 --- a/snap/local/ros_common/check_daemon_running.sh +++ b/snap/local/local-ros/check_daemon_running.sh @@ -1,13 +1,7 @@ -#!/usr/bin/bash +#!/bin/bash -e # Define a function to log and echo messages -log_and_echo() { - local message="$1" - # Log the message with logger - logger -t "${SNAP_NAME}" "${SNAP_NAME}.check_daemon_running.sh: $message" - # Echo the message to standard error - echo >&2 "$message" -} +source $SNAP/usr/bin/utils.sh if snapctl services ${SNAP_NAME}.daemon | grep -qw active; then log_and_echo "to run ${SNAP_NAME} snap interactively, you need to stop the daemon first, run:" diff --git a/snap/local/local-ros/configure_hook_ros.sh b/snap/local/local-ros/configure_hook_ros.sh new file mode 100755 index 0000000..7483d15 --- /dev/null +++ b/snap/local/local-ros/configure_hook_ros.sh @@ -0,0 +1,176 @@ +#!/bin/bash -e + +# The configure hook is called every time one the following actions happen: +# - initial snap installation +# - snap refresh +# - whenever the user runs snap set|unset to change a configuration option + +# Define a function to log and echo messages +source $SNAP/usr/bin/utils.sh + +# Function to check the type of the provided XML file +check_xml_profile_type() { + local xml_file="$1" + + if [[ ! -f "$xml_file" ]]; then + log_and_echo "File '$xml_file' does not exist." + return 1 + fi + + local root_element + local namespace + + # Extract the root element + root_element=$(yq '. | keys | .[1]' "$xml_file") + + # Extract the namespace based on the root element + if [[ "$root_element" == "CycloneDDS" ]]; then + namespace=$(yq .CycloneDDS."+@xmlns" "$xml_file") + elif [[ "$root_element" == "profiles" ]]; then + namespace=$(yq .profiles."+@xmlns" "$xml_file") + else + namespace="unknown" + fi + + # Remove quotes from the extracted values + root_element=${root_element//\"/} + namespace=${namespace//\"/} + + if [[ "$root_element" == "profiles" ]] && [[ "$namespace" == "http://www.eprosima.com/XMLSchemas/fastRTPS_Profiles" ]]; then + echo "rmw_fastrtps_cpp" + elif [[ "$root_element" == "CycloneDDS" ]] && [[ "$namespace" == "https://cdds.io/config" ]]; then + echo "rmw_cyclonedds_cpp" + else + exit 1 + fi +} + +VALID_ROS_KEYS=("localhost-only" "domain-id" "transport" "namespace") + +# Call the validation function +validate_keys "ros" VALID_ROS_KEYS[@] + +ROS_LOCALHOST_ONLY="$(snapctl get ros.localhost-only)" +ROS_DOMAIN_ID="$(snapctl get ros.domain-id)" + +# Make sure ROS_LOCALHOST_ONLY is valid +VALID_ROS_LOCALHOST_ONLY_OPTIONS=(1 0) +validate_option "ros.localhost-only" VALID_ROS_LOCALHOST_ONLY_OPTIONS[@] + +# Make sure ROS_DOMAIN_ID is valid +# Make sure WEBUI_PORT is valid +SUPPORTED_RANGE=(0 232) +# Validate a specific port, for example, webui.port +validate_number "ros.domain-id" SUPPORTED_RANGE[@] + +# Get the ros.transport setting using snapctl +OPT="ros.transport" +TRANSPORT_SETTING="$(snapctl get ${OPT})" + +# Check if TRANSPORT_SETTING is "builtin" +if [ "$TRANSPORT_SETTING" == "builtin" ]; then + # Change the value to "rmw_fastrtps_cpp" + TRANSPORT_SETTING="rmw_fastrtps_cpp" +fi + +# Only exit with status 1 if conditions are not met +if [ "$TRANSPORT_SETTING" != "rmw_fastrtps_cpp" ] && [ "$TRANSPORT_SETTING" != "rmw_cyclonedds_cpp" ] && [ ! -f "${SNAP_COMMON}/${TRANSPORT_SETTING}.xml" ]; then + log_and_echo "'${SNAP_COMMON}/${TRANSPORT_SETTING}.xml' does not exist." + exit 1 +fi + +if [ "$TRANSPORT_SETTING" = "rmw_fastrtps_cpp" ] || [ "$TRANSPORT_SETTING" = "shm" ]; then + if ! snapctl is-connected shm-plug; then + log_and_echo "to use 'rmw_fastrtps_cpp' and 'shm' tranport shm-plug need to be connected, please run:" + log_and_echo "sudo snap connect ${SNAP_NAME}:shm-plug ${SNAP_NAME}:shm-slot" + exit 1 + fi +fi + +# Make sure ros-humble-ros-base is connected +ROS_PLUG="ros-humble-ros-base" + +if ! snapctl is-connected ${ROS_PLUG}; then + log_and_echo "Plug '${ROS_PLUG}' isn't connected. Please run:" + log_and_echo "snap connect ${SNAP_NAME}:${ROS_PLUG} ${ROS_PLUG}:${ROS_PLUG}" + exit 1 +fi + +# Create the ${SNAP_COMMON}/ros.env file and export variables (for bash session running ROS2) +ROS_ENV_FILE="${SNAP_COMMON}/ros.env" + +# Create the ${SNAP_COMMON}/ros.env file and export variables (for bash session running ROS2) +ROS_SNAP_ARGS="${SNAP_COMMON}/ros_snap_args" + +echo "export ROS_DOMAIN_ID=${ROS_DOMAIN_ID}" > "${ROS_ENV_FILE}" +echo "export ROS_LOCALHOST_ONLY=${ROS_LOCALHOST_ONLY}" >> "${ROS_ENV_FILE}" + +NAMESPACE=$(snapctl get ros.namespace) + +# Check if the namespace is set and not empty +if [ -n "$NAMESPACE" ]; then + echo "ros.domain-id=${ROS_DOMAIN_ID} ros.localhost-only=${ROS_LOCALHOST_ONLY} ros.transport=${TRANSPORT_SETTING} ros.namespace=${NAMESPACE}" > "${ROS_SNAP_ARGS}" + echo "export ROS_NAMESPACE=${NAMESPACE}" >> "${ROS_ENV_FILE}" +else + echo "ros.domain-id=${ROS_DOMAIN_ID} ros.localhost-only=${ROS_LOCALHOST_ONLY} ros.transport=${TRANSPORT_SETTING} ros.namespace!" > "${ROS_SNAP_ARGS}" + echo "unset ROS_NAMESPACE" >> "${ROS_ENV_FILE}" +fi + +# Check the ros.transport setting and export the appropriate environment variable +if [ "$TRANSPORT_SETTING" != "rmw_fastrtps_cpp" ] && [ "$TRANSPORT_SETTING" != "rmw_cyclonedds_cpp" ]; then + profile_type=$(check_xml_profile_type "${SNAP_COMMON}/${TRANSPORT_SETTING}.xml") + if [[ "$profile_type" == "rmw_fastrtps_cpp" ]]; then + echo "unset CYCLONEDDS_URI" >> "${ROS_ENV_FILE}" + echo "export RMW_IMPLEMENTATION=${profile_type}" >> "${ROS_ENV_FILE}" + echo "export FASTRTPS_DEFAULT_PROFILES_FILE=${SNAP_COMMON}/${TRANSPORT_SETTING}.xml" >> "${ROS_ENV_FILE}" + elif [[ "$profile_type" == "rmw_cyclonedds_cpp" ]]; then + echo "unset FASTRTPS_DEFAULT_PROFILES_FILE" >> "${ROS_ENV_FILE}" + echo "export RMW_IMPLEMENTATION=${profile_type}" >> "${ROS_ENV_FILE}" + echo "export CYCLONEDDS_URI=file://${SNAP_COMMON}/${TRANSPORT_SETTING}.xml" >> "${ROS_ENV_FILE}" + else + log_and_echo "'${TRANSPORT_SETTING}' is not a supported value for '${OPT}'. Possible values are: rmw_fastrtps_cpp, rmw_cyclonedds_cpp, or a valid profile XML file." + exit 1 + fi +elif [ "$TRANSPORT_SETTING" == "rmw_fastrtps_cpp" ] || [ "$TRANSPORT_SETTING" == "rmw_cyclonedds_cpp" ]; then + echo "unset CYCLONEDDS_URI" >> "${ROS_ENV_FILE}" + echo "unset FASTRTPS_DEFAULT_PROFILES_FILE" >> "${ROS_ENV_FILE}" + echo "export RMW_IMPLEMENTATION=${TRANSPORT_SETTING}" >> "${ROS_ENV_FILE}" +fi + +# Define the path for the manage_ros_env.sh script +MANAGE_SCRIPT="${SNAP_COMMON}/manage_ros_env.sh" + +# Create the manage_ros_env.sh script in ${SNAP_COMMON} +cat << EOF > "${MANAGE_SCRIPT}" +#!/bin/bash + +ROS_ENV_FILE="${SNAP_COMMON}/ros.env" +SOURCE_LINE="source \${ROS_ENV_FILE}" + +add_source_to_bashrc() { + if ! grep -Fxq "\$SOURCE_LINE" ~/.bashrc; then + echo "\$SOURCE_LINE" >> ~/.bashrc + echo "Added '\$SOURCE_LINE' to ~/.bashrc" + else + echo "'\$SOURCE_LINE' is already in ~/.bashrc" + fi +} + +remove_source_from_bashrc() { + sed -i "\|\$SOURCE_LINE|d" ~/.bashrc + echo "Removed '\$SOURCE_LINE' from ~/.bashrc" +} + +case "\$1" in + remove) + remove_source_from_bashrc + ;; + add|*) + add_source_to_bashrc + ;; +esac +EOF + +# Make the manage_ros_env.sh script executable +chmod +x "${MANAGE_SCRIPT}" + diff --git a/snap/local/ros_common/install_hook_ros.sh b/snap/local/local-ros/install_hook_ros.sh similarity index 70% rename from snap/local/ros_common/install_hook_ros.sh rename to snap/local/local-ros/install_hook_ros.sh index 9d48a98..86848fb 100755 --- a/snap/local/ros_common/install_hook_ros.sh +++ b/snap/local/local-ros/install_hook_ros.sh @@ -1,16 +1,12 @@ -#!/bin/sh -e +#!/bin/bash -e # Define a function to log messages -log() { - local message="$1" - # Log the message with logger - logger -t "${SNAP_NAME}" "install hook: $message" -} +source $SNAP/usr/bin/utils.sh - -snapctl set transport="udp" -snapctl set ros-localhost-only=0 -snapctl set ros-domain-id=0 +snapctl set ros.transport="udp" +snapctl set ros.localhost-only=0 +snapctl set ros.domain-id=0 +snapctl set ros.namespace! # unset if ! snapctl is-connected ros-humble-ros-base; then log "Plug 'ros-humble-ros-base' isn't connected, please run:" diff --git a/snap/local/local-ros/ros_setup.sh b/snap/local/local-ros/ros_setup.sh new file mode 100755 index 0000000..2fffe43 --- /dev/null +++ b/snap/local/local-ros/ros_setup.sh @@ -0,0 +1,5 @@ +#!/bin/bash -e + +source $SNAP_COMMON/ros.env + +exec $@ \ No newline at end of file diff --git a/snap/local/ros_common/shm.xml b/snap/local/local-ros/shm.xml similarity index 98% rename from snap/local/ros_common/shm.xml rename to snap/local/local-ros/shm.xml index 1f9e585..4f2910f 100644 --- a/snap/local/ros_common/shm.xml +++ b/snap/local/local-ros/shm.xml @@ -15,4 +15,4 @@ false - \ No newline at end of file + diff --git a/snap/local/local-ros/udp-lo-cyclone.xml b/snap/local/local-ros/udp-lo-cyclone.xml new file mode 100644 index 0000000..6cdc91c --- /dev/null +++ b/snap/local/local-ros/udp-lo-cyclone.xml @@ -0,0 +1,16 @@ + + + + + lo + false + + + auto + 30 + + + + + + \ No newline at end of file diff --git a/snap/local/local-ros/udp-lo.xml b/snap/local/local-ros/udp-lo.xml new file mode 100644 index 0000000..fd0d0b2 --- /dev/null +++ b/snap/local/local-ros/udp-lo.xml @@ -0,0 +1,21 @@ + + + + + udp_transport_localhost + UDPv4 + +
127.0.0.1
+
+
+
+ + + + + udp_transport_localhost + + false + + +
diff --git a/snap/local/ros_common/udp.xml b/snap/local/local-ros/udp.xml similarity index 98% rename from snap/local/ros_common/udp.xml rename to snap/local/local-ros/udp.xml index 7ebb6f7..73e3e55 100644 --- a/snap/local/ros_common/udp.xml +++ b/snap/local/local-ros/udp.xml @@ -15,4 +15,4 @@ false - \ No newline at end of file + diff --git a/snap/local/local-ros/utils.sh b/snap/local/local-ros/utils.sh new file mode 100755 index 0000000..eafb4f4 --- /dev/null +++ b/snap/local/local-ros/utils.sh @@ -0,0 +1,237 @@ +#!/bin/bash -e + +# Define a function to log and echo messages +log_and_echo() { + local message="$1" + local script_name=$(basename "$0") + # Log the message with logger + logger -t "${SNAP_NAME}" "${script_name}: $message" + # Echo the message to standard error + echo -e >&2 "$message" +} + +log() { + local message="$1" + local script_name=$(basename "$0") + # Log the message with logger + logger -t "${SNAP_NAME}" "${script_name}: $message" +} + +is_integer() { + expr "$1" : '-\?[0-9][0-9]*$' >/dev/null 2>&1 +} + +# Function to validate the option values +validate_option() { + local OPT=$1 + local VALID_OPTIONS=("${!2}") + + VALUE="$(snapctl get ${OPT})" + + # Create an associative array to check valid options + declare -A valid_options_map + for option in "${VALID_OPTIONS[@]}"; do + valid_options_map["$option"]=1 + done + + # Join the valid options with newlines + JOINED_OPTIONS=$(printf "%s\n" "${VALID_OPTIONS[@]}") + + if [ -n "${VALUE}" ]; then + if [[ -z "${valid_options_map[$VALUE]}" ]]; then + log_and_echo "'${VALUE}' is not a supported value for '${OPT}' parameter. Possible values are:\n${JOINED_OPTIONS}" + exit 1 + fi + fi +} + +# Function to validate configuration keys +validate_keys() { + local top_level_key=$1 + local valid_keys=("${!2}") + + # Get the current configuration keys + local config_keys=$(snapctl get "$top_level_key" | yq '. | keys' | sed 's/- //g' | tr -d '"') + + # Create an associative array to check valid keys + declare -A valid_keys_map + for key in "${valid_keys[@]}"; do + valid_keys_map["$key"]=1 + done + + # Join the valid options with newlines + JOINED_OPTIONS=$(printf "%s\n" "${valid_keys[@]}") + + # Iterate over the keys in the configuration + for key in $config_keys; do + # Check if the key is in the list of valid keys + if [[ -z "${valid_keys_map[$key]}" ]]; then + log_and_echo "'${key}' is not a supported value for '${top_level_key}' key. Possible values are:\n${JOINED_OPTIONS}" + exit 1 + fi + done +} + +validate_number() { + local value_key=$1 + local range=("${!2}") + local excluded_values=("${!3:-}") + + # Get the value using snapctl + local value=$(snapctl get "$value_key") + + # Extract the min and max range values + local min_value=${range[0]} + local max_value=${range[1]} + + # Join the excluded values with newlines if they exist + local joined_excluded_values + local exclude_message + if [ -n "$excluded_values" ]; then + joined_excluded_values=$(printf "%s\n" "${excluded_values[@]}") + exclude_message=" excluding:\n${joined_excluded_values[*]}" + else + exclude_message="" + fi + + # Check if the value is an integer + if ! [[ "$value" =~ ^[0-9]+$ ]]; then + log_and_echo "'${value}' is not a supported value for '${value_key}'. Possible values are integers between ${min_value} and ${max_value}.${exclude_message}" + exit 1 + fi + + # Check if the value is in the valid range + if [ "$value" -lt "$min_value" ] || [ "$value" -gt "$max_value" ]; then + log_and_echo "'${value}' is not a supported value for '${value_key}'. Possible values are integers between ${min_value} and ${max_value}.${exclude_message}" + exit 1 + fi + + # Check if the value is in the excluded list + if [ -n "$excluded_values" ]; then + for excluded_value in "${excluded_values[@]}"; do + if [ "$value" -eq "$excluded_value" ]; then + log_and_echo "'${value}' is not a supported value for '${value_key}'. Possible values are integers between ${min_value} and ${max_value}.${exclude_message}" + exit 1 + fi + done + fi +} + +validate_float() { + local value_key=$1 + + # Get the value using snapctl + local value=$(snapctl get "$value_key") + + # Check if the value is a floating-point number + if ! [[ "$value" =~ ^-?[0-9]*\.?[0-9]+$ ]]; then + log_and_echo "'${value}' is not a supported value for '${value_key}'. Possible values are floating-point numbers." + exit 1 + fi +} + +validate_regex() { + local value_key=$1 + local regex=$2 + local error_message=$3 + + # Get the value using snapctl + local value=$(snapctl get "$value_key") + + # Check if the value matches the regex + if ! [[ "$value" =~ $regex ]]; then + log_and_echo "'${value}' is not a supported value for '${value_key}'. ${error_message}" + exit 1 + fi +} + +validate_path() { + local value_key=$1 + + # Get the value using snapctl + local config_path=$(snapctl get "$value_key") + + # Check if the path is a valid file + if [ ! -f "$config_path" ]; then + log_and_echo "The path specified in '$value_key' does not exist: '$config_path'." + exit 1 + fi +} + +validate_ipv4_addr() { + local value_key=$1 + + # Get the value using snapctl + local ip_address=$(snapctl get "$value_key") + local ip_address_regex='^(([0-9]{1,3}\.){3}[0-9]{1,3})$' + + if [[ "$ip_address" =~ $ip_address_regex ]]; then + # Split the IP address into its parts + IFS='.' read -r -a octets <<< "$ip_address" + + # Check each octet + for octet in "${octets[@]}"; do + if ((octet < 0 || octet > 255)); then + log_and_echo "Invalid format for '$value_key'. Each part of the IPv4 address must be between 0 and 255. Received: '$ip_address'." + exit 1 + fi + done + else + log_and_echo "Invalid format for '$value_key'. Expected format: a valid IPv4 address. Received: '$ip_address'." + exit 1 + fi +} + +# Universal function to validate serial ports +validate_serial_port() { + local port_key=$1 + + # Get the port value using snapctl + local port_value=$(snapctl get "$port_key") + + # Check if the value is "auto" or a valid serial port + if [ "$port_value" != "auto" ] && [ ! -e "$port_value" ]; then + log_and_echo "'${port_value}' is not a valid value for '${port_key}'. It must be 'auto' or a valid serial port in the /dev/ directory." + exit 1 + fi +} + +# Function to find the ttyUSB* device for the specified USB Vendor and Product ID +find_ttyUSB() { + # Extract port parameter, vendor, and product ID + PORT_PARAM="$1" + VENDOR_ID="$2" + PRODUCT_ID="$3" + + # Get the serial-port value using snapctl + SERIAL_PORT=$(snapctl get "$PORT_PARAM") + + if [ "$SERIAL_PORT" == "auto" ]; then + for device in /sys/bus/usb/devices/*; do + if [ -f "$device/idVendor" ]; then + current_vendor_id=$(cat "$device/idVendor") + if [ "$current_vendor_id" == "$VENDOR_ID" ]; then + if [ -z "$PRODUCT_ID" ] || ([ -f "$device/idProduct" ] && [ "$(cat "$device/idProduct")" == "$PRODUCT_ID" ]); then + # Look for ttyUSB device in the subdirectories + for subdir in "$device/"*; do + if [ -d "$subdir" ]; then + for tty in $(find "$subdir" -name "ttyUSB*" -print 2>/dev/null); do + if [ -e "$tty" ]; then + ttydev=$(basename "$tty") + echo "/dev/$ttydev" + return 0 + fi + done + fi + done + fi + fi + fi + done + echo "Error: Device with ID $VENDOR_ID:${PRODUCT_ID:-*} not found." + return 1 + else + echo "$SERIAL_PORT" + return 0 + fi +} \ No newline at end of file diff --git a/snap/local/ros_common/configure_hook_ros.sh b/snap/local/ros_common/configure_hook_ros.sh deleted file mode 100755 index 15b60ee..0000000 --- a/snap/local/ros_common/configure_hook_ros.sh +++ /dev/null @@ -1,76 +0,0 @@ -#!/bin/sh -e - -# The configure hook is called every time one the following actions happen: -# - initial snap installation -# - snap refresh -# - whenever the user runs snap set|unset to change a configuration option - -# Define a function to log and echo messages -log_and_echo() { - local message="$1" - # Log the message with logger - logger -t "${SNAP_NAME}" "configure hook: $message" - # Echo the message to standard error - echo >&2 "$message" -} - -log() { - local message="$1" - # Log the message with logger - logger -t "${SNAP_NAME}" "configure hook: $message" -} - -# Make sure ROS_LOCALHOST_ONLY is valid -OPT="ros-localhost-only" -ROS_LOCALHOST_ONLY="$(snapctl get ${OPT})" -if [ -n "${ROS_LOCALHOST_ONLY}" ]; then - case "${ROS_LOCALHOST_ONLY}" in - 1) ;; - 0) ;; - *) - log_and_echo "'${ROS_LOCALHOST_ONLY}' is not a supported value for '${OPT}'." \ - "Possible values are 0 or 1." - exit 1 - ;; - esac -fi - -# Make sure ROS_DOMAIN_ID is valid -OPT="ros-domain-id" -ROS_DOMAIN_ID="$(snapctl get ${OPT})" - -is_integer() { - expr "$1" : '-\?[0-9][0-9]*$' >/dev/null 2>&1 -} - -if ! is_integer "${ROS_DOMAIN_ID}" || [ "${ROS_DOMAIN_ID}" -lt 0 ] || [ "${ROS_DOMAIN_ID}" -gt 232 ]; then - log_and_echo "'${ROS_DOMAIN_ID}' is not a supported value for '${OPT}'. Possible values are integers between 0 and 232." - exit 1 -fi - -# Get the transport setting using snapctl -OPT="transport" -TRANSPORT_SETTING="$(snapctl get ${OPT})" - -# Only exit with status 1 if conditions are not met -if [ "$TRANSPORT_SETTING" != "builtin" ] && [ ! -f "${SNAP_COMMON}/${TRANSPORT_SETTING}.xml" ]; then - log_and_echo "'${SNAP_COMMON}/${TRANSPORT_SETTING}.xml' does not exist." - exit 1 -fi - -if [ "$TRANSPORT_SETTING" = "builtin" ] || [ "$TRANSPORT_SETTING" = "shm" ]; then - if ! snapctl is-connected shm-plug; then - log_and_echo "to use 'builtin' and 'shm' tranport shm-plug need to be connected, please run:" - log_and_echo "sudo snap connect ${SNAP_NAME}:shm-plug ${SNAP_NAME}:shm-slot" - exit 1 - fi -fi - -# # Make sure ros-humble-ros-base is connected -# ROS_PLUG="ros-humble-ros-base" - -# if ! snapctl is-connected ${ROS_PLUG}; then -# log_and_echo "Plug '${ROS_PLUG}' isn't connected. Please run:" -# log_and_echo "snap connect ${SNAP_NAME}:${ROS_PLUG} ${ROS_PLUG}:${ROS_PLUG}" -# exit 1 -# fi diff --git a/snap/local/ros_common/ros_setup.sh b/snap/local/ros_common/ros_setup.sh deleted file mode 100755 index ee6d3b5..0000000 --- a/snap/local/ros_common/ros_setup.sh +++ /dev/null @@ -1,28 +0,0 @@ -#!/usr/bin/bash -e - -log() { - local message="$1" - # Log the message with logger - logger -t "${SNAP_NAME}" "ros_setup: $message" -} - -TRANSPORT="$(snapctl get transport)" -# watch the log with: "journalctl -t ${SNAP_NAME}" -log "transport: ${TRANSPORT}" - -if [ "$TRANSPORT" = "builtin" ]; then - log "using builtin transport setting" -else - PROFILE_FILE="${SNAP_COMMON}/${TRANSPORT}.xml" - export FASTRTPS_DEFAULT_PROFILES_FILE=$PROFILE_FILE - log "using ${PROFILE_FILE} transport setting" - log "$(cat $FASTRTPS_DEFAULT_PROFILES_FILE)" -fi - -export ROS_LOCALHOST_ONLY="$(snapctl get ros-localhost-only)" -export ROS_DOMAIN_ID="$(snapctl get ros-domain-id)" - -log "ROS_LOCAHOST_ONLY=${ROS_LOCALHOST_ONLY}" -log "ROS_DOMAIN_ID=${ROS_DOMAIN_ID}" - -exec $@ \ No newline at end of file diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 32fb515..c5b6d92 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -5,14 +5,22 @@ summary: OAK-x cameras driver for Husarion robots icon: snap/gui/icon.png description: | The `husarion-depthai` snap contains all the necessary software to bring the OAK-x cameras up. - It offers the following parameters: + + **Parameters** - * driver: `{...}` - * ros-domain-id: `0` - * ros-localhost-only: `0` - * transport: `udp` - - The `driver` parameter is a dictionary that contains the following keys: + The snap provides the following configurable parameters (`param name`: `default value`): + + * `driver`: `{...}` + * `ros`: `{...}` + + The `ros` contains the following keys: + + * `ros.domain-id`: `0` - Sets the `ROS_DOMAIN_ID` environment variable for the ROS driver. + * `ros.localhost-only`: `0` - Sets the `ROS_LOCALHOST_ONLY` environment variable for the ROS driver. + * `ros.transport`: `udp` - Configures DDS transport. Options are `udp`, `shm`, `builtin` (or `rmw_fastrtps_cpp`), `rmw_cyclonedds_cpp`. Corresponding DDS XML files can be found in the `/var/snap/rosbot-xl/common` directory (custom FastDDS setups can also be created here). + * `ros.namespace`: `(unset)` - Namespace for all topics and transforms. + + The `driver` contains the following keys: * name: `oak` * parent-frame: `oak-d-base-frame` @@ -27,7 +35,6 @@ description: | * ffmpeg-image-transport.encoding: `libx264` * ffmpeg-image-transport.preset: `ultrafast` * ffmpeg-image-transport.tune: `zerolatency` - * namespace: `(unset)` To set the parameters, use the snap set command, e.g., @@ -139,70 +146,6 @@ parts: - libcanberra-gtk3-module - libglu1-mesa - # ffmpeg: - # plugin: colcon - # source: https://github.com/ros-misc-utilities/ffmpeg_image_transport.git - # # source-branch: v2.9.0-humble - # build-packages: - # - python3-catkin-pkg-modules - # - python3-vcstool - # stage-packages: - # - ros-humble-image-transport - # - ros-humble-image-transport-plugins - # - ffmpeg - # - ros-humble-cv-bridge - # # needed to run ffmpeg without errors: - # - libpulse-dev - # - libblas3 - # - libjpeg-turbo8-dev - # override-pull: | - # craftctl default - # cd $CRAFT_PART_SRC - # vcs import $CRAFT_PART_SRC < $CRAFT_PART_SRC/ffmpeg_image_transport.repos - - - # husarion-depthai: - # plugin: colcon - # source: https://github.com/luxonis/depthai-ros.git - # source-branch: v2.9.0-humble - # build-packages: - # - python3-catkin-pkg-modules - # - python3-vcstool - # override-pull: | - # craftctl default - - # cd $CRAFT_PART_SRC - - # git clone https://github.com/ros-misc-utilities/ffmpeg_image_transport.git - # vcs import $CRAFT_PART_SRC < $CRAFT_PART_SRC/ffmpeg_image_transport/ffmpeg_image_transport.repos - - # # Set the snap version from the git tag - # # The grade is set to 'stable' if the latest entry in the git history - # # is the tag itself, otherwise set to devel - # version="$(git describe --always --tags| sed -e 's/^v//;s/-/+git/;y/-/./')" - # [ -n "$(echo $version | grep "+git")" ] && grade=devel || grade=stable - # craftctl set version="$version" - # craftctl set grade="$grade" - - # build-packages: - # - python3-vcstool - # - python3-pip - # - python3-colcon-common-extensions - # - python3-rosdep - # - python3-catkin-pkg - - # ffmpeg: - # plugin: colcon - # source: https://github.com/ros-misc-utilities/ffmpeg_image_transport.git - # build-packages: - # - python3-vcstool - # - python3-pip - # - python3-colcon-common-extensions - # - python3-rosdep - # override-pull: | - # craftctl default - - # vcs import $CRAFT_PART_SRC < $CRAFT_PART_SRC/ffmpeg_image_transport.repos yq: plugin: nil override-build: | @@ -222,7 +165,7 @@ parts: # copy local scripts to the snap usr/bin local-files-ros: plugin: dump - source: snap/local/ros_common/ + source: snap/local/local-ros/ organize: '*.sh': usr/bin/ '*.xml': usr/share/husarion-depthai/config/