# Orisys AutoCal Internal control application for the **5-axis calibration platform**, driving a ZMotion ZMC bus controller (EtherCAT or RTEX) from the PC. Replaces the vendor example program it was ported from: D:\orisys_ws\zmotion\Python64\例程9-总线控制运动 > **Internal use only.** AutoCal is not part of the customer installer. See > "Do not ship this app" below. ## What it does - **Connect** over Ethernet (with an IP scan), serial, PCI, local/simulated, or a built-in **demo** transport that needs no hardware and no DLL. - **Initialise the bus** by downloading `ECAT.bas` or `RTEX.bas` to the controller, then follow the controller's own state machine to completion — or to a timeout, with the vendor's diagnostic hint. - **Read axis state** for all five drives at once: enable, axis type, pulse equivalent, speed, acceleration, commanded and measured position (in user units *and* millimetres), axis status word, motion and homing state. - **Move**: enable/disable, jog, absolute and relative moves, stop, stop-all, driver homing, clear a drive alarm. Every command is guarded — see below. - **Exchange raw bus data**: EtherCAT object-dictionary (SDO) reads and writes, and RTEX parameters. Each family is enabled only when the controller reports that bus type. ## Run it PySide6 only — Qt's LGPLv3 binding, so an internal closed-source tool needs no licence purchase. (`PyQt5`, which the studio app uses, is GPLv3-or-commercial.) ```powershell # with the environment that already has PySide6 (Anaconda py310, for example) $env:PYTHONPATH = "D:\orisys_ws\orisys-app\shared\src;D:\orisys_ws\orisys-app\apps\autocal\src" python D:\orisys_ws\orisys-app\apps\autocal\run.py ``` Or with no controller attached at all: ```powershell python .\apps\autocal\run.py --demo ``` `run.py` sets `ORISYS_APP_ID=autocal` before importing anything from `orisys_app_shared`, so this app's `config/`, `preference.json` and `outputs/` resolve to `apps/autocal/` rather than the studio's. ### Controller library The ZMotion runtime is vendored at `shared/resources/zmotion/x64/` (see the README beside it for provenance). Point `ORISYS_ZMOTION_DLL_DIR` at another directory to use a different build — on a motion controller, silently loading the wrong runtime version is the kind of fault that looks like a working machine until it moves, so the override is authoritative: if it does not contain `zauxdll.dll`, the app refuses to connect instead of quietly falling back. ## Safety model The guards live in `motion/service.py`, not in the UI, so they hold whatever calls them: - **Motion is refused** while the bus is not initialised, while the axis is unknown, or on a non-finite target — with a reason, never a silent no-op. - **Homing needs a configured mode.** The vendor example pre-fills `17`, which is one specific drive's mode; AutoCal starts at `0` and refuses, so the operator chooses for the drive in front of them instead of inheriting a number from another machine. - **Homing is refused on RTEX**, as the vendor example itself does. - **Disconnect stops the axes first** (the vendor program closes the handle and leaves the platform coasting). - **SDO/RTEX are gated by the reported bus type**, so a write can never be aimed at the wrong family. - **A failed read is marked, not zeroed.** `ZmotionValue.value_or_none` returns `None` on failure; nothing silently treats a failed read's zero as a position. The displayed millimetre value is *derived* from the pulse equivalent (bench-measured: `units=10` → 1000 user units = 2 mm). It is a display value, not a calibrated length, and the UI says so. ## Layout ```text apps/autocal/ run.py # source-tree launcher config/ preferences.default.json # shipped defaults locales/{zh_CN,en_US}.json bas/{ECAT,RTEX}.bas # copies: the vendor needs a non-Chinese path src/orisys_autocal/ main.py # logging, locale, theme, window preferences.py # AutoCal's own preference schema motion/ # Qt-free except poller.py (the Qt worker bridge) models.py errors.py units.py service.py transport.py poller.py ui/ # window.py + one module per panel tools/ # panel_check.py, panel_snapshot.py tests/ ``` `motion/poller.py` is the only place a controller call can originate. It owns the DLL handle on its own thread, so the vendor library's blocking calls (a ROM write, a bus scan) never freeze the window and the ctypes handle is never shared between threads. The UI talks to it through signals only. ## Verify Both work with no display, no controller and no DLL: ```powershell python .\apps\autocal\tools\panel_check.py # 120 layout/theme/locale/gating checks python .\apps\autocal\tools\panel_snapshot.py --demo --theme dark --out shot.png python .\apps\autocal\tools\panel_snapshot.py --compare --out tabs.png # one image per tab python .\apps\autocal\tools\layout_mockup.py --out layout.png # proposed layout, drawn python -m pytest apps\autocal\tests # offscreen suite ``` `layout_mockup.py` is a **wireframe, not the application**: it paints the proposed single-page layout with the shared theme tokens and imports nothing from the app, so the layout can be reviewed and marked up without building the panels. It draws the full page plus previews of the other workspace tabs in one canvas. `panel_check.py` builds the real window in both themes at 1280×720, 1600×900 and 1920×1080, asserts that no widget escapes its parent, that motion is unreachable before the bus is initialised, that the SDO/RTEX panels follow the reported bus family, and then runs a full simulated session from `ECAT.bas`. The suite is written for **PySide6**. Where PySide6 is missing, the test modules are skipped rather than failed: a broken environment should not look like a broken application. `tests/test_binding_purity.py` enforces the binding policy statically — no module may import PyQt5/PySide2/PyQt6, no module may use an unscoped Qt enumerator (removed in PySide6), `shared/` may import no binding at all, and the controller interface must stay importable headless. ## Do not ship this app AutoCal is an internal tool. It is deliberately **not** packaged by `build/studio`, whose PyInstaller spec must not learn about the ZMotion runtime DLLs, and it is not in the customer installer. The ZMotion SDK is vendor-supplied hardware support software with its own terms; do not redistribute those DLLs with a customer product.