From e556c131bf6a63f4a5bb9a733619cbd28b41fbc8 Mon Sep 17 00:00:00 2001 From: yijian Date: Tue, 29 Sep 2026 17:41:11 +0800 Subject: [PATCH] fixed axis 4 warning --- apps/autocal/config/bas/ECAT_511.bas | 112 ++++++++++++++++++ apps/autocal/config/locales/en_US.json | 2 + apps/autocal/config/locales/zh_CN.json | 2 + .../src/orisys_autocal/motion/models.py | 19 ++- .../src/orisys_autocal/motion/service.py | 17 ++- .../src/orisys_autocal/ui/bus_panel.py | 14 ++- .../src/orisys_autocal/ui/status_strip.py | 6 + apps/autocal/src/orisys_autocal/ui/window.py | 4 +- 8 files changed, 171 insertions(+), 5 deletions(-) create mode 100644 apps/autocal/config/bas/ECAT_511.bas diff --git a/apps/autocal/config/bas/ECAT_511.bas b/apps/autocal/config/bas/ECAT_511.bas new file mode 100644 index 0000000..5de9b9b --- /dev/null +++ b/apps/autocal/config/bas/ECAT_511.bas @@ -0,0 +1,112 @@ + +'*******************************************************ECAT总线初始化 +''''''''' + +global CONST BUS_TYPE = 0 '总线类型。用于上位机区分当前模式 +global CONST MAX_AXISNUM = 32 '最大轴数 +global CONST Bus_Slot = 0 '槽位号0 +global CONST Bus_AxisStart = 0 '总线轴起始轴号 + +global Bus_InitStatus '总线初始化完成状态 +Bus_InitStatus = -1 +global Bus_TotalAxisnum '检查扫描的总轴数 + +delay(3000) '延时3S等待驱动器上电 +'**********************初始化ECAT总线 +Ecat_Init() + +end + +global sub Ecat_Init() + for i=0 to MAX_AXISNUM - 1 '初始化还原轴类型 + AXIS_ENABLE(i) = 0 + atype(i)=0 + next + + Bus_InitStatus = -1 + Bus_TotalAxisnum = 0 + SLOT_STOP(Bus_Slot) + delay(200) + slot_scan(Bus_Slot) '开始扫描 + if return then + + ?"总线扫描成功","连接设备数:"NODE_COUNT(Bus_Slot) + ? + ?"开始映射轴号" + for i=0 to NODE_COUNT(Bus_Slot)-1 '遍历总线下所有从站节点 + + if NODE_AXIS_COUNT(Bus_Slot,i) <>0 then '判断当前节点是否有电机 + for j=0 to NODE_AXIS_COUNT(Bus_Slot,i)-1 + AXIS_ADDRESS(Bus_AxisStart+i)=Bus_TotalAxisnum+1 '映射轴号 + ATYPE(Bus_AxisStart+i)=65 '设置控制模式 65-位置 66-速度 67-转矩 + DRIVE_PROFILE(Bus_AxisStart+i)= 4 '设置PROFILE功能 + disable_group(Bus_AxisStart+i) '每轴单独分组 + + DRIVE_IO(Bus_AxisStart+i) = 128 + (Bus_AxisStart+i)*16 '映射驱动器上的IO状态 + REV_IN(Bus_AxisStart+i) = 128 + (Bus_AxisStart+i)*16 + FWD_IN(Bus_AxisStart+i) = 129 + (Bus_AxisStart+i)*16 + DATUM_IN(Bus_AxisStart+i) = 130 + (Bus_AxisStart+i)*16 + INVERT_IN(128 + (Bus_AxisStart+i)*16,ON) + INVERT_IN(129 + (Bus_AxisStart+i)*16,ON) + INVERT_IN(130 + (Bus_AxisStart+i)*16,ON) + + Bus_TotalAxisnum=Bus_TotalAxisnum+1 '总轴数+1 + next + endif + + next + + + ' Axis 4 is a Panasonic MINAS A6 servo, not a Leadshine CL3B stepper: + ' its limit/datum inputs are active-high as the ZMC reads them, so they + ' must NOT be inverted like the steppers above. Override the loop for + ' that one axis only: Bus_TotalAxisnum-1 is the last axis the scan found, + ' which on this machine is the servo and never a stepper. + INVERT_IN(128 + (Bus_AxisStart + Bus_TotalAxisnum - 1)*16,OFF) + INVERT_IN(129 + (Bus_AxisStart + Bus_TotalAxisnum - 1)*16,OFF) + INVERT_IN(130 + (Bus_AxisStart + Bus_TotalAxisnum - 1)*16,OFF) + ?"轴号映射完成","连接总轴数:"Bus_TotalAxisnum + wa 2000 + + SLOT_START(Bus_Slot) '启动总线 + if return then + ?"总线开启成功" + + ?"开始清除驱动器错误(根据驱动器数据字典设置)" + for i= Bus_AxisStart to Bus_AxisStart + Bus_TotalAxisnum - 1 + + DRIVE_CONTROLWORD(i)=128 '根据驱动器数据字典 + wa 100 + DRIVE_CONTROLWORD(i)=6 + wa 100 + DRIVE_CONTROLWORD(i)=15 + wa 100 + next + + ?"驱动器错误清除完成" + wa 100 + + ?"清除控制器错误" + datum(0) + DRIVE_CLEAR(0) + ?"控制器错误清除完成" + wa 100 + + ?"轴使能准备" + for i= Bus_AxisStart to Bus_AxisStart + Bus_TotalAxisnum - 1 + base(i) + AXIS_ENABLE=1 + next + wdog=1 '使能总开关 + Bus_InitStatus = 1 + ?"轴使能完成" + else + ?"总线开启失败" + Bus_InitStatus = 0 + endif + + else + ?"总线扫描失败" + Bus_InitStatus = 0 + endif +end sub \ No newline at end of file diff --git a/apps/autocal/config/locales/en_US.json b/apps/autocal/config/locales/en_US.json index 9356f18..aebcfb5 100644 --- a/apps/autocal/config/locales/en_US.json +++ b/apps/autocal/config/locales/en_US.json @@ -103,6 +103,8 @@ "state_pending": "Not started", "state_running": "Initialising鈥�", "state_ok": "Initialised", + "state_unverified": "Init not confirmed", + "unverified_hint": "The controller still holds the initialisation flag from its last run; this power-up has not re-scanned the bus. If the platform will not move, download the BAS file and re-initialise.", "state_failed": "Initialisation failed", "state_timeout": "Initialisation timed out", "nodes": "Nodes", diff --git a/apps/autocal/config/locales/zh_CN.json b/apps/autocal/config/locales/zh_CN.json index d7fadeb..edaf80a 100644 --- a/apps/autocal/config/locales/zh_CN.json +++ b/apps/autocal/config/locales/zh_CN.json @@ -103,6 +103,8 @@ "state_pending": "鏈畬鎴�", "state_running": "姝e湪鍒濆鍖栤��", "state_ok": "鍒濆鍖栨垚鍔�", + "state_unverified": "鏈‘璁ゅ垵濮嬪寲", + "unverified_hint": "鎺у埗鍣ㄤ繚鐣欎簡涓婃鐨勬�荤嚎鍒濆鍖栨爣蹇楋紝鏈寮�鏈哄皻鏈噸鏂版壂鎻忥紱濡傛棤娉曡繍鍔紝璇峰厛涓嬭浇 BAS 鏂囦欢骞堕噸鏂板垵濮嬪寲銆�", "state_failed": "鍒濆鍖栧け璐�", "state_timeout": "鍒濆鍖栬秴鏃�", "nodes": "鑺傜偣鏁伴噺", diff --git a/apps/autocal/src/orisys_autocal/motion/models.py b/apps/autocal/src/orisys_autocal/motion/models.py index 0c9a79a..a4f5289 100644 --- a/apps/autocal/src/orisys_autocal/motion/models.py +++ b/apps/autocal/src/orisys_autocal/motion/models.py @@ -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: diff --git a/apps/autocal/src/orisys_autocal/motion/service.py b/apps/autocal/src/orisys_autocal/motion/service.py index 95c1739..1920214 100644 --- a/apps/autocal/src/orisys_autocal/motion/service.py +++ b/apps/autocal/src/orisys_autocal/motion/service.py @@ -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, ) diff --git a/apps/autocal/src/orisys_autocal/ui/bus_panel.py b/apps/autocal/src/orisys_autocal/ui/bus_panel.py index 353cc70..46d416e 100644 --- a/apps/autocal/src/orisys_autocal/ui/bus_panel.py +++ b/apps/autocal/src/orisys_autocal/ui/bus_panel.py @@ -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 diff --git a/apps/autocal/src/orisys_autocal/ui/status_strip.py b/apps/autocal/src/orisys_autocal/ui/status_strip.py index 12568db..a8d5d48 100644 --- a/apps/autocal/src/orisys_autocal/ui/status_strip.py +++ b/apps/autocal/src/orisys_autocal/ui/status_strip.py @@ -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") diff --git a/apps/autocal/src/orisys_autocal/ui/window.py b/apps/autocal/src/orisys_autocal/ui/window.py index 7d17520..a18d269 100644 --- a/apps/autocal/src/orisys_autocal/ui/window.py +++ b/apps/autocal/src/orisys_autocal/ui/window.py @@ -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}")