Skip to content

release: strucpp 0.6.0#196

Merged
thiagoralves merged 16 commits into
mainfrom
development
Jul 16, 2026
Merged

release: strucpp 0.6.0#196
thiagoralves merged 16 commits into
mainfrom
development

Conversation

@thiagoralves

Copy link
Copy Markdown
Contributor

Release 0.6.0 — SoftMotion SM3 layer, composite/array shared globals via per-global mutex, and IEC 61131-3 function-block VAR_EXTERNAL shared-global access.

Merges development into main for the v0.6.0 tag/release.

🤖 Generated with Claude Code

thiagoralves and others added 16 commits July 5, 2026 01:16
Bundle a native PLCopen SoftMotion (Light) library alongside
iec-standard-fb and oscat-basic, targeting CiA 402 servo drives.

Vendors the drive-side OpenSML blocks (GPL-3.0,
https://github.com/feecat/OpenSML) — Power/Home/ProfilePosition/
ProfileVelocity/Stop over an OpenSML_Axis CiA 402 reference struct —
and exposes canonical PLCopen MC_* wrappers (MC_Power, MC_Home,
MC_MoveAbsolute/Relative/Velocity, MC_Stop/Halt, MC_Reset,
MC_ReadActualPosition/Velocity, MC_ReadStatus). Position/velocity in
raw drive units for now.

Built via rebuild-libs.mjs (rebuildLibraryFromDisk, depends on
iec-standard-fb for TON) and mirrored to the vscode extension. Users
reference the blocks by bare name via bundled-lib auto-discovery;
tree-shaking emits only used chunks.

Validated ST -> C++ -> g++ end to end; new library test plus the full
library suite (446) pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Complete the plcopen-softmotion library with controller-side
interpolation. Adds the S7RTT single-axis 7-segment S-curve online
trajectory generator (Apache-2.0, https://github.com/feecat/S7RTT),
converted from PLCopen XML to ST (FB_S7RTT_OTG, FB_S7RTT_Plan, the
FC_S7RTT_* helpers, and the ST_MotionState/ST_MotionLimits structs),
plus the OpenSML Cyclic-Synchronous-Position/Velocity blocks and the
OpenSML_AxisController over an OpenSML_Control aggregate.

Fixes two latent OpenSML issues surfaced during porting:
- OpenSML_SyncPosition referenced xSimulation without declaring it
- OpenSML_AxisController used dangling `=>`(unconnected) outputs

Library now ships 21 FBs + 3 functions + 4 types. Validated ST -> C++
-> g++ end to end, plus a runtime numeric test: a point-to-point OTG
move converges to target while respecting the velocity limit.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Design for CODESYS-like ESI-XML import that binds a CiA 402 servo to an
OpenSML_Axis with no manual %I/%Q mapping. Documents Path A (glue-based,
ships on today's compiler via generated located globals + a per-axis
link POU in openplc-editor; runtime unchanged) and Path B (compiler
struct-binding follow-up that removes the glue), plus units/scaling and
DC-sync considerations and a recommended sequencing.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Rewrite the EtherCAT plan around porting CODESYS SoftMotion projects
with minimal source changes. Key finding: OpenSML == CODESYS SoftMotion
Light (SML) architecturally, and S7RTT covers full-SM3 controller-side
csp — so the motion engine already exists; the remaining work is a
CODESYS-shaped compatibility layer (AXIS_REF_SML/SM3 types, exact MC_*
signatures, LREAL units + scaling, enums, a per-axis drive-bridge FB
modeled on SM_Drive_ETC_GenericDSP402, PLCopen state machine).

Agreed direction: SML tier first (near-direct over OpenSML, matches the
CODESYS SML_CompatibilityCheck_DS402 example), then full SM3 via S7RTT;
full LREAL units + scaling from the start; EtherCAT device name = axis
name with compile-time global + bridge generation. CODESYS .project
import documented as a separate later workstream (autonomy-edge).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Research verdict: CODESYS SoftMotion application code only passes the
axis to MC_* blocks and reads/writes fields — axis methods/properties
are all driver-internal — so AXIS_REF_SM3 can be a plain STRUCT passed
by VAR_IN_OUT reference. Adopt unsuffixed CODESYS names (AXIS_REF_SM3,
MC_Power, …) for lowest porting friction; back them drive-side first,
upgrade to S7RTT csp later.

Add the ranked common block set, the practical axis-field subset, and
the behavioral-fidelity gotchas the bridge must honor (call-every-cycle,
Execute:=FALSE doesn't stop, Aborting buffer default, command vs
RealState, MC_Power drives the PLCopen state machine, unit scaling).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…x FB inout copy-back

Phase 1 of the SoftMotion integration: a CODESYS SoftMotion (SM3)
compatibility layer over the OpenSML/S7RTT engine so CODESYS single-axis
projects port unmodified.

Library (libs/sources/plcopen-softmotion/):
- sm3_enums.st: SMC_AXIS_STATE, SMC_CONTROLLER_MODE, MC_DIRECTION,
  MC_BUFFER_MODE, SMC_HOMING_MODE, SMC_ERROR (single-axis subset)
- sm3_axis_ref.st: AXIS_REF_SM3 struct (CODESYS field names, LREAL
  technical units, increments<->units scaling, embedded raw CiA402 Drive)
- sm3_drive.st: SM_Drive_GenericDS402 — generic CiA402 drive bridge
  (analogue of CODESYS SM_Drive_ETC_GenericDSP402): PDO<->axis, scaling,
  PLCopen state derivation
- sm3_mc_blocks.st: CODESYS-signature MC_Power/MoveAbsolute/MoveRelative/
  MoveVelocity/Halt/Stop/Reset/Home/ReadStatus/ReadActualPosition/
  ReadActualVelocity/ReadAxisError (LREAL units) over the OpenSML engine
- removes the earlier raw-unit mc_wrappers.st (superseded)

Compiler fix (src/backend/codegen.ts):
- FB VAR_IN_OUT params were copied IN at the call site but never copied
  BACK, silently discarding a callee's mutations — so no MC_* block could
  modify its axis. Now emits the matching copy-out after the call
  (true inout semantics), including nested struct-field inouts. A later
  strucpp branch replaces this copy-in/out with by-reference pointers.

Tests: SM3 manifest + compile + runtime drive-simulation (enable sequence
+ scaled absolute move reaches 250000 inc / 250.0 u) + OTG convergence;
codegen copy-back regressions (scalar/struct/nested). Full suite green
(1979 passed).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ut copy-back)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…bodies

Composite VAR_GLOBAL/VAR_EXTERNAL (structs, arrays, FB instances) were
declarable but any in-body access threw ("not yet supported"). This
blocked the CODESYS SoftMotion pattern where an AXIS_REF_SM3 global is
shared between its drive bridge and the MC_* blocks.

Now a composite external is accessed directly through its canonical
value behind the GlobalVar pointer (`g->value`, `g->value.field`,
`g->value.field.N` for bit access, and as an FB VAR_IN_OUT arg with the
copy-in/copy-out pair). The per-global mutex is intentionally bypassed
for composites: they are shared within a single (bus-cycle) task, e.g. a
SoftMotion axis driven by its bridge and MC_* blocks in one scan.
Cross-task composite sharing (a lock spanning a whole field/call access)
remains future work. Scalar shared globals keep the mutex read()/write().

Updates the two compile tests that asserted the old fail-loud behaviour
to assert the generated `->value` access. Full suite green (1979).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ith_lock)

