Skip to content
Open
Show file tree
Hide file tree
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
13 changes: 13 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
version: 2
updates:
- package-ecosystem: github-actions
directory: /
schedule:
interval: weekly
open-pull-requests-limit: 5

- package-ecosystem: pip
directory: /
schedule:
interval: weekly
open-pull-requests-limit: 5
44 changes: 44 additions & 0 deletions .github/workflows/format.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
name: autofix.ci

on:
pull_request:
push:
branches:
- main
workflow_dispatch:

permissions:
contents: write
pull-requests: write

jobs:
ruff:
name: Ruff format
runs-on: ubuntu-22.04
steps:
- name: Check out repository
uses: actions/checkout@v4
with:
ref: ${{ github.head_ref || github.ref_name }}

- name: Set up Python 3.10
uses: actions/setup-python@v5
with:
python-version: '3.10'
cache: pip
cache-dependency-path: requirements-dev.txt

- name: Install formatter
run: python -m pip install -r requirements-dev.txt

- name: Format Python
if: github.event_name != 'push'
run: ./scripts/format

- name: Check Python formatting
if: github.event_name == 'push'
run: ./scripts/check-format

- name: Commit formatting fixes
if: github.event_name != 'push'
uses: autofix-ci/action@v1
20 changes: 20 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,23 @@ list_property:
- list_value2
empyt_list_property: []
```

## Formatting
This package uses Ruff for Python formatting and import ordering. The formatter is configured
for ROS 2's Python style preferences and Ubuntu 22.04 / Python 3.10 compatibility in
`pyproject.toml`.

Install the formatter:
```bash
python3 -m pip install -r requirements-dev.txt
```

Format the repository:
```bash
./scripts/format
```

Check formatting without modifying files:
```bash
./scripts/check-format
```
93 changes: 56 additions & 37 deletions launch/launch_all_vehicles.launch.py
Original file line number Diff line number Diff line change
@@ -1,120 +1,139 @@
# python imports
from pathlib import Path

import yaml

# ROS imports
from ament_index_python import get_package_share_directory
from launch import LaunchDescription, logging
from launch_ros.actions import Node

package_name = "mavinsight"
namespace = "viz"
LOGGER = logging.get_logger("vehicle_launch_logger")
initial_paths_overrides = ["chimera_d_4.yaml", "c2_c130_crash.yaml"]
from launch import LaunchDescription, logging

package_name = 'mavinsight'
namespace = 'viz'
LOGGER = logging.get_logger('vehicle_launch_logger')
initial_paths_overrides = ['chimera_d_4.yaml', 'c2_c130_crash.yaml']


def generate_launch_description():
ld = LaunchDescription()

share_dir = Path(get_package_share_directory(package_name))
shared_resources = share_dir / "package_resources"
shared_resources = share_dir / 'package_resources'

global_config = shared_resources / 'global_node_config.yaml'

# TODO change behavior for empty initial paths override
initial_paths = [(shared_resources) / p for p in initial_paths_overrides]

LOGGER.info(f"Initial paths: {[p.name for p in initial_paths]}")
LOGGER.info(f'Initial paths: {[p.name for p in initial_paths]}')

nodes = build_nodes(initial_paths, global_config)
for node in nodes:
ld.add_action(node)

return ld


def build_nodes(paths: list[Path], global_config: Path) -> list[Node]:
LOGGER.debug("Starting build")
LOGGER.debug('Starting build')
# initialize set of processed paths and output list
processed = set()
node_list = []

while paths:
# capture and error check next path
config_path = paths.pop()
LOGGER.info(f"Starting processing on {config_path.as_posix()}")
assert isinstance(config_path, Path), f"Unrecognized build_nodes input type."
if config_path.suffix != ".yaml":
LOGGER.error(f"Non-yaml config file detected: {config_path.as_posix()}. GraphMember configs must be yaml-encoded.\nSkipping...")
LOGGER.info(f'Starting processing on {config_path.as_posix()}')
assert isinstance(config_path, Path), f'Unrecognized build_nodes input type.'
if config_path.suffix != '.yaml':
LOGGER.error(
f'Non-yaml config file detected: {config_path.as_posix()}. GraphMember configs must be yaml-encoded.\nSkipping...'
)
continue
if config_path in processed:
LOGGER.error(f"Potential circular path detected in config files.\nConfig file: {config_path.as_posix()} is contained by a sub-member.\nSkipping...")
LOGGER.error(
f'Potential circular path detected in config files.\nConfig file: {config_path.as_posix()} is contained by a sub-member.\nSkipping...'
)
continue
LOGGER.debug(f"non-circular path")
LOGGER.debug(f'non-circular path')
# path is checkable, add to processed list
processed.add(config_path)

# resolve filename to absolute path in either sensor config or vehicle config
try:
abs_path = resolve_config_file(config_path)
except FileExistsError:
LOGGER.error(f"Duplicate filenames in Vehicle + Sensor dirs for file: {config_path.as_posix()}.\nSkipping...")
LOGGER.error(
f'Duplicate filenames in Vehicle + Sensor dirs for file: {config_path.as_posix()}.\nSkipping...'
)
continue
if abs_path is None:
LOGGER.error(f"Cannot find file: {config_path.as_posix()} in any MAVInsight config folder.\nSkipping...")
LOGGER.error(
f'Cannot find file: {config_path.as_posix()} in any MAVInsight config folder.\nSkipping...'
)
continue
LOGGER.debug(f"abs path acquired")
LOGGER.debug(f'abs path acquired')

# open file and confirm yaml encoding
with open(abs_path.as_posix(), "r", encoding="utf-8") as f:
with open(abs_path.as_posix(), 'r', encoding='utf-8') as f:
config = yaml.safe_load(f)
if type(config) is not dict:
LOGGER.error(f"Error parsing file: {abs_path.as_posix()} as yaml. GraphMember configs must be yaml-encoded.\nSkipping...")
LOGGER.error(
f'Error parsing file: {abs_path.as_posix()} as yaml. GraphMember configs must be yaml-encoded.\nSkipping...'
)
continue
LOGGER.debug(f"file opened successfully")
LOGGER.debug(f'file opened successfully')

# parse yaml down to the param layer (remove the layers of nesting above params)
while len(config.keys()) == 1:
config = config[next(iter(config))]
LOGGER.debug(f"Base yaml acquired")
LOGGER.debug(f'Base yaml acquired')

# select the correct executable for this config file
try:
ex = config['executable']
except KeyError as e:
LOGGER.error(f"Config file: {abs_path.as_posix()} contains no executable param.\nSkipping...")
LOGGER.error(
f'Config file: {abs_path.as_posix()} contains no executable param.\nSkipping...'
)
continue
LOGGER.debug(f"File type identified")
LOGGER.debug(f'File type identified')

# create Node action for launch description
node_list.append(Node(
package=package_name,
executable=ex,
name=abs_path.stem,
namespace=namespace,
parameters=[global_config.as_posix(), abs_path.as_posix()],
output="screen",
))
node_list.append(
Node(
package=package_name,
executable=ex,
name=abs_path.stem,
namespace=namespace,
parameters=[global_config.as_posix(), abs_path.as_posix()],
output='screen',
)
)

# add sub-members to list of nodes to be built
sensors = config.get('sensors', [])
if len(sensors) > 0:
LOGGER.info(f"Adding new sensor files: {sensors}")
for sens in config.get("sensors", []):
LOGGER.info(f'Adding new sensor files: {sensors}')
for sens in config.get('sensors', []):
paths.append(Path(sens))

vizs = config.get('viz', [])
if len(vizs) > 0:
LOGGER.info(f"Adding new visualization files: {vizs}")
for viz in config.get("viz", []):
LOGGER.info(f'Adding new visualization files: {vizs}')
for viz in config.get('viz', []):
paths.append(Path(viz))

return node_list


def resolve_config_file(path: Path) -> Path | None:
if path.is_absolute():
return path

package_configs = Path(get_package_share_directory(package_name)) / "package_resources"
package_configs = Path(get_package_share_directory(package_name)) / 'package_resources'
resolved_path = package_configs / path
if not resolved_path.is_file():
raise FileNotFoundError(f"Could not find configs for: {path} in mavinsight configs folder.")
raise FileNotFoundError(f'Could not find configs for: {path} in mavinsight configs folder.')
return resolved_path
Loading
Loading