Skip to content

odometer system#3871

Draft
patrickelectric wants to merge 1 commit intobluerobotics:masterfrom
patrickelectric:odometer
Draft

odometer system#3871
patrickelectric wants to merge 1 commit intobluerobotics:masterfrom
patrickelectric:odometer

Conversation

@patrickelectric
Copy link
Copy Markdown
Member

@patrickelectric patrickelectric commented Apr 10, 2026

Summary by Sourcery

Introduce a new Zenoh-powered odometer microservice and expose it through the HTTP/API gateway.

New Features:

  • Add an odometer FastAPI service that tracks cumulative distance from MAVLink GLOBAL_POSITION_INT messages via Zenoh and persists state to disk.
  • Expose odometer state retrieval and reset endpoints under a versioned /odometer API path via the existing HTTP gateway and frontend dev proxy.

Build:

  • Define a dedicated pyproject configuration for the odometer service with its Python dependencies.

Signed-off-by: Patrick José Pereira <patrickelectric@gmail.com>
@sourcery-ai
Copy link
Copy Markdown

sourcery-ai bot commented Apr 10, 2026

Reviewer's Guide

Introduces a new Zenoh-powered "odometer" microservice (with HTTP API, persistence, and FastAPI versioning) and wires it into BlueOS via nginx, frontend dev proxy, and helper port configuration.

Sequence diagram for odometer HTTP state retrieval

sequenceDiagram
    actor User
    participant Browser
    participant Nginx
    participant OdometerAPI as Odometer_FastAPI
    participant OdometerService

    User->>Browser: Request_/odometer/
    Browser->>Nginx: HTTP_GET_/odometer/
    Nginx->>OdometerAPI: Proxy_GET_/odometer/
    OdometerAPI->>OdometerService: get_state()
    OdometerService-->>OdometerAPI: OdometerState
    OdometerAPI-->>Nginx: 200_OK_JSON_OdometerState
    Nginx-->>Browser: 200_OK_JSON_OdometerState
    Browser-->>User: Render_total_distance_and_metadata
Loading

Sequence diagram for odometer Zenoh sample processing

sequenceDiagram
    participant ArduPilot
    participant ZenohRouter as Zenoh_router
    participant OdometerSession as zenoh_Session
    participant OdometerService
    participant StateFile as odometer_json_state

    ArduPilot->>ZenohRouter: Publish_GLOBAL_POSITION_INT
    ZenohRouter->>OdometerSession: Forward_SAMPLE_for_key_expr
    OdometerSession->>OdometerService: _on_sample(sample)
    OdometerService->>OdometerService: Parse_payload_to_Position
    OdometerService->>OdometerService: update_position(position)
    Note over OdometerService: Update_total_distance_m_last_position_sample_count
    loop Periodic_persistence
        OdometerService->>StateFile: persist_state()
    end
Loading

Class diagram for the new odometer service domain model

classDiagram
    class Position {
        float lat_deg
        float lon_deg
        float alt_m
        int time_boot_ms
    }

    class OdometerState {
        float total_distance_m
        Position last_position
        int sample_count
        datetime updated_at
    }

    class _RawState {
        float total_distance_m
        dict~string_Any~ last_position
        int sample_count
        string updated_at
    }

    class ResetResponse {
        string status
        OdometerState state
    }

    class OdometerService {
        +Path state_path
        +string key_expr
        +Path zenoh_config_path
        +float persist_interval_seconds
        -OdometerState _state
        -Lock _lock
        -zenoh_Session _session
        -zenoh_Subscriber _subscriber
        -Task _persist_task
        -AbstractEventLoop _loop
        +OdometerService(state_path, key_expr, zenoh_config_path, persist_interval_seconds)
        +start()
        +stop()
        -_open_session() zenoh_Session
        -_load_state() OdometerState
        -_on_sample(sample)
        +update_position(position) OdometerState
        +get_state() OdometerState
        +reset() OdometerState
        -_serialize_state() _RawState
        +persist_state()
        -_write_state(serialized)
        -_persist_state_periodically()
    }

    Position <.. OdometerState : uses
    Position <.. OdometerService : uses
    OdometerState <.. OdometerService : manages
    _RawState <.. OdometerService : serialization
    OdometerState <.. ResetResponse : contains
    OdometerService <.. get_odometer_service : provided_by
Loading

File-Level Changes

Change Details Files
Expose the odometer HTTP API through nginx and the frontend dev proxy so it is reachable via /odometer.
  • Add /odometer/ location block in nginx that proxies to the odometer service on port 9127 with CORS enabled.
  • Add a Vite dev-server proxy rule mapping ^/odometer to the backend SERVER_ADDRESS.
core/tools/nginx/nginx.conf
core/frontend/vite.config.js
Register the odometer service port in the helper service so it is considered an expected/managed core service.
  • Include port 9127 in the Helper service’s known core service ports list.
core/services/helper/main.py
Implement the odometer microservice that accumulates traveled distance from MAVLink GLOBAL_POSITION_INT messages received via Zenoh and exposes a REST API to read/reset the state.
  • Define configuration via argparse and environment variables for HTTP port, state file path, Zenoh config, key expression, and persistence interval.
  • Implement OdometerState and Position Pydantic models plus internal _RawState dataclass for JSON serialization.
  • Implement haversine-based distance calculation between subsequent positions to update total_distance_m and sample_count with thread-safe access via an asyncio.Lock.
  • Open a Zenoh session (optionally from config file) and declare a subscriber on the configured key expression, parsing MAVLink GLOBAL_POSITION_INT-style messages into Position objects.
  • Persist state periodically and on shutdown to a JSON file under an appdirs-based data directory, with robust load/parse and error handling.
  • Expose FastAPI endpoints /odometer/ (GET) to fetch state and /odometer/reset (POST) to reset state, wrapped with fastapi-versioning and GenericErrorHandlingRoute, and add a simple HTML root handler.
  • Wire service lifecycle into FastAPI startup/shutdown events and run it via uvicorn in an asyncio main().
core/services/odometer/main.py
Declare the odometer service as a standalone Python project with its dependencies for packaging/running.
  • Add pyproject.toml specifying project metadata, Python version, runtime dependencies (zenoh, FastAPI, commonwealth, etc.), and uv configuration with commonwealth sourced from the workspace.
core/services/odometer/pyproject.toml

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant