build: add directory-oriented make workflows
Host SITL smoke / Deterministic Gazebo command path (push) Has been cancelled
Host SITL smoke / Deterministic Gazebo command path (push) Has been cancelled
This commit is contained in:
1 parent
4bc98adbc6
commit
7dc3ccb619
40 files changed
+1367
-278
No files matched your search
+28
-28
@@ -1,33 +1,33 @@
|
||||
[workspace]
|
||||
members = [
|
||||
"boards/geprc-f722-v2",
|
||||
"boards/kakute-f7-mini-v3",
|
||||
"libs/kaptan-actuator",
|
||||
"libs/kaptan-control",
|
||||
"libs/kaptan-config",
|
||||
"libs/kaptan-config-derive",
|
||||
"libs/kaptan-file-mavlink",
|
||||
"libs/kaptan-data-channel",
|
||||
"libs/kaptan-dma",
|
||||
"libs/kaptan-drivers",
|
||||
"libs/kaptan-err-reportable",
|
||||
"libs/kaptan-err-reportable-macros",
|
||||
"libs/kaptan-flight-config",
|
||||
"libs/kaptan-flight-runtime",
|
||||
"libs/kaptan-i2c",
|
||||
"libs/kaptan-math",
|
||||
"libs/kaptan-mavlink",
|
||||
"libs/kaptan-log",
|
||||
"libs/kaptan-diagnostics-mavlink",
|
||||
"libs/kaptan-navigation",
|
||||
"libs/kaptan-safety",
|
||||
"libs/kaptan-sensor",
|
||||
"libs/kaptan-spi",
|
||||
"libs/kaptan-time",
|
||||
"libs/kaptan-tx",
|
||||
"platforms/stm32f7",
|
||||
"tools/sitl",
|
||||
"tools/configurator",
|
||||
"boards/geprc-f722-v2",
|
||||
"boards/kakute-f7-mini-v3",
|
||||
"libs/kaptan-actuator",
|
||||
"libs/kaptan-control",
|
||||
"libs/kaptan-config",
|
||||
"libs/kaptan-config-derive",
|
||||
"libs/kaptan-file-mavlink",
|
||||
"libs/kaptan-data-channel",
|
||||
"libs/kaptan-dma",
|
||||
"libs/kaptan-drivers",
|
||||
"libs/kaptan-err-reportable",
|
||||
"libs/kaptan-err-reportable-macros",
|
||||
"libs/kaptan-flight-config",
|
||||
"libs/kaptan-flight-runtime",
|
||||
"libs/kaptan-i2c",
|
||||
"libs/kaptan-math",
|
||||
"libs/kaptan-mavlink",
|
||||
"libs/kaptan-log",
|
||||
"libs/kaptan-diagnostics-mavlink",
|
||||
"libs/kaptan-navigation",
|
||||
"libs/kaptan-safety",
|
||||
"libs/kaptan-sensor",
|
||||
"libs/kaptan-spi",
|
||||
"libs/kaptan-time",
|
||||
"libs/kaptan-tx",
|
||||
"platforms/stm32f7",
|
||||
"tools/sitl",
|
||||
"tools/configurator",
|
||||
]
|
||||
resolver = "2"
|
||||
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
override SHELL := /bin/sh
|
||||
override MAKEFLAGS += --no-print-directory
|
||||
|
||||
.DEFAULT_GOAL := help
|
||||
.DELETE_ON_ERROR:
|
||||
|
||||
override CARGO := cargo
|
||||
override PYTHON := python3
|
||||
override CARGO_FLAGS := --locked --offline
|
||||
|
||||
override BOARDS := geprc-f722-v2 kakute-f7-mini-v3
|
||||
override BOARD_PACKAGES := $(addprefix kaptan-,$(BOARDS))
|
||||
override BOARD_PACKAGE_ARGS := $(addprefix -p ,$(BOARD_PACKAGES))
|
||||
override HOST_EXCLUDES := $(addprefix --exclude ,$(BOARD_PACKAGES))
|
||||
|
||||
override PRIMARY_GOAL := $(firstword $(MAKECMDGOALS))
|
||||
|
||||
run:
|
||||
@exec python3 ../scripts/make-argv.py exec run $(CARGO)
|
||||
|
||||
ifeq ($(PRIMARY_GOAL),run)
|
||||
|
||||
.PHONY: run
|
||||
|
||||
# Application arguments may also look like Make goals. The run recipe recovers
|
||||
# their exact boundaries from the original invocation.
|
||||
.DEFAULT:
|
||||
@:
|
||||
|
||||
else
|
||||
|
||||
.PHONY: help run fmt clippy test docs pre-commit check clean
|
||||
|
||||
help:
|
||||
@printf '%s\n' 'Usage: make firmware -- <cargo arguments...>' '' \
|
||||
'Example: make firmware -- test --workspace --locked --offline' '' \
|
||||
'Workspace targets:' \
|
||||
' fmt Check Rust formatting' \
|
||||
' clippy Run host and embedded Clippy checks' \
|
||||
' test Run Rust and firmware script tests' \
|
||||
' docs Build Rust API documentation with warnings denied' \
|
||||
' pre-commit Run fmt, clippy, test, and docs' \
|
||||
' check Alias for pre-commit' \
|
||||
' clean Remove Cargo build artifacts'
|
||||
|
||||
fmt:
|
||||
$(CARGO) fmt --all --check
|
||||
|
||||
clippy:
|
||||
$(CARGO) clippy --workspace --all-targets $(HOST_EXCLUDES) \
|
||||
$(CARGO_FLAGS) -- -D warnings
|
||||
$(CARGO) clippy -p platform-stm32f7 --all-targets \
|
||||
$(CARGO_FLAGS) -- -D warnings
|
||||
$(CARGO) clippy $(BOARD_PACKAGE_ARGS) --target thumbv7em-none-eabihf \
|
||||
$(CARGO_FLAGS) -- -D warnings
|
||||
|
||||
test:
|
||||
$(CARGO) test --workspace $(CARGO_FLAGS)
|
||||
$(PYTHON) -m unittest \
|
||||
scripts/test-analyze-fw.py scripts/test-summarize-test-coverage.py
|
||||
|
||||
docs:
|
||||
RUSTDOCFLAGS='-D warnings' $(CARGO) doc --workspace --no-deps \
|
||||
$(HOST_EXCLUDES) $(CARGO_FLAGS)
|
||||
|
||||
pre-commit: fmt clippy test docs
|
||||
|
||||
check: pre-commit
|
||||
|
||||
clean:
|
||||
$(CARGO) clean
|
||||
|
||||
endif
|
||||
+9
-3
@@ -14,9 +14,15 @@ firmware/
|
||||
└── tests/ firmware integration-test fixtures
|
||||
```
|
||||
|
||||
Run Cargo commands from this directory:
|
||||
This workspace owns its Makefile. From this directory, list its commands with:
|
||||
|
||||
```sh
|
||||
cd firmware
|
||||
cargo test --workspace --locked --offline
|
||||
make help
|
||||
```
|
||||
|
||||
From the repository root, the firmware path is a direct Cargo proxy. For
|
||||
example, `make firmware -- test --workspace --locked --offline` runs the host
|
||||
test suite. Build and package every supported board with
|
||||
`make firmware/boards`, or select one board by its directory path.
|
||||
Run `make firmware clippy` for Rust lints and `make firmware docs` for API
|
||||
documentation with warnings denied.
|
||||
@@ -0,0 +1,44 @@
|
||||
override SHELL := /bin/sh
|
||||
override MAKEFLAGS += --no-print-directory
|
||||
|
||||
.DEFAULT_GOAL := help
|
||||
.DELETE_ON_ERROR:
|
||||
|
||||
override CARGO := cargo
|
||||
override CARGO_FLAGS := --locked --offline
|
||||
|
||||
override EMBEDDED_TARGET := thumbv7em-none-eabihf
|
||||
override TARGET_DIR := ../target/$(EMBEDDED_TARGET)/release
|
||||
override SCRIPTS_DIR := ../scripts
|
||||
|
||||
override BOARDS := geprc-f722-v2 kakute-f7-mini-v3
|
||||
override BOARD_PACKAGES := $(addprefix kaptan-,$(BOARDS))
|
||||
override BOARD_PACKAGE_ARGS := $(addprefix -p ,$(BOARD_PACKAGES))
|
||||
|
||||
override PRIMARY_GOAL := $(firstword $(MAKECMDGOALS))
|
||||
|
||||
run:
|
||||
@exec python3 ../../scripts/make-argv.py exec run $(CARGO) build --release \
|
||||
$(CARGO_FLAGS) $(BOARD_PACKAGE_ARGS) --target $(EMBEDDED_TARGET)
|
||||
@set -e; for board in $(BOARD_PACKAGES); do \
|
||||
$(SCRIPTS_DIR)/create-dfu-image.sh $(TARGET_DIR)/$$board; \
|
||||
done
|
||||
|
||||
ifeq ($(PRIMARY_GOAL),run)
|
||||
|
||||
.PHONY: run
|
||||
|
||||
# Application arguments may also look like Make goals. The run recipe recovers
|
||||
# their exact boundaries from the original invocation.
|
||||
.DEFAULT:
|
||||
@:
|
||||
|
||||
else
|
||||
|
||||
.PHONY: help run
|
||||
|
||||
help:
|
||||
@printf '%s\n' 'Usage: make firmware/boards -- <cargo build arguments...>' '' \
|
||||
'With no arguments, every board is built and packaged.'
|
||||
|
||||
endif
|
||||
@@ -0,0 +1,69 @@
|
||||
override SHELL := /bin/sh
|
||||
override MAKEFLAGS += --no-print-directory
|
||||
|
||||
.DEFAULT_GOAL := help
|
||||
.DELETE_ON_ERROR:
|
||||
|
||||
override CARGO := cargo
|
||||
override CARGO_FLAGS := --locked --offline
|
||||
override DFU_UTIL := dfu-util
|
||||
|
||||
override EMBEDDED_TARGET := thumbv7em-none-eabihf
|
||||
override PACKAGE := kaptan-$(BOARD)
|
||||
override TARGET_DIR := ../../target/$(EMBEDDED_TARGET)/release
|
||||
override CREATE_IMAGE := ../../scripts/create-dfu-image.sh
|
||||
|
||||
override DFU_DEVICE := 0483:df11
|
||||
override DFU_ALT := 0
|
||||
override DFU_ADDRESS := 0x08000000:leave
|
||||
|
||||
override PRIMARY_GOAL := $(firstword $(MAKECMDGOALS))
|
||||
|
||||
run:
|
||||
@exec python3 ../../../scripts/make-argv.py exec run $(CARGO) build --release \
|
||||
$(CARGO_FLAGS) -p $(PACKAGE) --target $(EMBEDDED_TARGET)
|
||||
$(CREATE_IMAGE) $(TARGET_DIR)/$(PACKAGE)
|
||||
|
||||
_build:
|
||||
$(CARGO) build --release $(CARGO_FLAGS) -p $(PACKAGE) \
|
||||
--target $(EMBEDDED_TARGET)
|
||||
$(CREATE_IMAGE) $(TARGET_DIR)/$(PACKAGE)
|
||||
|
||||
flash: _build
|
||||
@printf '%s\n' 'Flashing $(TARGET_DIR)/$(PACKAGE).bin to $(DFU_DEVICE)'
|
||||
@exec python3 ../../../scripts/make-argv.py exec flash \
|
||||
$(DFU_UTIL) --device $(DFU_DEVICE) --alt $(DFU_ALT) \
|
||||
--dfuse-address $(DFU_ADDRESS) \
|
||||
--download $(TARGET_DIR)/$(PACKAGE).bin
|
||||
|
||||
ifeq ($(PRIMARY_GOAL),run)
|
||||
|
||||
.PHONY: run
|
||||
|
||||
# Application arguments may also look like Make goals. The run recipe recovers
|
||||
# their exact boundaries from the original invocation.
|
||||
.DEFAULT:
|
||||
@:
|
||||
|
||||
else
|
||||
|
||||
ifeq ($(PRIMARY_GOAL),flash)
|
||||
|
||||
.PHONY: flash _build
|
||||
|
||||
.DEFAULT:
|
||||
@:
|
||||
|
||||
else
|
||||
|
||||
.PHONY: help run flash _build
|
||||
|
||||
help:
|
||||
@printf '%s\n' 'Usage: make firmware/boards/$(BOARD) -- <cargo build arguments...>' '' \
|
||||
' make firmware/boards/$(BOARD) flash -- <dfu-util arguments...>' '' \
|
||||
'With no arguments, this board is built and packaged.' \
|
||||
'The flash target builds, packages, and writes this board over DFU.'
|
||||
|
||||
endif
|
||||
|
||||
endif
|
||||
@@ -14,7 +14,9 @@ kaptan-config = { path = "../../libs/kaptan-config" }
|
||||
platform-stm32f7 = { path = "../../platforms/stm32f7" }
|
||||
# RTIC's attribute expansion emits absolute `rtic` support paths in the final
|
||||
# board crate; the task graph and all RTIC source remain platform-owned.
|
||||
rtic = { version = "2.3", default-features = false, features = ["thumbv7-backend"] }
|
||||
rtic = { version = "2.3", default-features = false, features = [
|
||||
"thumbv7-backend",
|
||||
] }
|
||||
|
||||
[[bin]]
|
||||
name = "kaptan-geprc-f722-v2"
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
override BOARD := geprc-f722-v2
|
||||
|
||||
include ../board.mk
|
||||
@@ -13,7 +13,9 @@ kaptan-config = { path = "../../libs/kaptan-config" }
|
||||
platform-stm32f7 = { path = "../../platforms/stm32f7" }
|
||||
# RTIC's attribute expansion emits absolute `rtic` support paths in the final
|
||||
# board crate; the task graph and all RTIC source remain platform-owned.
|
||||
rtic = { version = "2.3", default-features = false, features = ["thumbv7-backend"] }
|
||||
rtic = { version = "2.3", default-features = false, features = [
|
||||
"thumbv7-backend",
|
||||
] }
|
||||
|
||||
[[bin]]
|
||||
name = "kaptan-kakute-f7-mini-v3"
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
override BOARD := kakute-f7-mini-v3
|
||||
|
||||
include ../board.mk
|
||||
@@ -11,7 +11,9 @@ workspace = true
|
||||
[dependencies]
|
||||
kaptan-err-reportable = { path = "../kaptan-err-reportable" }
|
||||
kaptan-dma = { path = "../kaptan-dma" }
|
||||
kaptan-mavlink = { path = "../kaptan-mavlink", default-features = false, features = ["sensor-telemetry"] }
|
||||
kaptan-mavlink = { path = "../kaptan-mavlink", default-features = false, features = [
|
||||
"sensor-telemetry",
|
||||
] }
|
||||
kaptan-sensor = { path = "../kaptan-sensor" }
|
||||
kaptan-time = { path = "../kaptan-time" }
|
||||
mavlink-core = { version = "0.18", default-features = false }
|
||||
@@ -17,7 +17,9 @@ kaptan-err-reportable = { path = "../kaptan-err-reportable" }
|
||||
kaptan-dma = { path = "../kaptan-dma" }
|
||||
kaptan-sensor = { path = "../kaptan-sensor", optional = true }
|
||||
kaptan-tx = { path = "../kaptan-tx" }
|
||||
mavlink-core = { version = "0.18", default-features = false, features = ["embedded"] }
|
||||
mavlink-core = { version = "0.18", default-features = false, features = [
|
||||
"embedded",
|
||||
] }
|
||||
bitflags = "2"
|
||||
num-derive = "0.4"
|
||||
num-traits = { version = "0.2", default-features = false }
|
||||
|
||||
@@ -30,6 +30,11 @@ kaptan-spi = { path = "../../libs/kaptan-spi" }
|
||||
kaptan-time = { path = "../../libs/kaptan-time" }
|
||||
cortex-m = "0.7"
|
||||
cortex-m-rt = "0.7"
|
||||
rtic = { version = "2.3", default-features = false, features = ["thumbv7-backend"] }
|
||||
stm32f7 = { version = "0.16", default-features = false, features = ["rt", "stm32f722"] }
|
||||
rtic = { version = "2.3", default-features = false, features = [
|
||||
"thumbv7-backend",
|
||||
] }
|
||||
stm32f7 = { version = "0.16", default-features = false, features = [
|
||||
"rt",
|
||||
"stm32f722",
|
||||
] }
|
||||
panic-halt = "1"
|
||||
@@ -1,8 +1,14 @@
|
||||
[toolchain]
|
||||
channel = "1.90.0"
|
||||
components = ["cargo", "clippy", "rustfmt", "rust-analyzer", "llvm-tools-preview"]
|
||||
components = [
|
||||
"cargo",
|
||||
"clippy",
|
||||
"rustfmt",
|
||||
"rust-analyzer",
|
||||
"llvm-tools-preview",
|
||||
]
|
||||
targets = [
|
||||
"thumbv7em-none-eabihf",
|
||||
"thumbv7em-none-eabi",
|
||||
"x86_64-unknown-linux-gnu",
|
||||
"thumbv7em-none-eabihf",
|
||||
"thumbv7em-none-eabi",
|
||||
"x86_64-unknown-linux-gnu",
|
||||
]
|
||||
@@ -166,18 +166,22 @@ def validation_errors(layout, section_list, load_segments):
|
||||
)
|
||||
|
||||
for index, segment in enumerate(load_segments):
|
||||
if segment.memory_size and region_for_span(
|
||||
segment.virtual_address, segment.memory_size, layout
|
||||
) is None:
|
||||
if (
|
||||
segment.memory_size
|
||||
and region_for_span(segment.virtual_address, segment.memory_size, layout)
|
||||
is None
|
||||
):
|
||||
errors.append(
|
||||
f"PT_LOAD[{index}] virtual span "
|
||||
f"0x{segment.virtual_address:08X}.."
|
||||
f"0x{segment.virtual_address + segment.memory_size:08X} "
|
||||
"is outside declared memory"
|
||||
)
|
||||
if segment.file_size and region_for_span(
|
||||
segment.physical_address, segment.file_size, layout
|
||||
) is None:
|
||||
if (
|
||||
segment.file_size
|
||||
and region_for_span(segment.physical_address, segment.file_size, layout)
|
||||
is None
|
||||
):
|
||||
errors.append(
|
||||
f"PT_LOAD[{index}] physical span "
|
||||
f"0x{segment.physical_address:08X}.."
|
||||
@@ -200,9 +204,7 @@ def flash_operation_section_errors(layout, section_list):
|
||||
return []
|
||||
|
||||
|
||||
INSTRUCTION = re.compile(
|
||||
r"^\s*([0-9A-Fa-f]+):\s+([A-Za-z][A-Za-z0-9.]*)\s*(.*?)\s*$"
|
||||
)
|
||||
INSTRUCTION = re.compile(r"^\s*([0-9A-Fa-f]+):\s+([A-Za-z][A-Za-z0-9.]*)\s*(.*?)\s*$")
|
||||
DIRECT_BRANCH = re.compile(
|
||||
r"^(?:b(?:eq|ne|cs|hs|cc|lo|mi|pl|vs|vc|hi|ls|ge|lt|gt|le)?"
|
||||
r"(?:\.[nw])?|blx?|bx|cbz|cbnz)$"
|
||||
@@ -321,7 +323,9 @@ def main():
|
||||
|
||||
errors = flash_operation_section_errors(layout, section_list)
|
||||
if errors:
|
||||
raise ValueError("Flash-operation placement validation failed:\n " + "\n ".join(errors))
|
||||
raise ValueError(
|
||||
"Flash-operation placement validation failed:\n " + "\n ".join(errors)
|
||||
)
|
||||
data = next(section for section in section_list if section.name == ".data")
|
||||
objdump = llvm_tool("llvm-objdump")
|
||||
errors = sram_control_flow_errors(
|
||||
@@ -337,7 +341,9 @@ def main():
|
||||
),
|
||||
)
|
||||
if errors:
|
||||
raise ValueError("Flash-operation control-flow validation failed:\n " + "\n ".join(errors))
|
||||
raise ValueError(
|
||||
"Flash-operation control-flow validation failed:\n " + "\n ".join(errors)
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
set -euo pipefail
|
||||
|
||||
if [[ $# -lt 1 || $# -gt 2 ]]; then
|
||||
printf 'Usage: %s <firmware.elf> [memory.x]\n' "$0" >&2
|
||||
exit 2
|
||||
printf 'Usage: %s <firmware.elf> [memory.x]\n' "$0" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
readonly input_elf="$1"
|
||||
@@ -15,18 +15,18 @@ readonly host="$(rustc -vV | sed -n 's/^host: //p')"
|
||||
readonly llvm_objcopy="$(rustc --print sysroot)/lib/rustlib/$host/bin/llvm-objcopy"
|
||||
|
||||
if [[ ! -f "$input_elf" ]]; then
|
||||
printf 'firmware ELF does not exist: %s\n' "$input_elf" >&2
|
||||
exit 1
|
||||
printf 'firmware ELF does not exist: %s\n' "$input_elf" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ ! -f "$memory_x" ]]; then
|
||||
printf 'cannot find linker layout: %s\n' "$memory_x" >&2
|
||||
exit 1
|
||||
printf 'cannot find linker layout: %s\n' "$memory_x" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ ! -x "$llvm_objcopy" ]]; then
|
||||
printf 'llvm-objcopy is unavailable; install it with: rustup component add llvm-tools-preview\n' >&2
|
||||
exit 1
|
||||
printf 'llvm-objcopy is unavailable; install it with: rustup component add llvm-tools-preview\n' >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
"$script_directory/analyze-fw.py" "$input_elf" "$memory_x"
|
||||
|
||||
@@ -14,18 +14,18 @@
|
||||
set -eu
|
||||
|
||||
if ! cargo llvm-cov --version >/dev/null 2>&1; then
|
||||
echo "cargo-llvm-cov is required; install it with: cargo install cargo-llvm-cov --locked" >&2
|
||||
exit 127
|
||||
echo "cargo-llvm-cov is required; install it with: cargo install cargo-llvm-cov --locked" >&2
|
||||
exit 127
|
||||
fi
|
||||
|
||||
if ! rustup toolchain list | grep -q '^nightly'; then
|
||||
echo "nightly with llvm-tools-preview is required for branch coverage" >&2
|
||||
exit 127
|
||||
echo "nightly with llvm-tools-preview is required for branch coverage" >&2
|
||||
exit 127
|
||||
fi
|
||||
|
||||
if ! python3 --version >/dev/null 2>&1; then
|
||||
echo "python3 is required for strict source-level coverage accounting" >&2
|
||||
exit 127
|
||||
echo "python3 is required for strict source-level coverage accounting" >&2
|
||||
exit 127
|
||||
fi
|
||||
|
||||
python3 scripts/test-summarize-test-coverage.py
|
||||
@@ -46,50 +46,50 @@ coverage_root=$CARGO_TARGET_DIR
|
||||
core_incomplete=0
|
||||
|
||||
report_package() {
|
||||
package=$1
|
||||
source_path=$2
|
||||
shift 2
|
||||
case "$package" in
|
||||
kaptan-math|kaptan-navigation|kaptan-control|kaptan-safety)
|
||||
package_target_dir="$coverage_root/$package"
|
||||
lcov_report="$coverage_root/$package.lcov"
|
||||
json_report="$coverage_root/$package.json"
|
||||
report_format=strict-core
|
||||
;;
|
||||
*)
|
||||
package_target_dir=$coverage_root
|
||||
lcov_report="$coverage_root/$package.lcov"
|
||||
report_format=lcov
|
||||
;;
|
||||
esac
|
||||
package=$1
|
||||
source_path=$2
|
||||
shift 2
|
||||
case "$package" in
|
||||
kaptan-math | kaptan-navigation | kaptan-control | kaptan-safety)
|
||||
package_target_dir="$coverage_root/$package"
|
||||
lcov_report="$coverage_root/$package.lcov"
|
||||
json_report="$coverage_root/$package.json"
|
||||
report_format=strict-core
|
||||
;;
|
||||
*)
|
||||
package_target_dir=$coverage_root
|
||||
lcov_report="$coverage_root/$package.lcov"
|
||||
report_format=lcov
|
||||
;;
|
||||
esac
|
||||
|
||||
case "$report_format" in
|
||||
strict-core)
|
||||
# Keep the strict core isolated, then export both representations
|
||||
# from the same test run. LCOV `DA` defines source lines; JSON
|
||||
# coordinates define branch identities across generic instances.
|
||||
RUSTC_BOOTSTRAP=1 CARGO_TARGET_DIR="$package_target_dir" cargo llvm-cov \
|
||||
--branch \
|
||||
--no-report \
|
||||
-p "$package" \
|
||||
"$@"
|
||||
RUSTC_BOOTSTRAP=1 CARGO_TARGET_DIR="$package_target_dir" cargo llvm-cov report \
|
||||
--lcov \
|
||||
--output-path "$lcov_report"
|
||||
RUSTC_BOOTSTRAP=1 CARGO_TARGET_DIR="$package_target_dir" cargo llvm-cov report \
|
||||
--json \
|
||||
--output-path "$json_report"
|
||||
totals=$(python3 scripts/summarize-test-coverage.py \
|
||||
"$lcov_report" "$json_report" "$source_path")
|
||||
;;
|
||||
lcov)
|
||||
RUSTC_BOOTSTRAP=1 CARGO_TARGET_DIR="$package_target_dir" cargo llvm-cov \
|
||||
--branch \
|
||||
--lcov \
|
||||
--output-path "$lcov_report" \
|
||||
-p "$package" \
|
||||
"$@"
|
||||
totals=$(awk -v source_path="$source_path" '
|
||||
case "$report_format" in
|
||||
strict-core)
|
||||
# Keep the strict core isolated, then export both representations
|
||||
# from the same test run. LCOV `DA` defines source lines; JSON
|
||||
# coordinates define branch identities across generic instances.
|
||||
RUSTC_BOOTSTRAP=1 CARGO_TARGET_DIR="$package_target_dir" cargo llvm-cov \
|
||||
--branch \
|
||||
--no-report \
|
||||
-p "$package" \
|
||||
"$@"
|
||||
RUSTC_BOOTSTRAP=1 CARGO_TARGET_DIR="$package_target_dir" cargo llvm-cov report \
|
||||
--lcov \
|
||||
--output-path "$lcov_report"
|
||||
RUSTC_BOOTSTRAP=1 CARGO_TARGET_DIR="$package_target_dir" cargo llvm-cov report \
|
||||
--json \
|
||||
--output-path "$json_report"
|
||||
totals=$(python3 scripts/summarize-test-coverage.py \
|
||||
"$lcov_report" "$json_report" "$source_path")
|
||||
;;
|
||||
lcov)
|
||||
RUSTC_BOOTSTRAP=1 CARGO_TARGET_DIR="$package_target_dir" cargo llvm-cov \
|
||||
--branch \
|
||||
--lcov \
|
||||
--output-path "$lcov_report" \
|
||||
-p "$package" \
|
||||
"$@"
|
||||
totals=$(awk -v source_path="$source_path" '
|
||||
/^SF:/ { selected = index($0, source_path) != 0 }
|
||||
selected && /^LF:/ { lines_found += substr($0, 4) }
|
||||
selected && /^LH:/ { lines_hit += substr($0, 4) }
|
||||
@@ -99,18 +99,18 @@ report_package() {
|
||||
printf "%d %d %d %d", lines_hit, lines_found, branches_hit, branches_found
|
||||
}
|
||||
' "$lcov_report")
|
||||
;;
|
||||
esac
|
||||
set -- $totals
|
||||
printf '%-26s lines %s/%s, branches %s/%s\n' "$package" "$1" "$2" "$3" "$4"
|
||||
;;
|
||||
esac
|
||||
set -- $totals
|
||||
printf '%-26s lines %s/%s, branches %s/%s\n' "$package" "$1" "$2" "$3" "$4"
|
||||
|
||||
case "$package" in
|
||||
kaptan-math|kaptan-navigation|kaptan-control|kaptan-safety)
|
||||
if [ "$1" -ne "$2" ] || [ "$3" -ne "$4" ]; then
|
||||
core_incomplete=1
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
case "$package" in
|
||||
kaptan-math | kaptan-navigation | kaptan-control | kaptan-safety)
|
||||
if [ "$1" -ne "$2" ] || [ "$3" -ne "$4" ]; then
|
||||
core_incomplete=1
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
echo "Host test coverage (reports: $CARGO_TARGET_DIR; strict core unions LCOV DA lines and JSON branches)"
|
||||
@@ -135,6 +135,6 @@ echo "kaptan-geprc-f722-v2 target-only/N/A (run its thumbv7em-none-eabihf c
|
||||
echo "kaptan-kakute-f7-mini-v3 target-only/N/A (run its thumbv7em-none-eabihf check)"
|
||||
|
||||
if [ "$core_incomplete" -ne 0 ]; then
|
||||
echo "Strict core coverage is incomplete; reports above identify each crate." >&2
|
||||
exit 1
|
||||
echo "Strict core coverage is incomplete; reports above identify each crate." >&2
|
||||
exit 1
|
||||
fi
|
||||
@@ -34,7 +34,9 @@ class CoverageFormatError(ValueError):
|
||||
"""Coverage records cannot be safely summarized by source location."""
|
||||
|
||||
|
||||
def selected_files(report: dict[object, object], source_path: str) -> Iterable[dict[object, object]]:
|
||||
def selected_files(
|
||||
report: dict[object, object], source_path: str
|
||||
) -> Iterable[dict[object, object]]:
|
||||
"""Yield selected file records after rejecting ambiguous macro expansion."""
|
||||
exports = report.get("data")
|
||||
if not isinstance(exports, list):
|
||||
@@ -78,7 +80,11 @@ def lcov_line_totals(report: str, source_path: str) -> tuple[int, int, set[str]]
|
||||
if record == "end_of_record":
|
||||
filename = None
|
||||
continue
|
||||
if filename is None or source_path not in filename or not record.startswith("DA:"):
|
||||
if (
|
||||
filename is None
|
||||
or source_path not in filename
|
||||
or not record.startswith("DA:")
|
||||
):
|
||||
continue
|
||||
fields = record[3:].split(",", 2)
|
||||
if len(fields) < 2:
|
||||
@@ -87,7 +93,9 @@ def lcov_line_totals(report: str, source_path: str) -> tuple[int, int, set[str]]
|
||||
line = int(fields[0])
|
||||
count = int(fields[1])
|
||||
except ValueError as error:
|
||||
raise CoverageFormatError(f"{filename}: non-integer LCOV DA record") from error
|
||||
raise CoverageFormatError(
|
||||
f"{filename}: non-integer LCOV DA record"
|
||||
) from error
|
||||
if line <= 0 or count < 0:
|
||||
raise CoverageFormatError(f"{filename}: invalid LCOV DA location or count")
|
||||
key = (filename, line)
|
||||
@@ -100,7 +108,9 @@ def lcov_line_totals(report: str, source_path: str) -> tuple[int, int, set[str]]
|
||||
return sum(lines.values()), len(lines), files
|
||||
|
||||
|
||||
def branch_totals(report: dict[object, object], source_path: str) -> tuple[int, int, set[str]]:
|
||||
def branch_totals(
|
||||
report: dict[object, object], source_path: str
|
||||
) -> tuple[int, int, set[str]]:
|
||||
"""Return the union of selected JSON branch outcomes and source files."""
|
||||
branches: dict[tuple[str, int, int, int, int], list[bool]] = {}
|
||||
files: set[str] = set()
|
||||
@@ -142,7 +152,9 @@ def branch_totals(report: dict[object, object], source_path: str) -> tuple[int,
|
||||
or file_id < 0
|
||||
or expanded_file_id < 0
|
||||
):
|
||||
raise CoverageFormatError(f"{filename}: invalid branch location or count")
|
||||
raise CoverageFormatError(
|
||||
f"{filename}: invalid branch location or count"
|
||||
)
|
||||
if file_id != expanded_file_id:
|
||||
raise CoverageFormatError(
|
||||
f"{filename}:{start_line}:{start_column}: macro-expanded branch is present; "
|
||||
@@ -175,9 +187,15 @@ def coverage_totals(
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("lcov_report", type=Path, help="LCOV report from cargo llvm-cov")
|
||||
parser.add_argument("json_report", type=Path, help="LLVM JSON report from cargo llvm-cov")
|
||||
parser.add_argument("source_path", help="workspace-relative path fragment to include")
|
||||
parser.add_argument(
|
||||
"lcov_report", type=Path, help="LCOV report from cargo llvm-cov"
|
||||
)
|
||||
parser.add_argument(
|
||||
"json_report", type=Path, help="LLVM JSON report from cargo llvm-cov"
|
||||
)
|
||||
parser.add_argument(
|
||||
"source_path", help="workspace-relative path fragment to include"
|
||||
)
|
||||
arguments = parser.parse_args()
|
||||
|
||||
try:
|
||||
|
||||
@@ -122,9 +122,7 @@ class FirmwareMemoryValidationTests(unittest.TestCase):
|
||||
self.assertEqual(
|
||||
ANALYZER.region_usage("FLASH", LAYOUT, sections, segments), 0x50
|
||||
)
|
||||
self.assertEqual(
|
||||
ANALYZER.region_usage("RAM", LAYOUT, sections, segments), 0x10
|
||||
)
|
||||
self.assertEqual(ANALYZER.region_usage("RAM", LAYOUT, sections, segments), 0x10)
|
||||
|
||||
def test_rejects_loadable_virtual_and_physical_spans_outside_memory(self):
|
||||
segments = [
|
||||
@@ -138,9 +136,7 @@ class FirmwareMemoryValidationTests(unittest.TestCase):
|
||||
|
||||
def test_requires_one_executable_data_section_in_ram(self):
|
||||
valid = [ANALYZER.Section(".data", 0x20, 0x20000000, True, True)]
|
||||
non_executable = [
|
||||
ANALYZER.Section(".data", 0x20, 0x20000000, True, False)
|
||||
]
|
||||
non_executable = [ANALYZER.Section(".data", 0x20, 0x20000000, True, False)]
|
||||
in_flash = [ANALYZER.Section(".data", 0x20, 0x08000000, True, True)]
|
||||
|
||||
self.assertEqual(ANALYZER.flash_operation_section_errors(LAYOUT, valid), [])
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
override SHELL := /bin/sh
|
||||
override MAKEFLAGS += --no-print-directory
|
||||
|
||||
.DEFAULT_GOAL := help
|
||||
.DELETE_ON_ERROR:
|
||||
|
||||
override CARGO := cargo
|
||||
override CARGO_FLAGS := --locked --offline
|
||||
|
||||
override PRIMARY_GOAL := $(firstword $(MAKECMDGOALS))
|
||||
|
||||
ifeq ($(PRIMARY_GOAL),run)
|
||||
|
||||
.PHONY: run
|
||||
|
||||
.DEFAULT:
|
||||
@:
|
||||
|
||||
else
|
||||
|
||||
.PHONY: help run
|
||||
|
||||
help: ## Show configurator commands
|
||||
@printf '%s\n' 'Configurator' '' \
|
||||
'The root Makefile passes every argument after -- to the application:' \
|
||||
' make firmware/tools/configurator -- --source-system 255 --serial /dev/ttyUSB0 --baud 115200'
|
||||
|
||||
endif
|
||||
|
||||
run: ## Run the configurator with the remaining command-line arguments
|
||||
@exec python3 ../../../scripts/make-argv.py exec run \
|
||||
$(CARGO) run $(CARGO_FLAGS) -p kaptan-configurator --
|
||||
@@ -16,7 +16,9 @@ kaptan-flight-config = { path = "../../libs/kaptan-flight-config" }
|
||||
kaptan-flight-runtime = { path = "../../libs/kaptan-flight-runtime" }
|
||||
kaptan-log = { path = "../../libs/kaptan-log" }
|
||||
kaptan-math = { path = "../../libs/kaptan-math" }
|
||||
kaptan-navigation = { path = "../../libs/kaptan-navigation", features = ["sitl"] }
|
||||
kaptan-navigation = { path = "../../libs/kaptan-navigation", features = [
|
||||
"sitl",
|
||||
] }
|
||||
kaptan-safety = { path = "../../libs/kaptan-safety" }
|
||||
kaptan-time = { path = "../../libs/kaptan-time" }
|
||||
minicbor = "0.25"
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
override SHELL := /bin/sh
|
||||
override MAKEFLAGS += --no-print-directory
|
||||
|
||||
.DEFAULT_GOAL := help
|
||||
.DELETE_ON_ERROR:
|
||||
|
||||
override PYTHON := python3
|
||||
|
||||
override PRIMARY_GOAL := $(firstword $(MAKECMDGOALS))
|
||||
|
||||
run: ## Run kaptan-sim with the remaining command-line arguments
|
||||
@exec python3 ../../../scripts/make-argv.py exec run ./kaptan-sim
|
||||
|
||||
ifeq ($(PRIMARY_GOAL),run)
|
||||
|
||||
.PHONY: run
|
||||
|
||||
.DEFAULT:
|
||||
@:
|
||||
|
||||
else
|
||||
|
||||
.PHONY: help run _check
|
||||
|
||||
help: ## Show simulation commands
|
||||
@printf '%s\n' 'Simulation' '' \
|
||||
'Application arguments are passed through the root Makefile:' \
|
||||
' make firmware/tools/sitl -- move 5 3 1 --wait'
|
||||
|
||||
_check:
|
||||
$(PYTHON) world-structure-test.py
|
||||
$(PYTHON) -m unittest test_kaptan_sim.py
|
||||
|
||||
endif
|
||||
@@ -12,18 +12,18 @@ compose_file="$project_root/tools/sitl/compose.yaml"
|
||||
python3 "$project_root/tools/sitl/world-structure-test.py"
|
||||
|
||||
cleanup() {
|
||||
status=$?
|
||||
trap - EXIT
|
||||
# Preserve the first failing command's result, but retain enough evidence
|
||||
# to distinguish a client/lease failure from a Gazebo or container start
|
||||
# failure in CI. `down` otherwise removes the only useful diagnostics.
|
||||
if [ "$status" -ne 0 ]; then
|
||||
echo "SITL smoke failed (exit $status); compose state and recent logs follow:" >&2
|
||||
docker compose -f "$compose_file" ps >&2 || true
|
||||
docker compose -f "$compose_file" logs --no-color --tail 250 >&2 || true
|
||||
fi
|
||||
docker compose -f "$compose_file" down --remove-orphans || true
|
||||
exit "$status"
|
||||
status=$?
|
||||
trap - EXIT
|
||||
# Preserve the first failing command's result, but retain enough evidence
|
||||
# to distinguish a client/lease failure from a Gazebo or container start
|
||||
# failure in CI. `down` otherwise removes the only useful diagnostics.
|
||||
if [ "$status" -ne 0 ]; then
|
||||
echo "SITL smoke failed (exit $status); compose state and recent logs follow:" >&2
|
||||
docker compose -f "$compose_file" ps >&2 || true
|
||||
docker compose -f "$compose_file" logs --no-color --tail 250 >&2 || true
|
||||
fi
|
||||
docker compose -f "$compose_file" down --remove-orphans || true
|
||||
exit "$status"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
trap 'exit 130' INT
|
||||
|
||||
@@ -50,9 +50,13 @@ GROUND_PLANE_TOLERANCE_METRES = 0.10
|
||||
def arguments() -> argparse.Namespace:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
subcommands = parser.add_subparsers(dest="command", required=True)
|
||||
ready = subcommands.add_parser("ready", help="wait for estimator and simulated sensors")
|
||||
ready = subcommands.add_parser(
|
||||
"ready", help="wait for estimator and simulated sensors"
|
||||
)
|
||||
ready.add_argument("--timeout", type=float, default=20.0)
|
||||
move = subcommands.add_parser("move", help="command absolute local north/east/up metres")
|
||||
move = subcommands.add_parser(
|
||||
"move", help="command absolute local north/east/up metres"
|
||||
)
|
||||
move.add_argument("north", type=float)
|
||||
move.add_argument("east", type=float)
|
||||
move.add_argument("up", type=float)
|
||||
@@ -78,9 +82,15 @@ def arguments() -> argparse.Namespace:
|
||||
"land",
|
||||
help="descend to an explicit local landing point and revoke virtual motor authority",
|
||||
)
|
||||
land.add_argument("north", type=float, help="absolute local north landing coordinate in metres")
|
||||
land.add_argument("east", type=float, help="absolute local east landing coordinate in metres")
|
||||
land.add_argument("--wait", action="store_true", help="wait for the terminal landed result")
|
||||
land.add_argument(
|
||||
"north", type=float, help="absolute local north landing coordinate in metres"
|
||||
)
|
||||
land.add_argument(
|
||||
"east", type=float, help="absolute local east landing coordinate in metres"
|
||||
)
|
||||
land.add_argument(
|
||||
"--wait", action="store_true", help="wait for the terminal landed result"
|
||||
)
|
||||
land.add_argument(
|
||||
"--verify-truth",
|
||||
action="store_true",
|
||||
@@ -122,7 +132,9 @@ def latest_simulation_time(
|
||||
if magic != b"KSC1" or version != VERSION:
|
||||
continue
|
||||
if latest is not None and simulation_time < latest:
|
||||
raise RuntimeError("Gazebo simulation clock reset; restart the command in the new epoch")
|
||||
raise RuntimeError(
|
||||
"Gazebo simulation clock reset; restart the command in the new epoch"
|
||||
)
|
||||
latest = simulation_time
|
||||
|
||||
|
||||
@@ -145,7 +157,10 @@ def wait_ready(socket_: socket.socket, timeout: float) -> None:
|
||||
if latest is not None and latest & READY == READY:
|
||||
return
|
||||
if latest is None:
|
||||
raise RuntimeError("host SITL is not running; run docker compose -f tools/sitl/compose.yaml up -d --build")
|
||||
raise RuntimeError(
|
||||
"host SITL is not running; run docker compose -f "
|
||||
"firmware/tools/sitl/compose.yaml up -d --build from the repository root"
|
||||
)
|
||||
names = (
|
||||
(ESTIMATOR_READY, "estimator initialization"),
|
||||
(IMU_FRESH, "fresh simulator IMU"),
|
||||
@@ -174,10 +189,7 @@ def truth_sample_within_tolerance(
|
||||
or velocity > velocity_tolerance
|
||||
or attitude_error > attitude_tolerance
|
||||
or (require_stopped_motors and not motors_stopped)
|
||||
or (
|
||||
require_ground_plane
|
||||
and ground_plane_error > GROUND_PLANE_TOLERANCE_METRES
|
||||
)
|
||||
or (require_ground_plane and ground_plane_error > GROUND_PLANE_TOLERANCE_METRES)
|
||||
)
|
||||
|
||||
|
||||
@@ -217,11 +229,16 @@ def verify_truth(
|
||||
keepalive is not None
|
||||
and control_socket is not None
|
||||
and simulation_time is not None
|
||||
and (next_keepalive_time is None or simulation_time >= next_keepalive_time)
|
||||
and (
|
||||
next_keepalive_time is None
|
||||
or simulation_time >= next_keepalive_time
|
||||
)
|
||||
):
|
||||
control_socket.sendto(keepalive, CONTROL)
|
||||
next_keepalive_time = simulation_time + GOAL_KEEPALIVE_MICROSECONDS
|
||||
payload = recv(truth_socket, min(acquisition_deadline, time.monotonic() + 0.01))
|
||||
payload = recv(
|
||||
truth_socket, min(acquisition_deadline, time.monotonic() + 0.01)
|
||||
)
|
||||
if payload is None or len(payload) != TRUTH_PACKET.size:
|
||||
continue
|
||||
(
|
||||
@@ -261,8 +278,12 @@ def verify_truth(
|
||||
attitude_w**2 + attitude_x**2 + attitude_y**2 + attitude_z**2
|
||||
)
|
||||
if not math.isfinite(quaternion_norm) or abs(quaternion_norm - 1.0) > 1e-3:
|
||||
raise RuntimeError("Gazebo truth contained a non-unit attitude quaternion")
|
||||
attitude_error = 2.0 * math.acos(min(1.0, abs(attitude_w) / quaternion_norm))
|
||||
raise RuntimeError(
|
||||
"Gazebo truth contained a non-unit attitude quaternion"
|
||||
)
|
||||
attitude_error = 2.0 * math.acos(
|
||||
min(1.0, abs(attitude_w) / quaternion_norm)
|
||||
)
|
||||
motors = (motor_1, motor_2, motor_3, motor_4)
|
||||
maximum_motor_fraction = max(abs(motor) for motor in motors)
|
||||
motors_stopped = all(abs(motor) <= 0.01 for motor in motors)
|
||||
@@ -449,7 +470,9 @@ def command_goal(
|
||||
if magic != b"KSL1" or version != VERSION or received_id != goal_id:
|
||||
continue
|
||||
if lifecycle == GOAL_REJECTED:
|
||||
raise RuntimeError(f"{action} was rejected because SITL was not estimator-ready")
|
||||
raise RuntimeError(
|
||||
f"{action} was rejected because SITL was not estimator-ready"
|
||||
)
|
||||
if lifecycle == GOAL_ACTIVE:
|
||||
active = True
|
||||
print(f"{action} active: north={north:g} m, east={east:g} m, up={up:g} m")
|
||||
@@ -512,7 +535,9 @@ def main() -> int:
|
||||
or not math.isfinite(values.truth_hold_seconds)
|
||||
)
|
||||
if tolerances:
|
||||
raise ValueError("truth tolerances and hold duration must be finite and positive")
|
||||
raise ValueError(
|
||||
"truth tolerances and hold duration must be finite and positive"
|
||||
)
|
||||
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as client:
|
||||
client.bind(USER_CONTROL)
|
||||
if values.command == "ready":
|
||||
|
||||
@@ -37,7 +37,9 @@ class TruthAcceptanceTests(unittest.TestCase):
|
||||
)
|
||||
|
||||
def test_move_truth_contract_allows_an_armed_hover(self):
|
||||
self.assertTrue(self.acceptable(motors_stopped=False, ground_plane_error=1.0, landing=False))
|
||||
self.assertTrue(
|
||||
self.acceptable(motors_stopped=False, ground_plane_error=1.0, landing=False)
|
||||
)
|
||||
|
||||
def test_landing_truth_contract_requires_zero_motors_and_ground_plane(self):
|
||||
self.assertFalse(self.acceptable(motors_stopped=False))
|
||||
@@ -77,23 +79,28 @@ class TruthAcceptanceTests(unittest.TestCase):
|
||||
arguments = command_goal.call_args.args
|
||||
self.assertEqual(arguments[0:2], (control_socket, clock_socket))
|
||||
magic, version, goal_id, north, east, up = self.client.GOAL.unpack(arguments[2])
|
||||
self.assertEqual((magic, version, north, east, up), (b"KSD1", 1, 1.0, -2.0, 0.0))
|
||||
self.assertEqual(
|
||||
(magic, version, north, east, up), (b"KSD1", 1, 1.0, -2.0, 0.0)
|
||||
)
|
||||
self.assertEqual(arguments[3], goal_id)
|
||||
self.assertEqual(arguments[4:], (
|
||||
1.0,
|
||||
-2.0,
|
||||
0.0,
|
||||
60.0,
|
||||
True,
|
||||
True,
|
||||
0.75,
|
||||
0.25,
|
||||
0.35,
|
||||
0.75,
|
||||
self.client.GOAL_LANDED,
|
||||
True,
|
||||
"land",
|
||||
))
|
||||
self.assertEqual(
|
||||
arguments[4:],
|
||||
(
|
||||
1.0,
|
||||
-2.0,
|
||||
0.0,
|
||||
60.0,
|
||||
True,
|
||||
True,
|
||||
0.75,
|
||||
0.25,
|
||||
0.35,
|
||||
0.75,
|
||||
self.client.GOAL_LANDED,
|
||||
True,
|
||||
"land",
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -67,7 +67,9 @@ def check_world() -> None:
|
||||
world = root.find("./world")
|
||||
if world is None:
|
||||
fail("missing world")
|
||||
model_names = {model_element.get("name") for model_element in world.findall("model")}
|
||||
model_names = {
|
||||
model_element.get("name") for model_element in world.findall("model")
|
||||
}
|
||||
if model_names != {"ground", "sitl_reference_map", "kaptan_sitl_quad"}:
|
||||
fail(
|
||||
"the declared flat-world model may contain only ground, the visual reference map, "
|
||||
@@ -76,7 +78,9 @@ def check_world() -> None:
|
||||
|
||||
ground = model(root, "ground")
|
||||
ground_collisions = ground.findall(".//collision")
|
||||
if [collision.get("name") for collision in ground_collisions] != ["ground_collision"]:
|
||||
if [collision.get("name") for collision in ground_collisions] != [
|
||||
"ground_collision"
|
||||
]:
|
||||
fail("ground must retain exactly one flat collision plane")
|
||||
if ground_collisions[0].find("geometry/plane") is None:
|
||||
fail("ground collision must remain a flat plane")
|
||||
@@ -89,11 +93,15 @@ def check_world() -> None:
|
||||
if reference_map.findall(".//collision"):
|
||||
fail("reference map must remain visual-only and cannot add obstacle physics")
|
||||
if reference_map.findall(".//transparency"):
|
||||
fail("reference map must not use unsupported SDF transparency material children")
|
||||
fail(
|
||||
"reference map must not use unsupported SDF transparency material children"
|
||||
)
|
||||
reference_link = named(reference_map, "link", "reference_link")
|
||||
actual_marks = {visual.get("name") for visual in reference_link.findall("visual")}
|
||||
if actual_marks != {"landing_pad"}:
|
||||
fail(f"reference map may contain only the landing pad; found {sorted(actual_marks)!r}")
|
||||
fail(
|
||||
f"reference map may contain only the landing pad; found {sorted(actual_marks)!r}"
|
||||
)
|
||||
|
||||
quad = model(root, "kaptan_sitl_quad")
|
||||
if pose(quad)[:3] != (0.0, 0.0, 0.14):
|
||||
@@ -122,7 +130,9 @@ def check_world() -> None:
|
||||
"body_collision",
|
||||
*(f"landing_foot_{foot}_collision" for foot in foot_positions),
|
||||
}
|
||||
actual_quad_collisions = {collision.get("name") for collision in base_link.findall("collision")}
|
||||
actual_quad_collisions = {
|
||||
collision.get("name") for collision in base_link.findall("collision")
|
||||
}
|
||||
if actual_quad_collisions != expected_quad_collisions:
|
||||
fail(
|
||||
"quad collision geometry must remain its body and four declared landing feet; "
|
||||
@@ -130,8 +140,12 @@ def check_world() -> None:
|
||||
)
|
||||
|
||||
sensors = quad.findall(".//sensor")
|
||||
if [(sensor.get("name"), sensor.get("type")) for sensor in sensors] != [("imu", "imu")]:
|
||||
fail("this local-state SITL world must not imply unmodeled visual/range/flow sensors")
|
||||
if [(sensor.get("name"), sensor.get("type")) for sensor in sensors] != [
|
||||
("imu", "imu")
|
||||
]:
|
||||
fail(
|
||||
"this local-state SITL world must not imply unmodeled visual/range/flow sensors"
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
Reference in new issue
Block a user