Replace the direct ->value shortcut for composite shared globals with
proper mutex-locked access through GlobalVar::with_lock — the same
per-global-mutex mechanism scalar globals already use, now generically
for any IEC 61131-3 type (struct / array / array-of-struct / FB) held in
a VAR_GLOBAL and reached via VAR_EXTERNAL. No copy-in/out or sync
machinery: codegen simply takes the global's own mutex on access.

- Reads: G->with_lock([&](auto* p){ return p-><path>; }) — a field/
  element read copies only that sub-value; self-contained (acquire →
  return → release), so several composite-global reads in one expression
  are sequential, never nested.
- Writes: the RHS is hoisted to a temp first, then
  G->with_lock([&](auto* p){ p-><path> = tmp; }); bit writes do the
  read-modify-write inside the one lock (atomic). Invariant: at most one
  global lock held at a time — no deadlock.
- FB VAR_IN_OUT of a composite global: locked whole-value read (copy-in)
  + locked write (copy-out); the by-value inout copy itself is the
  separate VAR_IN_OUT mechanism.

The lock lives entirely inside GlobalVar::with_lock (#ifdef
STRUCPP_THREADED), so non-threaded/baremetal builds compile it out — zero
overhead, no <mutex>. GlobalVar<V> is unchanged (already generic).
Factored the access-chain rendering into renderAccessTail so the same
logic renders onto the lock lambda's pointer. strucpp only; no runtime
change. Tests: struct/array/bit/FB-inout access + a two-task shared
composite global compiling under -DSTRUCPP_THREADED and without. Full
suite green (1984).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…through write()

