Skip to content

implement v0.1.0 — Tello+Simulator foundation with CI/CD - #32

Merged
thanos merged 14 commits into
mainfrom
v0.1.0/Tello+Simulator_foundation
Jun 10, 2026
Merged

implement v0.1.0 — Tello+Simulator foundation with CI/CD#32
thanos merged 14 commits into
mainfrom
v0.1.0/Tello+Simulator_foundation

Conversation

@thanos

@thanos thanos commented Jun 10, 2026

Copy link
Copy Markdown
Owner

Research & Design

Core Library (lib/)

Tests (test/) — 170 tests, 0 failures, closed #29

  • Public API, Vehicle, Adapter, Sim, Tello (connection/encoder/parser), Command, Error, Safety, Policy, Geofence, Mission, Telemetry

CI/CD (.github/workflows/), closed #30

  • ci.yml: 7 jobs — lint, test (Elixir 1.17–1.20 matrix), coverage (coveralls), security (sobelow), dialyzer, docs, release gate
  • release.yml: Hex.pm publish on tag

Quality Tools, closed #31

  • excoveralls with 70% min coverage threshold
  • sobelow for security auditing
  • dialyxir for type checking
  • credo with ExSlop plugin for AI-slop detection (40 checks)
  • .credo.exs: strict config with anti-slop checks enabled

Key Design Decisions

  • Vehicle child_spec restart: :temporary (prevents restart loops)
  • SDK mode command bypasses allowlist (required to enter command mode)
  • Dry-run mode still updates vehicle state for subsequent commands
  • Vehicle state initialized from adapter telemetry on connect
  • Sim adapter returns {:error, reason, state} 3-tuple per Adapter contract
  • Drone.connect/2 returns {:error, :name_already_taken} on duplicates
  • Mission commands stored as prepend-list, reversed on read/run
  • Command history stored as prepend-list for O(1) insertion

Refactoring (from ExSlop findings)

  • Replaced list ++ [item] with [item | list] in Mission, Vehicle, Sim.State
  • Replaced length(list) >= 3 guard with pattern match [_, _, _ | _]
  • Replaced length() comparisons with Enum.count() in test assertions
  • Added trailing newline to mission.ex

thanos added 14 commits June 10, 2026 06:23
Research & Design
  - docs/research/tello_sdk.md: Tello SDK protocol, UDP commands, responses, closed #1
  - docs/research/beam_udp.md: BEAM UDP patterns, active mode, packet framing, closed #2
  - docs/research/safety_model.md: safety pipeline requirements and constraints, closed #3
  - docs/research/simulator_design.md: simulator architecture, state machine design, closed #4
  - docs/design/v0_1_0_plan.md: full v0.1.0 implementation roadmap, closed #5
  - docs/design/adapter_contract.md: Adapter behaviour contract specification, closed #6
  - docs/design/safety_pipeline.md: safety validation flow and policy docs, closed #7
  - docs/design/telemetry_events.md: :telemetry event definitions, closed #8
  - docs/reviews/v0_1_0_review_checklist.md: design review — all 6 gates passed

Core Library (lib/)
  - lib/drone.ex: Public API (connect, disconnect, command proxies), closed #9
  - lib/drone/vehicle.ex: Supervised GenServer per drone, command pipeline, closed #10
  - lib/drone/adapter.ex: Adapter behaviour + Drone.Adapter.resolve/1, closed #11
  - lib/drone/adapters/sim.ex: Simulator adapter (in-process state machine), closed #12
  - lib/drone/adapters/sim/state.ex: Sim state struct with battery/position tracking, closed #13
  - lib/drone/adapters/tello.ex: Tello UDP adapter, closed #14
  - lib/drone/adapters/tello/connection.ex: UDP socket management, heartbeat, closed #15
  - lib/drone/adapters/tello/encoder.ex: Command encoding to Tello protocol, closed #16
  - lib/drone/adapters/tello/parser.ex: Response parsing from Tello protocol, closed #17
  - lib/drone/command.ex: Command struct with constructors and predicates, closed #18
  - lib/drone/error.ex: Error types (:safety, :connection, :command, :adapter), closed #21
  - lib/drone/safety.ex: Pure validation module — the safety pipeline, closed #22
  - lib/drone/safety/policy.ex: Policy struct with indoor/unrestricted presets, closed #23
  - lib/drone/safety/geofence.ex: Circle and polygon geofence support, closed #24
  - lib/drone/mission.ex: Mission DSL for scripting command sequences, closed #25
  - lib/drone/telemetry.ex: :telemetry event emission, closed #26
  - lib/drone/supervisor.ex: DynamicSupervisor for drone processes, closed #27
  - lib/drone/application.ex: OTP application with supervised tree, closed #28

Tests (test/) — 170 tests, 0 failures, closed #29
  - Public API, Vehicle, Adapter, Sim, Tello (connection/encoder/parser),
    Command, Error, Safety, Policy, Geofence, Mission, Telemetry

