Extract the shared _export_actions builder from add_export_actions so the same dialog/file-naming/collection logic can target either a toolbar (DA-12, DA-07) or a QMenu (for toolbars on a width budget). The builder's docstring documents that the handlers must stay closures: a closure slot is stored strongly by the connection, so the actions anchor the parent window's Python wrapper for the life of the C++ action - bound-method slots are stored weakly, and rewiring them as window methods lets an otherwise-unreferenced window be GC'd mid-test, cascading C++ deletion into child dialogs. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
CIMTechniques Service Suite
A modern Python 3 + PySide6 rebuild of CIMTechniques' legacy VB6 instrument tools — a unified, signed, single-install application for configuring and monitoring CIMTechniques sensor stations over serial / RS-232 during production and in the field.
The suite is a monorepo: a shared core package, a shell launcher, and one module
per instrument family. You install, sign, and update one app.
| Module | Instrument | Status |
|---|---|---|
| DA-12 | DA-12 Monitoring Station | Functionally complete against simulator; hardware verification pending |
| DA-07 ("eLink") | DA-07 station (Station → Devices → Channels) | Functionally complete against simulator; hardware verification pending |
| IOModbus | Config-driven Modbus RTU master (bundled device catalog) | Functionally complete against simulator; hardware verification pending |
All three modules are rebuilt from their original VB6 baselines (preserved read-only
under cim_suite/modules/<id>/legacy/). The outstanding milestone is verification
against real DA-12, DA-07, and Modbus hardware.
Quick start
Requires Windows and Python 3.11+.
# Setup
py -m venv .venv
.venv\Scripts\python -m pip install -e ".[dev]"
# Run against the in-memory simulator (no hardware needed — the normal dev path)
.venv\Scripts\python -m cim_suite.shell.app --module da12 --simulate
.venv\Scripts\python -m cim_suite.shell.app --module da07 --simulate
.venv\Scripts\python -m cim_suite.shell.app --module iomodbus --simulate
# Run against real hardware on a serial port
.venv\Scripts\python -m cim_suite.shell.app --module da12 --port COM3
# Open the suite launcher (module card grid)
.venv\Scripts\python -m cim_suite.shell.app
The simulator (SimulatedStation) speaks the real wire protocol, so the whole app —
and the full test suite — runs headless with no instrument attached.
Development
# Tests (headless; UI tests run offscreen via pytest-qt)
.venv\Scripts\python -m pytest -q
# Lint
.venv\Scripts\python -m ruff check cim_suite tests
A green pytest and a clean ruff are both part of "done" here.
Building a standalone app
# One-folder executable (PyInstaller)
.venv\Scripts\pyinstaller --noconfirm --distpath packaging\dist --workpath packaging\build packaging\suite.spec
# Windows installer (requires Inno Setup 6). Pass the version so the installer and
# Add/Remove Programs match the app — it's single-sourced from pyproject.toml:
$v = .venv\Scripts\python -c "import cim_suite; print(cim_suite.__version__)"
& "C:\Program Files (x86)\Inno Setup 6\ISCC.exe" /DAppVersion=$v packaging\installer.iss
Versioning & releases
The suite uses Semantic Versioning (MAJOR.MINOR.PATCH). There
are two records of change, with different audiences:
- Git history — the technical record. Every commit uses the
Conventional Commit format
<type>: <description>. CHANGELOG.md— the human-facing record: plain-language release notes for the (non-technical) people who use the app. It's also shown in-app via the What's new button on the start page.
| Commit type | Meaning | Version bump | In changelog? |
|---|---|---|---|
feat |
A new user-facing capability | minor | Yes (Added / Changed) |
fix |
A bug fix | patch | Yes (Fixed) |
feat! / BREAKING CHANGE: in body |
Removes or breaks existing behavior | major | Yes (Changed / Removed) |
docs, style, refactor, perf, test, build, ci, chore, revert |
Internal work | none | Collapsed to one "maintenance" line, or dropped |
# One-time on a fresh clone: install the commit-message validation hook
.venv\Scripts\python scripts\setup_hooks.py
# Bump the version (deterministic; edits pyproject.toml only). --dry-run to preview.
.venv\Scripts\python scripts\bump.py --dry-run
.venv\Scripts\python scripts\bump.py # auto-detect from commits
.venv\Scripts\python scripts\bump.py --minor # or force a bump level
# Draft release notes: in Claude Code run /release-notes (collects commits, drafts
# plain-language entries for your review, and only writes CHANGELOG.md once approved).
# Under the hood it calls:
.venv\Scripts\python scripts\release_notes.py --json
The version string lives in one place — [project] version in pyproject.toml;
cim_suite.__version__ reads from it, and the installer takes it via /DAppVersion.
The bump level is computed from commit prefixes, never by an LLM. The changelog prose
is the only LLM-assisted step and is never committed without review. Releases start from
the last vX.Y.Z git tag (or a --baseline <rev> you pass when there's no tag yet).
Cutting a release (order matters — draft notes before bumping, so the suggested version is correct):
/release-notesin Claude Code → review/approve the draft → it writesCHANGELOG.md.python scripts\bump.py→ updatespyproject.toml.git commit -am "chore: release X.Y.Z"(the release commit itself doesn't bump).git tag vX.Y.Zgit push --follow-tags(pushes the commit and the new tag together).
Architecture
Five strictly-ordered layers. The protocol core is pure (no I/O, no Qt) and the device is swappable — this is the central discipline, not an accident.
| Layer | Package | Role |
|---|---|---|
| Shared core | cim_suite/core |
Protocol primitives, transport interface + pyserial reader, theme/design system, config, export engine |
| Protocol (pure) | cim_suite/modules/<id>/protocol |
Framing, decode (frame → dataclass), encode (command → wire string). No I/O; fully unit-tested |
| Transport | cim_suite/modules/<id>/transport |
SimulatedStation in-memory fake; real serial transport lives in core |
| Domain | cim_suite/modules/<id>/domain |
In-memory models, calibration, logging, and the StationController hub |
| UI | cim_suite/modules/<id>/ui |
MainWindow + tabbed thin views that render models and call controller methods |
| Shell | cim_suite/shell |
Entry point, module registry, launcher landing page, suite window |
Data flows one way in, one way out, through the StationController:
transport (reader thread) → controller → framer → decode → models → *Changed signals → UI
UI edit → controller.set_* → encode → transport.write
Note: the modules deliberately do not share a protocol. DA-12 streams big-endian fixed-point
{...}frames unsolicited; DA-07 uses a little-endian, IEEE-754-float, checksummed~...\rdialect over a polled ACK handshake; IOModbus is a standard half-duplex Modbus RTU master driven by a bundled register-map catalog (no proprietary protocol of its own). Seedocs/SUITE-ARCHITECTURE.mdandCLAUDE.mdfor the full rationale.
Documentation
| Doc | What it covers |
|---|---|
CLAUDE.md |
Orientation + working conventions (the best starting point) |
docs/SUITE-ARCHITECTURE.md |
Monorepo / multi-module design |
docs/RUNNING.md |
Running the app |
docs/REBUILD-STATUS.md |
Point-in-time rebuild status |
docs/BACKLOG.md |
Live to-do list |
docs/HARDWARE-VERIFICATION.md |
Protocol details to confirm against real hardware |
docs/DESIGN-SYSTEM.md |
Suite-wide visual standard |
docs/EXPORT.md |
.xlsx export engine + adoption checklist |
docs/VB6-MIGRATION-PLAYBOOK.md |
Reverse-engineering traps from the VB6 port |
CHANGELOG.md |
Human-facing release notes (see Versioning & releases above) |
License
Proprietary — © CIMTechniques. All rights reserved.