Capturing an FB output via `=>` into a VAR_EXTERNAL scalar emitted
`g->read() = value;`. Shared-global scalars are pointers whose value is
read via `->read()` (an rvalue) and written via `->write()`, so assigning
to `->read()` failed to compile with "lvalue required as left operand of
assignment".

emitCaptureToLvalue now mirrors the scalar-external branch of
generateAssignmentStatement: a plain (non-field, non-deref) VAR_EXTERNAL
target routes the write through `name->write(value)`. Composite externals
(already handled via with_lock) and locals are unchanged.

Surfaced by the generated SoftMotion drive bridge, whose command outputs
(wControlWord, diTargetVelocity, iTargetTorque, …) are captured with `=>`
into located scalar globals bound to the CiA 402 PDO addresses.

Adds a g++-compiled regression test asserting the bridge pattern emits
`->write(` and never `->read() =`.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…(IEC 61131-3)

An FB's VAR_EXTERNAL was silently code-generated as a plain member — a private
copy the FB mutated in isolation, never touching the real global — and was never
validated against the project's globals. IEC 61131-3 permits a function block
(unlike a function) to access configuration globals via VAR_EXTERNAL.

Storage: configuration VAR_GLOBALs are now emitted as file-scope
`inline GlobalVar<V>` singletons (one per name) instead of configuration-class
members, so the single canonical storage + mutex is reachable from every POU at
any nesting depth. Programs are unchanged — the configuration still injects the
pointer via the constructor, now handing over the file-scope address (same-name
resolution keeps instantiation/located/retain code identical).

Function blocks: VAR_EXTERNAL is emitted as a `GlobalVar<V>*` bound in the FB's
own constructor to `&<ns>::<name>` (self-bind — file-scope visibility means no
pointer threading through containers), and body access is rewritten through the
pointer (->read()/->write()/with_lock), exactly like a program. Validation now
checks FB externals too and anchors the "no matching VAR_GLOBAL" diagnostic at
the declaration (file+line) instead of emitting it file-less; functions remain
excluded per the standard.

Adds a g++ compile+run regression proving an FB mutates the SHARED global
(threaded + non-threaded) and a located FB-external diagnostic test.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…fig prefix)

Config VAR_GLOBALs moved to file-scope singletons, so the debug pointer table
must address them directly (`&NAME.value`) rather than through the configuration
instance (`&g_config.NAME.value`), which no longer has those members. Also
dedupes by name to match the file-scope emission. Fixes a g++ error in
generated_debug.cpp ("class Configuration_X has no member named …").

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
feat: bundle PLCopen SoftMotion library (OpenSML + S7RTT)
@thiagoralves
thiagoralves merged commit ba7439b into main Jul 16, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant