devkit_ui.obstacles
Obstacle management for the Sowbot webui.
Owns the obstacle list, its YAML persistence, the /obstacles publisher, and the UI cards rendered on the Nav and Mission tabs. Designed to be attached to a NiceGuiNode (see ObstacleManager.attach) so it can read the node’s GPS/odom state and publish on its behalf, without inheriting from rclpy.node.Node itself.
Threading model
self._obstacles is an immutable tuple. All writes replace it wholesale under self._lock, and bump self._version in the same critical section. Readers grab the tuple reference lock-free (CPython attribute reads are atomic) and walk a consistent snapshot.
Two races are deliberately accepted:
Status string: two concurrent writers can race on obstacle_status; last writer wins. The string is human-readable progress only, so a late “saved” message can briefly mask an earlier “writing…” — never misleading enough to matter.
Topic vs file: _persist_and_publish snapshots under the lock, then writes the file, then publishes — both lock-free. An external topic observer can therefore briefly see a state newer than what’s on disk (by milliseconds). The next save converges.
Both are commented at the relevant call sites.
Coordinate frames
Obstacles are stored in WGS84 lat/lon — matches NavSatFix and Leaflet. They are projected to the local map frame at publish time using the (odom_xy ↔ gps_latlon) anchor from the host node’s latest_odom and latest_gps. Without an anchor we cannot project, so we don’t publish — a costmap subscriber would otherwise treat raw lat/lon as metric and place obstacles thousands of kilometres from the origin.
Functions
|
Renders the obstacle list + Leaflet rendering panel in the current ui context. |
|
Renders the Field/Obstacle draw-mode toggle, shape sub-toggle, radius input, obstacle padding input, and Finish-polygon button in the current ui context (the Mission sidebar column). |
|
Render the “Mark Obstacle” card and keep its status display synchronized with the obstacle manager. |
|
Tessellate a circle into a closed WGS84 ring. |
|
Convert (lat, lon) to local ENU (x, y) metres relative to (lat0, lon0). |
|
Single obstacle dict → closed lat/lon ring, or [] for degenerate input. |
|
All obstacles → F2C-ready list of rings. |
|
Validate add() inputs. |
|
Inverse of latlon_to_xy. |
Classes
|
State + accessors shared between the sidebar controls and the list panel. |
|
Owns the obstacle list, persistence, and the ROS publisher. |
- class devkit_ui.obstacles.MissionDrawHandle(node, manager: ObstacleManager, mission_map, obstacle_pad_widget)[source]
Bases:
objectState + accessors shared between the sidebar controls and the list panel. Created by attach_mission_sidebar_controls; consumed by attach_mission_obstacle_panel.
- class devkit_ui.obstacles.ObstacleManager(path: str = '/workspace/maps/obstacles.yaml')[source]
Bases:
objectOwns the obstacle list, persistence, and the ROS publisher.
After attach(node), the node exposes:
node.obstacles : tuple[dict, …] read-only snapshot node.obstacles_version : int bumps on every change node.obstacle_status : str last-action status node.default_obstacle_radius : float shared by UI bindings
- add(kind: str, *, lat: float | None = None, lon: float | None = None, radius_m: float = 0.5, points_ll: list | None = None, name: str = '') str | None[source]
Add a circle or polygon obstacle. Returns the allocated name on success, None on failure (status carries the reason either way).
- devkit_ui.obstacles.attach_mission_obstacle_panel(handle: MissionDrawHandle) None[source]
Renders the obstacle list + Leaflet rendering panel in the current ui context. Call from below the map (wide layout).
- devkit_ui.obstacles.attach_mission_sidebar_controls(node, manager: ObstacleManager, mission_map, boundary_click: Callable[[float, float], None]) MissionDrawHandle[source]
Renders the Field/Obstacle draw-mode toggle, shape sub-toggle, radius input, obstacle padding input, and Finish-polygon button in the current ui context (the Mission sidebar column).
Also wires the mission_map’s ‘map-click’ handler to dispatch between boundary-drawing (callback) and obstacle-drawing (manager.add).
Returns a handle the caller passes to attach_mission_obstacle_panel.
Render the “Mark Obstacle” card and keep its status display synchronized with the obstacle manager.
- Parameters:
node – UI node used to store the default radius, GPS data, and status.
manager (ObstacleManager) – Manager used to add and remove obstacles.
- devkit_ui.obstacles.circle_to_ring_ll(center_lat: float, center_lon: float, radius_m: float) list[tuple[float, float]][source]
Tessellate a circle into a closed WGS84 ring. Side count scales with radius, clamped to [16, 64].
- devkit_ui.obstacles.latlon_to_xy(lat: float, lon: float, lat0: float, lon0: float) tuple[float, float][source]
Convert (lat, lon) to local ENU (x, y) metres relative to (lat0, lon0).
- devkit_ui.obstacles.obstacle_to_ring_ll(obs: dict) list[tuple[float, float]][source]
Single obstacle dict → closed lat/lon ring, or [] for degenerate input.
- devkit_ui.obstacles.obstacles_to_rings_ll(obstacles) list[list[tuple[float, float]]][source]
All obstacles → F2C-ready list of rings. Drops degenerate entries.