fix: align navigation contract and readiness handling
This commit is contained in:
@@ -55,7 +55,6 @@ def validate_goal(body):
|
||||
elif not isinstance(value, str) or len(value) > 256: raise GatewayError("invalid trace text")
|
||||
validate_pose(body["target_pose"])
|
||||
for key in ("position_tolerance", "yaw_tolerance", "timeout_sec"): number(body[key], key, positive=True)
|
||||
if body["yaw_tolerance"] > math.pi: raise GatewayError("yaw_tolerance is radians and must be <= pi")
|
||||
return copy.deepcopy(body)
|
||||
|
||||
|
||||
@@ -130,10 +129,14 @@ class Gateway:
|
||||
try: health = self.backend.health()
|
||||
except Exception: health = {}
|
||||
fresh = self._fresh(health, self.config.readiness_max_age_sec)
|
||||
ready = health.get("ready") is True and fresh and not self._occupied()
|
||||
blocked_known = self._blocked_known(health.get("blocked"))
|
||||
ready = health.get("ready") is True and fresh and blocked_known and not self._occupied()
|
||||
return {"ready": ready, "map_id": health.get("map_id"), "stamp_monotonic": self.clock(),
|
||||
"error_code": ("" if self._occupied() else "INPUTS_UNHEALTHY" if not blocked_known
|
||||
else health.get("error_code", "") if fresh else ""),
|
||||
"reason": "motion resource occupied or quarantined" if self._occupied() else
|
||||
(health.get("reason", "") if fresh else "readiness missing or stale")}
|
||||
("blocked telemetry missing or stale" if not blocked_known else
|
||||
health.get("reason", "") if fresh else "readiness missing or stale")}
|
||||
|
||||
def submit(self, body):
|
||||
goal = validate_goal(body)
|
||||
@@ -189,6 +192,10 @@ class Gateway:
|
||||
stamp = sample.get("received_at")
|
||||
return isinstance(stamp, (int, float)) and math.isfinite(stamp) and 0 <= self.clock()-stamp <= maximum
|
||||
|
||||
def _blocked_known(self, sample):
|
||||
return (self._fresh(sample, self.config.readiness_max_age_sec) and
|
||||
type(sample.get("value")) is bool)
|
||||
|
||||
def poll(self, goal_id):
|
||||
with self.lock:
|
||||
r = self._record(goal_id)
|
||||
@@ -198,14 +205,25 @@ class Gateway:
|
||||
if not r["_cancel_sent"]:
|
||||
try: health = self.backend.health()
|
||||
except Exception: health = {}
|
||||
if not self._fresh(health, self.config.readiness_max_age_sec) or health.get("ready") is not True or health.get("map_id") != r["request"]["map_id"]:
|
||||
if not self._fresh(health, self.config.readiness_max_age_sec) or health.get("ready") is not True or health.get("map_id") != r["request"]["map_id"] or not self._blocked_known(health.get("blocked")):
|
||||
self.cancel(goal_id, "FAILED")
|
||||
if (self._fresh(health, self.config.readiness_max_age_sec) and
|
||||
health.get("error_code") in {"INPUTS_UNHEALTHY", "ROBOT_STATE_UNAVAILABLE", "BACKEND_NOT_CONFIGURED"}):
|
||||
r["error_code"] = health["error_code"]
|
||||
if not self._blocked_known(health.get("blocked")): r["error_code"] = "INPUTS_UNHEALTHY"
|
||||
r["message"] = "readiness lost during execution; stop requested"
|
||||
try: snapshot = self.backend.snapshot(goal_id)
|
||||
except Exception: snapshot = {"controller_state": "UNKNOWN"}
|
||||
state = snapshot.get("controller_state", "UNKNOWN")
|
||||
r.update(controller_state=state, sequence=r["sequence"]+1, elapsed=max(0, self.clock()-r["_started"]))
|
||||
r.update(pose_valid=False, current_pose=None)
|
||||
blocked_sample = snapshot.get("blocked")
|
||||
r["blocked"] = (blocked_sample["value"] if
|
||||
self._fresh(blocked_sample, self.config.readiness_max_age_sec) and
|
||||
type(blocked_sample.get("value")) is bool else None)
|
||||
if r["blocked"] is None:
|
||||
if not r["_cancel_sent"]: self.cancel(goal_id, "FAILED")
|
||||
r["error_code"] = "INPUTS_UNHEALTHY"
|
||||
pose_sample = snapshot.get("pose")
|
||||
if self._fresh(pose_sample, self.config.pose_max_age_sec):
|
||||
try:
|
||||
|
||||
Reference in New Issue
Block a user