AZ-657 (frame_ingest): RTSP session lifecycle FSM with bounded
exponential backoff (1 s → 30 s cap), AI-lock plumb through
watch::Sender that stamps every emitted Frame, and SPS/PPS
hard-fail via OpenError::UnsupportedProfile. The actual RTSP wire
client is abstracted behind an RtspTransport trait so AZ-658 can
pin retina/FFmpeg alongside the decoder; the lifecycle FSM itself
is production code today. tokio::select! around every transport
call so a hung open/read cannot wedge graceful shutdown. 10 unit +
5 integration tests cover happy path, bounded reconnect, stream-
drop reopen, hard-fail no-retry, and AI-lock toggle.
AZ-682 (scan_controller): typed ScanState (ZoomedOut / ZoomedIn /
TargetFollow) with a complete pure transition catalogue, every
(state, trigger) → next_state from description.md §1/§4/§5 covered;
spec-disallowed combos return TransitionOutcome.accepted = false
with RejectReason::UnsupportedTransition (loud, not silent). Frame-
rate floor monitor with hysteresis suppresses ZoomedOut → ZoomedIn
while sustained FPS < 10 fps per description.md §5/§6. Rolling
100-sample tick-latency window surfaces p99; health goes yellow
above the 10 ms budget. 18 unit + 5 integration tests cover the
catalogue, fps-floor activate/clear, and tick-latency budget.
Cumulative review (batches 10-12): all OPEN findings carried
forward without regressions. See
_docs/03_implementation/batch_12_cycle1_report.md §6.
Notes: pre-existing dead-code error in autopilot::Runtime::
vlm_provider_name (origin batch 4) blocks workspace -D warnings
clippy. Recorded in _docs/_process_leftovers/ — not in batch 12
scope.
Co-authored-by: Cursor <cursoragent@cursor.com>
AZ-654 SweepEngine: pendulum default, Raster/LawnMower variants
reserved and explicitly NotImplemented (no silent fallback per AC-3).
Time injected via next_step(now) for deterministic dwell tests.
AZ-655 PlanExecutor: linear yaw/pitch interpolation between PanGoals
with self-throttle (default 50 ms); stats expose
commands_emitted/dropped_to_throttle counters. PanGoal/PanPlan added
to shared::models::gimbal (spec drift: data_model.md §PanPlan flagged
for next doc sync).
AZ-656 CentreOnTarget: zoom-aware proportional control loop (correction
~ 1/zoom); target_lost debounced — fires once per loss streak, resets
on bbox return. Also fixes the misleadingly-named monotonic_ns() helper
introduced by AZ-653 that used SystemTime::now(): GimbalController now
owns a shared::clock::MonoClock and stamps GimbalState::ts_monotonic_ns
via clock.elapsed_ns(). AZ-656 AC-2 forced the correction; integration
test verifies the fix end-to-end.
58/58 gimbal_controller tests green (47 unit + 7 AZ-653 integration +
4 new batch_11 integration). Workspace test suite green this run.
Co-authored-by: Cursor <cursoragent@cursor.com>
Implements the vendor wire protocol for the A40 gimbal (XOR-8 checksum,
not CRC16 — task spec corrected against ArduPilot AP_Mount_Viewpro.h):
frame encode/decode, typed FrameId/CameraCommand/ImageSensor, A1 angles,
C1 camera, C2 set-zoom command builders, and a tokio UdpSocket transport
with bounded retry, per-command deadline, and atomic vendor-fault
counters surfaced via faults()/health(). GimbalControllerHandle::set_pose
and zoom now ride the transport when wired; remain disabled when no
transport is bound. 32/32 gimbal_controller tests green; workspace test
suite green except for a pre-existing flake in
mission_executor::state_machine::ac3_bounded_retry_then_success that
reproduces only under parallel workspace test load (passes 5/5 in
isolation; flagged in batch 8 report, unrelated to this batch).
Co-authored-by: Cursor <cursoragent@cursor.com>
Geofence (INCLUSION+EXCLUSION, ≤500 ms detect→RTL), battery
thresholds (RTL@25%/land@15% + signed override), middle-waypoint
re-upload (CLEAR_ALL→upload→SET_CURRENT(0)), and post-flight
mapobjects push trigger. Adds production MAVLink command issuers
for both geofence and battery failsafe families.
Implements 6 ACs with 12 integration tests + module unit tests;
full workspace test suite green. See batch_09_cycle1_report.md
for AC coverage and known limitations.
Co-authored-by: Cursor <cursoragent@cursor.com>
AZ-650 (mission_executor pre-flight Built-In Test):
- BitEvaluator trait + BitItemStatus { Pass, Degraded, Fail, Skipped }
+ BitReport + BitOverall fusion. Pluggable per-item evaluators so
the composition root decides which dependencies are wired today.
- BitController owns evaluator list + mpsc ack channel + sticky-pass
+ ack deadline. Publishes bit_ok via tokio watch — composition root
pipes it into the telemetry projection where the existing FSM
bit_ok guard already consumes it (no FSM changes needed).
- BitState { Idle, Pass, AwaitingAck { report_id }, Failed { reason } }
with broadcast::Sender<BitEvent> for operator-side observability.
Sticky-pass semantics: once Pass is reached (directly or via signed
ack on a Degraded report), the controller stops re-evaluating —
BIT is a one-shot pre-flight gate, not a continuous monitor.
- BitDegradedAck arrives pre-validated by operator_bridge; the
controller only matches report_id and applies the operator id to
the audit log.
- Concrete evaluators landed today (3 of 12 spec items, the rest
depend on components still in todo/):
- StateDirFreeSpaceEvaluator (dir creatable/readable; statvfs is
documented follow-up).
- WallClockBoundEvaluator (chrono::Utc::now vs configurable bound).
- MissionLoadedEvaluator (waypoint count via Arc<Mutex<usize>>).
- MapObjectsSyncedEvaluator (maps SyncState -> BIT status per Q9).
Tests:
- ac1_all_pass_proceeds, ac2_fail_blocks_transition,
ac3_degraded_requires_signed_ack (+ mismatched_ack supplement),
ac4_degraded_ack_timeout_fails_the_bit — all 4 ACs green.
- Pure next_state table covered by lib unit tests.
- Per-evaluator unit tests for Pass/Fail/Degraded branches.
Quality gates:
- cargo fmt: clean.
- cargo clippy -p mission_executor --tests -- -D warnings: 0 warns.
- cargo test --workspace: all green.
- Pre-existing flake in state_machine::ac3_bounded_retry_then_success
(batch 7 report) remains pre-existing — passes on rerun.
Co-authored-by: Cursor <cursoragent@cursor.com>
Verdict: PASS_WITH_WARNINGS. Six findings, all Medium or Low:
F1 (Medium) telemetry adapter gap UavTelemetry -> Telemetry,
F2-F5 doc drift queued for Step 13 (module-layout, architecture
section 5.6, mapobjects_store description, data_model),
F6 pre-existing dead-code on autopilot::runtime::vlm_provider_name.
No new Architecture findings; layer + Public API discipline holds.
Co-authored-by: Cursor <cursoragent@cursor.com>
Lands the second batch under epic AZ-626's implementation plan.
mavlink_layer (AZ-641 + AZ-642):
- Hand-rolled MAVLink v2 codec covering the §7.7 surface: HEARTBEAT,
SYS_STATUS, SET_MODE, ATTITUDE, GLOBAL_POSITION_INT, MISSION_* (7),
COMMAND_LONG, COMMAND_ACK, EXTENDED_SYS_STATE, STATUSTEXT (17 total).
- Streaming decoder demuxes arbitrary-sized byte arrivals, drops malformed
frames with typed parse-error counters (crc/truncated/unknown_id/seq_gap),
and surfaces sequence gaps without hard-failing the link.
- Encoder tracks the per-link tx_seq counter and applies the MAVLink v2
trailing-zero payload truncation rule.
- UDP and POSIX-serial transports behind a single async Transport trait;
the run loop owns transport open with bounded exponential backoff
(2 s serial / 5 s UDP cap) and a tokio::select! per-link read+write
loop.
- 1 Hz outbound HEARTBEAT scheduler + inbound-heartbeat watchdog that
fires LinkUp / LinkLost on a broadcast channel and feeds health detail
(connected, last_heartbeat_age_ms, signing_enabled, parse_errors).
mission_client (AZ-644):
- HTTPS GET /missions/{id} over rustls (no OpenSSL on the airframe).
- Bundled JSON Schema (crates/shared/contracts/mission-schema.json,
draft-07, additionalProperties:false) validates every response;
schema-invalid bodies surface as FetchError::SchemaInvalid with a
1 KiB sample of the raw body for offline analysis.
- Transient failures (timeout, 5xx, 429) retry with bounded exponential
backoff up to MissionClientOptions.max_attempts (default 5); permanent
failures (4xx, malformed URL) abort immediately.
- Health surface mirrors AC-1's contract: last_fetch_ts,
fetch_errors_total, schema_version, connection_state.
Caught and fixed before commit (NOT a code-review finding — caught by
the unit test that hand-computed CRC("123456789")): the hand-rolled
X.25 CRC accumulator was operating in u16 throughout. The MAVLink C
reference declares `tmp` as uint8_t, which silently truncates the
shifted-in bits. Round-trip tests passed (encoder and decoder shared
the bug); a real MAVLink peer would have rejected every frame. Fixed
by mirroring the C reference: `let mut tmp: u8 = …; tmp ^= tmp.wrapping_shl(4);`.
Added a regression test asserting CRC("123456789") == 0x6F91 against
pymavlink's reference value (NOT the textbook 0x29B1 — MAVLink uses a
byte-wise variant, not the bit-reflected CCITT).
AC verification (full detail in
_docs/03_implementation/batch_02_cycle1_report.md):
AZ-641: AC-1 + AC-3 + AC-4 verified via UDP loopback integration tests;
AC-2 (serial) requires a socat pty pair and runs in the SITL/CI
tier (test exists as #[ignore]-marked stub).
AZ-642: AC-1 + AC-2 + AC-3 verified via exhaustive codec round-trip and
decoder negative-path tests; AC-4 (SITL round-trip) requires
ArduPilot SITL — the CRC fix above means the codec is now
wire-correct, ready for the sitl-conformance Woodpecker stage.
AZ-644: all four ACs verified via wiremock-driven integration tests.
Workspace gates green:
- cargo check --workspace clean
- cargo check --workspace --no-default-features clean
- cargo fmt --all -- --check clean
- cargo clippy --workspace --all-targets -- -D warnings clean
- cargo test --workspace pass (1 expected ignore)
Layering invariants from module-layout.md hold: mavlink_layer and
mission_client are Layer 2 actors importing only `shared`; no sibling
Layer-2 imports; MavlinkHandle implements shared::contracts::MavlinkSink.
Jira: AZ-641, AZ-642, AZ-644 transitioned To Do → In Progress at batch
start; the matching In Testing transitions follow this commit.
Co-authored-by: Cursor <cursoragent@cursor.com>
Lands the first task of the implementation epic AZ-626: a cargo workspace
with 14 crates (shared + autopilot binary + 12 component crates), a
multi-stage Dockerfile + dev/test compose stacks, a Woodpecker CI pipeline,
the on-airframe systemd unit with flight-gate wiring, three environment
TOML configs, and the canonical entity catalogue from data_model.md as
`shared::models`.
Per-AC verification (full detail in
_docs/03_implementation/batch_01_cycle1_report.md):
- AC-1 cargo check --workspace clean
- AC-2 cargo test --workspace passes; per-crate it_compiles() <0.01 s
- AC-6 cargo build/test --no-default-features clean; VlmClient default
impl returns VlmAssessment::disabled()
- AC-9 tracing-subscriber emits JSON logs with ts/level/target/fields
- AC-10 runtime::ensure_state_directories creates mapobjects/, audit/,
pending_pushes/ under storage.state_dir
Deferred to external infra (artifacts written, verification re-runs in CI
and in downstream tasks):
- AC-3 Woodpecker runner; CI yml in place
- AC-4 docker-compose mocks land with AZ-660/AZ-644/AZ-675
- AC-5 SITL conformance lands with AZ-641/AZ-648/AZ-652
- AC-7 aarch64 cross-compile via cargo-zigbuild stage
- AC-8 systemd unit (Linux + systemd host)
Layering invariants from module-layout.md hold: shared (L1) imports
nothing; Layer 2 actor crates import only shared; Layer 3 coordinators
(operator_bridge, mission_executor) import only their documented Layer 2
deps; Layer 4 (scan_controller) imports its documented Layer 2 + Layer 3
deps; the autopilot binary (L5) is the only consumer of every component.
cargo fmt --all --check + cargo clippy --all-targets -- -D warnings both
clean. Jira AZ-640 transitioned to In Progress at the start of this batch;
the matching In Testing transition follows this commit.
Co-authored-by: Cursor <cursoragent@cursor.com>
Bring this repo's .cursor/ in line with the suite monorepo root .cursor/
so rules, skills, and autodev artifacts stay consistent across
submodules and sibling repos.
Co-authored-by: Cursor <cursoragent@cursor.com>
Replaced HARBOR_USER and HARBOR_TOKEN with REGISTRY_USER and REGISTRY_TOKEN for improved clarity and consistency. Adjusted the Docker login command and updated the image tagging to reflect the new registry path. This change enhances the pipeline's flexibility for different environments.
Add HARBOR_USER/HARBOR_TOKEN from Woodpecker secrets and a docker login
step before the existing build/push, so pipelines can push to the new
TLS-authenticated Harbor registry.
Pipeline reads $REGISTRY_HOST from the Woodpecker global secret
'registry_host' instead of hardcoding 'localhost:5000'. The full
host:port lives in the secret, so Harbor migration (AZ-205) becomes
one secret edit rather than editing every submodule pipeline.
Made-with: Cursor
- Updated coding rules to emphasize readability, meaningful comments, and maintainability.
- Adjusted test coverage thresholds to 75% for business logic and clarified expectations for test scenarios.
- Enhanced guidelines for handling skipped tests, emphasizing the need for investigation and resolution.
- Introduced a completeness audit for decomposition in research steps to ensure thoroughness in addressing problem dimensions.
Made-with: Cursor
- Introduced a matrix for building on both linux/arm64 and linux/amd64 platforms.
- Updated image tags to include platform-specific identifiers for better versioning.
- Enhanced the labels section to dynamically set the platform label based on the build matrix.
- Added a guideline to place all source code under the `src/` directory in `coderule.mdc`.
- Removed the outdated guideline regarding the `src/` layout in `python.mdc` to streamline project structure.
These updates improve project organization and maintainability by clarifying the structure for source code and project files.
Added functionality to set camera ready for bringing it down or up.
Camera will be made available for AI after bringCameraDown command is given via UDPSocket.
Camera will be made unavailable for AI after bringCameraUp command is given via UDPSocket.
Tool has three inputs map_tile_folder, current_latitude and current_longitude.
With these tool will got through map tiles in given folder and list tiles in order based on their distance from given location.
- uses MAVSDK::MissionRaw objects for missions
- added new state AZ_DRONE_STATE_MISSION_UPLOADED
- new state is used in AzDroneControllerPlane before waiting for AUTO switch
TODO!!
- move to AzMissionController
- use JSON file instead of hard coded mission items