Merge branch 'main' of ssh://892887cvmk08.aicp.io:24928/orisys-tech/orisys-app

This commit is contained in:
ykcheungab
2026-10-08 10:02:27 +08:00
8 changed files with 171 additions and 5 deletions
@@ -111,7 +111,16 @@ class AxisState:
@dataclass(frozen=True)
class BusStatus:
"""Bus-level state, from the ``.bas`` program's globals."""
"""Bus-level state, from the ``.bas`` program's globals.
``verified_this_session`` exists because ``BUS_TYPE``, ``Bus_InitStatus``
and ``Bus_TotalAxisnum`` are ZBasic **globals**: they live in controller
memory and survive the program finishing, a reconnect, and (with the ROM
program reloading) a reboot. ``Bus_InitStatus == 1`` therefore means "a scan
completed at some point", not "this bus is live now". A controller that was
power-cycled but not re-initialised still reports 1, and reporting that as
初始化成功 is what made a bus which had never been set up look ready.
"""
bus_type: BusType = "unknown"
init_status: int = INIT_RUNNING
@@ -121,6 +130,14 @@ class BusStatus:
bas_loaded: bool = False
init_started_at: float | None = None
message: str = ""
#: False until this session has seen the state machine through a run it
#: started (a download or a re-init). An adopted latch starts False.
verified_this_session: bool = False
@property
def adopted_unverified(self) -> bool:
"""The latch says initialised, but nothing here has confirmed it."""
return self.init_status == INIT_OK and not self.verified_this_session
@property
def initialized(self) -> bool:
@@ -401,7 +401,9 @@ class MotionService:
self._spec = spec
logger.info("Connected: %s via %s", spec.describe(), self._transport.source)
# A controller may already have a BAS program running from a previous
# session; adopt it rather than claiming the bus is uninitialised.
# session; adopt it rather than claiming the bus is uninitialised. The
# adopted state is *not* marked verified: its globals are a latch, and
# nothing in this session has yet seen a scan happen.
self._adopt_existing_bas(bas_path=bas_path)
return MotionResult.success(f"已连接 {spec.describe()}" if self._is_zh else f"Connected {spec.describe()}")
@@ -469,12 +471,14 @@ class MotionService:
self._bas_loaded = True
self._init_started_at = time.monotonic()
# A download is a run this session owns, so its outcome can be trusted.
self._bus = BusStatus(
bus_type=self._bus.bus_type,
init_status=INIT_RUNNING,
bas_path=filename,
bas_loaded=True,
init_started_at=self._init_started_at,
verified_this_session=True,
)
# Read BUS_TYPE so the SDO/RTEX panels enable for the right family.
try:
@@ -541,6 +545,7 @@ class MotionService:
bas_path=self._bus.bas_path,
bas_loaded=True,
init_started_at=self._init_started_at,
verified_this_session=True,
)
# Re-read BUS_TYPE now: the program may be a different family than the
# one the previous session left behind.
@@ -662,7 +667,13 @@ class MotionService:
)
return
verified = bus.verified_this_session
if init_status != INIT_RUNNING:
# A run this session owned has just settled: that is the only
# evidence available that the state machine actually moved, rather
# than the globals having been left at their last values.
if self._init_started_at is not None:
verified = True
self._init_started_at = None
try:
node_count = self._transport.bus_node_count(0)
@@ -679,6 +690,7 @@ class MotionService:
bas_loaded=True,
init_started_at=self._init_started_at,
message=message,
verified_this_session=verified,
)
def _read_axis(self, axis: int) -> AxisState:
@@ -967,4 +979,7 @@ def _replace_bus_type(bus: BusStatus, bus_type: BusType) -> BusStatus:
bas_loaded=bus.bas_loaded,
init_started_at=bus.init_started_at,
message=bus.message,
# Carried, not reset: re-reading BUS_TYPE is incidental, and dropping
# this would make a just-verified init look unverified again.
verified_this_session=bus.verified_this_session,
)
@@ -36,6 +36,7 @@ _STATE_KEYS = {
_STATE_TONES = {
"bus.state_running": "warn",
"bus.state_ok": "ok",
"bus.state_unverified": "warn",
"bus.state_failed": "error",
"bus.state_timeout": "error",
}
@@ -140,10 +141,19 @@ class BusPanel(QGroupBox):
self.choose_button.setEnabled(True)
def set_status(self, bus: BusStatus, *, now: float | None = None) -> None:
"""Render the bus state, including the vendor's timeout case."""
"""Render the bus state, including the vendor's timeout case.
An *adopted* ``Bus_InitStatus == 1`` is shown as unverified rather than
as success. The value is a ZBasic global that survives the program and a
reboot, so it proves a scan happened at some point, not that this bus is
live now — and calling that 初始化成功 is what made a machine which had
never been set up look ready to move.
"""
key = _STATE_KEYS.get(bus.init_status, "bus.state_pending")
if bus.init_status == INIT_FAILED and bus.message and "超时" in bus.message:
key = "bus.state_timeout"
elif bus.adopted_unverified:
key = "bus.state_unverified"
self._state_text = t(key)
self._state_tone = _STATE_TONES.get(key, "muted")
self.state_value.setText(self._state_text)
@@ -156,7 +166,7 @@ class BusPanel(QGroupBox):
self.type_value.setText(t(type_keys.get(bus.bus_type, "bus.type_unknown")))
self.nodes_value.setText(str(bus.node_count))
self.axes_value.setText(str(bus.total_axes))
self.message_label.setText(bus.message)
self.message_label.setText(bus.message or (t("bus.unverified_hint") if bus.adopted_unverified else ""))
# ------------------------------------------------------------- translations
@@ -224,6 +224,12 @@ class StatusStrip(QFrame):
if not connected:
self.chip_init.setText(t("bus.state_pending"))
self.chip_init.set_tone("muted")
elif snapshot.bus.adopted_unverified:
# The controller's initialisation global is a latch that survives the
# program and a reboot; without a run this session, "initialised" is
# not something this process has seen. Say so rather than claim it.
self.chip_init.setText(t("bus.state_unverified"))
self.chip_init.set_tone("warn")
elif snapshot.bus.initialized:
self.chip_init.setText(t("bus.state_ok"))
self.chip_init.set_tone("ok")
+3 -1
View File
@@ -878,7 +878,9 @@ class AutoCalMainWindow(QMainWindow):
t("status.poll_paused") if self.status_strip.paused else t("status.poll_ok"),
t("status.poll_every", ms=self.poller.interval_ms),
]
if snapshot.bus.init_status == 1:
if snapshot.bus.adopted_unverified:
parts.append(t("bus.state_unverified"))
elif snapshot.bus.init_status == 1:
parts.append(t("bus.state_ok"))
if self._last_error:
parts.append(f"{t('status.last_error')}: {self._last_error}")