devkit_ui.missions

class devkit_ui.missions.MissionStore(path: str = '/workspace/maps/missions.db')[source]

Bases: object

Owns the mission list and its YAML persistence.

After attach(node), the node exposes:

node.missions : tuple[dict, …] read-only snapshot node.missions_version : int bumps on every change node.mission_status : str last-action status

Mission record schema:

id: ‘MISSION_1’ (allocated, immutable) name: str (operator label; defaults to id) rows: list[str] (topo entry-node names) action: str (key in ACTIONS) action_params: dict (per-action parameter overrides) repeat_every_hours: int | None (None == one-shot) active: bool created_at: str (ISO 8601 UTC) last_run_at: str | None last_run_success: bool | None

add(*, rows: list, action: str, action_params: dict | None = None, name: str = '', repeat_every_hours: int | None = None, active: bool = True) → str | None[source]

Add a mission. Returns its allocated id, or None on failure. Status carries the reason either way.

attach(node) → None[source]

Wire into a NiceGuiNode. Kicks off a background load.

close() → None[source]

Release backend resources (the SQLite connection). A no-op for the YAML backend.

delete(mid: str) → bool[source]
find(mid: str) → dict | None[source]

Lock-free single-mission lookup by id. Returns the dict from the current snapshot — treat as read-only.

find_by_name(name: str) → dict | None[source]

Lock-free lookup by operator name. Returns the first match. Useful for collision checks before add() (e.g. the UI_RUN record the executor creates to anchor repeat intervals).

next_due_in_hours(mid: str) → float | None[source]

For the UI chip. Returns one of:

DUE_NOW (0.0) active, never run yet DUE_FAILED (-1.0) active, last run failed — retry sentinel > 0.0 hours until the recurring interval elapses None inactive, completed one-shot, or unknown id

Import DUE_NOW / DUE_FAILED from this module rather than comparing against magic floats. UI rendering pattern:

h = store.next_due_in_hours(mid) if h is None: label = ‘done’ elif h == DUE_FAILED: label = ‘retry’ elif h == DUE_NOW: label = ‘due now’ else: label = f’in {h:.1f}h’

record_run(mid: str, success: bool) → bool[source]

Record a run outcome. Called by the executor (and any ‘Run now’ path) so the repeat interval re-arms from actual robot activity regardless of who triggered the run.

Side effect: one-shot missions (repeat_every_hours is None) that complete successfully are auto-deactivated.

Returns False for an unknown ID; otherwise returns whether the backend accepted the update.

reset(mid: str) → bool[source]

Re-arm a mission: clear run history and re-activate.

The canonical path for:
  • a completed one-shot that needs to run again (e.g. crop re-planted),

  • a failed mission the operator wants to retry without waiting for the next scheduled executor pass.

Does not modify other mission fields. Returns False for an unknown ID; otherwise returns whether the backend accepted the update.

set_active(mid: str, active: bool) → bool[source]

Convenience wrapper around update(). The common toggle.

today_queue() → list[tuple[str, str, str, dict]][source]

Active+due missions expanded into (mission_id, row_id, action, action_params) tuples in declaration order. Lock-free; the executor walks this list and dispatches each tuple in sequence.

update(mid: str, **fields) → bool[source]
devkit_ui.missions.validate_mission(name: str, rows: list, action: str, repeat_every_hours: int | None) → str | None[source]

Return an error string, or None if OK.

Expects name to already be cleaned (uppercase, only [A-Z0-9_]). MissionStore.add() and .update() clean before calling; external callers should do the same or pass name=’’ to use the id default.

Modules

sqlite

store

Mission storage and scheduling for the Sowbot webui.

test_sqlite

test_store

test_yaml

yaml