CI/CD (.github/workflows/), closed #30
  - ci.yml: 7 jobs — lint, test (Elixir 1.17–1.20 matrix), coverage (coveralls),
    security (sobelow), dialyzer, docs, release gate
  - release.yml: Hex.pm publish on tag

Quality Tools, closed #31
  - excoveralls with 70% min coverage threshold
  - sobelow for security auditing
  - dialyxir for type checking
  - credo with ExSlop plugin for AI-slop detection (40 checks)
  - .credo.exs: strict config with anti-slop checks enabled

Key Design Decisions
  - Vehicle child_spec restart: :temporary (prevents restart loops)
  - SDK mode command bypasses allowlist (required to enter command mode)
  - Dry-run mode still updates vehicle state for subsequent commands
  - Vehicle state initialized from adapter telemetry on connect
  - Sim adapter returns {:error, reason, state} 3-tuple per Adapter contract
  - Drone.connect/2 returns {:error, :name_already_taken} on duplicates
  - Mission commands stored as prepend-list, reversed on read/run
  - Command history stored as prepend-list for O(1) insertion

Refactoring (from ExSlop findings)
  - Replaced list ++ [item] with [item | list] in Mission, Vehicle, Sim.State
  - Replaced length(list) >= 3 guard with pattern match [_, _, _ | _]
  - Replaced length() comparisons with Enum.count() in test assertions
  - Added trailing newline to mission.ex
…0 tests pass, credo strict is clean.

- safety.ex:88: Removed unreachable emergency clause — emergency commands bypass the pipeline entirely at line 55.
- sim.ex:110: Merged into single :emergency catch-all — emergency commands are handled before check_mode is called.
- tello.ex:211: Removed catch-all clause — all known command types are explicitly matched above.
- tello.ex: Already removed in previous commit (_cmd catch-all)
- vehicle.ex:367: Removed update_vehicle_state(vehicle_state, cmd, _reply) catch-all — all 11 command types are explicitly matched, so it could never be reached
Both dialyzer pattern_match_cov errors should now be resolved.
1. Coveralls CI failure: Made mix coveralls.github non-blocking with || true — coveralls upload won't kill CI. The 70% threshold check (mix coveralls --min-coverage 70) still enforces coverage.
2. Placeholder repo URLs: Updated mix.exs and README.md from user/ex_drone to thanos/ex_drone.
Note: You'll also need to register the repo on coveralls.io (https://coveralls.io) and set the COVERALLS_REPO_TOKEN secret in GitHub Actions for the badge/upload to work properly.
…ralls will enforce the 70% threshold automatically.
Added a validate_args/1 stage as the first check in Safety.check/3 (lib/drone/safety.ex). Rejects out-of-range values with {:error, :safety, reason}:
- move distance: 20–500 cm → :invalid_distance
- rotate degrees: 1–3600 → :invalid_degrees
- set_speed speed: 10–100 cm/s → :invalid_speed
- hover seconds: positive integer → :invalid_seconds
Emergency commands still bypass (validated empirically). Limits defined as module constants matching the Tello SDK.

F-02: Integer battery (High), closed #34
Sim adapter now reports trunc(state.battery) in both telemetry/1 and query(:battery) (lib/drone/adapters/sim.ex), while keeping fractional drain precision internally. State type updated to number().

F-04: Graceful unknown-name handling (High), closed #36
Introduced private call/2 and command/2 helpers in lib/drone.ex that check Vehicle.whereis/1 for nil and return {:error, :not_connected} instead of crashing. This also eliminated ~9 duplicated response-handling blocks.

F-03: Single disconnect (High), closed #35
Removed the explicit adapter_module.disconnect/1 call from handle_call(:disconnect, ...) in lib/drone/vehicle.ex; cleanup now happens only in terminate/2. Verified with a counting test adapter asserting exactly one call.
- closed #41 and closed #42 F-09/F-10: Wire unrestricted + accept %Policy{}
- closed #40 F-08: Parse negative numbers (Tello parser)
Tests (+30 tests, 170 → 200)
- State-asserting movement tests (position via telemetry, rotation-affects-heading)
- Drone.GeometryTest unit tests for the extracted module
- Parser negative/composite number tests
- Policy unrestricted preset and precedence tests
- Vehicle %Policy{} struct connect test
- Disconnect telemetry termination-path test (also fixed a latent telemetry metadata bug: terminate/2 was emitting the raw module instead of the adapter key)
Duplication: shared geometry module
Extracted Drone.Geometry (move_delta/3, rotate_yaw/3, flip_delta/1) and removed the verbatim duplicates from vehicle.ex, sim.ex, and tello.ex. The call_command/command helpers were already extracted in the prior F-04 batch.
Issue #43: Centralized Tello connection defaults
- Exposed default_*() functions from Connection module
- Removed duplicate hardcoded defaults in Tello.connect/1
- Single source of truth for drone_ip, drone_port, local_port, timeout

Issue #44: Simplified Vehicle.child_spec
- Changed :id from dynamic Keyword.get(opts, :name) to constant __MODULE__
- DynamicSupervisor ignores the :id value anyway (generates its own)
- Reduced unnecessary computation of unused value
- Updated test to document the simplification

Issue #45: Complete flight time simulation
- Added State.add_flight_time/2 helper to sim/state.ex
- Takeoff/land now add 3 seconds of flight time
- Move/rotate/flip now add 2 seconds of flight time
- Hover adds actual hover duration (seconds) to flight time
- query(:time) now returns state.flight_time_seconds instead of command count
- Matches real Tello behavior (motor-on time in seconds)

Issue #50: Removed duplication and dead code
- Extracted @default_vehicle_state module attribute in vehicle.ex
- Defstruct and fallback path now reference same source
- Removed duplicate default_vehicle_state/0 private function
- Fixed credo issues: aliased nested modules, formatted large numbers with underscores
- Command utility functions (types/0, safe_to_retry?/1, etc.) kept as public API

All tests passing (215), credo --strict clean, coverage 86.9%
Added Mox for behavior mocking and comprehensive test coverage improvements:

Coverage Improvements:
- sim.ex: 92.4% -> 97.4% (+5%)
- mission.ex: 66.6% -> 95.8% (+29.2%)
- supervisor.ex: 57.1% -> 100% (+42.9%)
- tello/connection.ex: 77.7% -> 83.3% (+5.6%)
- vehicle.ex: 89.3% (added state synchronization tests)
- safety.ex: 94.1% (added edge case tests)
- Overall: 87.4% -> 90.2% (+2.8%)

New Tests:
- test/drone/supervisor_test.exs: Full coverage for start_vehicle/stop_vehicle
- test/drone/mission_test.exs: Added tests for all command types (flip, emergency, speed, stop, all query types), error handling, command ordering
- test/drone/adapters/sim_test.exs: Added hover, speed, stop, emergency mode blocking, all query types (:height, :speed, :time, :wifi, :serial_number), failure_rate randomization
- test/drone/adapters/tello/connection_test.exs: Added default_*() function tests, closed socket error handling
- test/drone/vehicle_test.exs: Added state synchronization tests (flying, mode, speed tracking), error handling
- test/drone/safety_test.exs: Added speed/stop command validation, geofence edge cases

Dependencies:
- Added {:mox, "~> 1.1"} for behavior mocking
- Configured test_coverage to exclude FakeTelloServer from coverage

All 253 tests passing, coverage above 90%
test/drone/supervisor_test.exs (NEW - 100% coverage)
- start_vehicle/1 tests with error handling
- stop_vehicle/1 tests with proper process termination checks
- Fixed flaky tests with Process.monitor for async termination
test/drone/mission_test.exs (29.2% improvement)
- All command types: flip (4 directions), emergency, speed, stop
- All query types: battery, height, speed, time, wifi, sdk_version, serial_number
- Command ordering verification
- Error handling: nonexistent drone, command errors
test/drone/adapters/sim_test.exs (5% improvement)
- hover command with flight time tracking
- speed and stop commands
- Emergency mode blocking all subsequent commands
- All query types: height, speed, time, wifi, serial_number
- failure_rate randomization (1.0 probability)
test/drone/adapters/tello/connection_test.exs (5.6% improvement)
- default_drone_ip(), default_drone_port(), default_local_port(), default_timeout()
- Error handling for closed sockets
test/drone/vehicle_test.exs (added state synchronization)
- Flying state tracking across commands
- Mode transitions: idle -> sdk_mode -> flying -> emergency
- Speed changes and stop command
- Adapter connection error handling
test/drone/safety_test.exs (edge cases)
- speed command validation (grounded and flying)
- stop command validation (requires flying)
- Geofence nil check
Technical Changes
1. Added Mox dependency (~> 1.1) for behavior-based mocking
2. Excluded FakeTelloServer from coverage reports via mix.exs
3. Fixed test flakiness in supervisor tests with proper process monitoring
4. 253 tests now passing (was 215), all stable
Documentation:
- CHANGELOG.md: Comprehensive v0.1.0 changelog with Added/Changed/Fixed
- README.md: Added Drone.Geometry to architecture section
- docs/architecture.md: Added Geometry module, updated command pipeline
  to show validate_args step and emergency bypass
- docs/safety.md: Added Policy struct direct usage example
- docs/simulator.md: Added flight time simulation docs
- docs/tello.md: Replaced FakeServer reference with test suite guidance,
  added Connection.default_*() accessor documentation

mix.exs:
- Added Mox dependency for test mocking
- Configured test_coverage to exclude FakeTelloServer

.gitignore:
- Added *.tar to ignore hex package builds
…ss.monitor

DynamicSupervisor.terminate_child is synchronous but Registry cleanup
is async. The process is dead when stop_vehicle returns, but the name
may still be registered briefly. Replaced Process.monitor with an
eventually/3 poll that retries until the name is unregistered.
@thanos
thanos merged commit ef98717 into main Jun 10, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment