wrps-demo-kit/03-plc/as-built/strucpp_runtime/include/iec_global.hpp
Clio Liu 55279ca78f docs(plc): as-built copy, versions, and a getting-started guide
Adds what the folder was missing for someone picking it up cold.

as-built/  the STruC++ output copied out of the live container - the C++
           the PLC is actually executing, plus program.st and the Modbus
           buffer config. The running image was made with docker commit
           and exists in no registry, so this is the only other copy of
           the compiled form.

           Verified while copying: the deployed program.st matches
           build/wrps.st generated from src/. Identical POU structure,
           ZERO differences in non-declaration lines. src/ is genuinely
           canonical and the running PLC agrees with it.

VERSIONS.md         every version read from the running system, not from
                    documentation: runtime v4.1.10, STruC++ 0.6.2, Editor
                    4.2.11, Debian 12, g++ 12.2, pymodbus 3.11.2, CI
                    Server R1.03. Plus which plugins are enabled -
                    ethercat is on for no reason - and a v3-vs-v4 table,
                    since most OpenPLC guidance online is for v3 and the
                    %MW HR1024 change silently produces wrong data.

GETTING-STARTED.md  three questions answered in a page: how to modify the
                    program, how to stand up a new PLC container from
                    scratch, how to move this one. Includes the compose
                    file, the Editor steps that are not generated and are
                    always missed, and a symptom-to-document table.
2026-09-02 16:48:30 +10:00

111 lines
4.5 KiB
C++

// ============================================================================
// WARNING: KEEP THIS HEADER C++14-CLEAN (see iec_var.hpp for the full rationale).
// ----------------------------------------------------------------------------
// This header is included by strucpp's emitted code, which on some Arduino cores
// (mbed-based: Nano RP2040/33 BLE, Opta, GIGA, Portenta, Edge) is compiled under
// `-std=gnu++14`. Do NOT use C++17/20 features unguarded (no `if constexpr`, no
// `std::trait_v`, no inline variables, no <optional>/<variant>/<string_view>).
// `auto` return-type deduction and generic/`decltype` trailing returns are C++14
// and OK. The <mutex> include + all locks are `#ifdef STRUCPP_THREADED`, so the
// baremetal (Arduino) build never pulls them in. Codegen emits with_lock()
// lambdas with CONCRETE parameter types (not `auto*`) to stay portable.
// ============================================================================
/**
* @file iec_global.hpp
* @brief Shared-global wrapper (value + per-global mutex) for VAR_GLOBAL /
* VAR_EXTERNAL.
*
* A CONFIGURATION `VAR_GLOBAL` is emitted as a `GlobalVar<V>` MEMBER of the
* configuration — one object bundling the canonical storage (`value`, the real
* IEC type, e.g. `IEC_BOOL` or a function-block type) with that global's own
* mutex. A PROGRAM's `VAR_EXTERNAL` reference is emitted as a plain
* `GlobalVar<V>*` pointing at that single canonical object. There is exactly one
* mutex per global (it lives on the canonical); locking "through the external
* pointer" locks the shared canonical.
*
* - NON-THREADED (baremetal / single task): the mutex and locks compile out;
* accesses go straight to `value`. Zero overhead.
*
* - STRUCPP_THREADED (openplc-runtime v4): every access (read / write / field
* / FB call) is serialized on the global's own mutex — fine-grained, so a
* lock is held for exactly one access and never nested with another global's
* lock (deadlock-free). Contract: per-access validity (no torn read/write);
* conflicts resolve last-writer-in-time. Data-agnostic — scalars, structs,
* arrays, and FB instances all work, and different-field writes from
* different tasks all survive because they mutate the one shared object.
*
* read() returns the real IEC value type, so generated bodies and constrained
* std-lib templates (NOT, ADD, …) deduce the operand correctly — the wrapper is
* never the deduced operand. Forcing is preserved via the canonical's
* get()/set(); located globals additionally honor the image forced-slot bitmap.
*/
#ifndef STRUCPP_IEC_GLOBAL_HPP
#define STRUCPP_IEC_GLOBAL_HPP
#include "iec_var.hpp"
#ifdef STRUCPP_THREADED
#include <mutex>
#endif
namespace strucpp {
template <typename V>
class GlobalVar {
public:
/** Canonical storage — the real IEC value/instance. Public so the located
* binding and debug exports can reach it (e.g. `g.value.raw_ptr()`), and so
* with_lock() can hand out a pointer to it. */
V value;
GlobalVar() = default;
/** Forward an initial value to the underlying IEC type. */
template <typename T>
explicit GlobalVar(T init) : value(init) {}
// Non-copyable / non-movable: the mutex is; and a canonical global is a
// fixed configuration member that nothing copies.
GlobalVar(const GlobalVar&) = delete;
GlobalVar& operator=(const GlobalVar&) = delete;
/** Scalar read: returns the real IEC value type (forcing-aware),
* deduction-friendly. Only instantiated for scalar globals (codegen uses
* with_lock() for structs / arrays / FB instances). */
auto read() const {
#ifdef STRUCPP_THREADED
std::lock_guard<std::mutex> lg(mtx_);
#endif
return value.get();
}
/** Scalar write (forcing-aware via set()). */
template <typename T>
void write(T v) {
#ifdef STRUCPP_THREADED
std::lock_guard<std::mutex> lg(mtx_);
#endif
value.set(v);
}
/** Locked direct access to the canonical, for field / array-element
* reads-writes and function-block calls: `f` receives `V*` (a pointer to
* `value`) and runs under the global's lock. Returns whatever `f` returns
* (a field's real type on reads → deduction-friendly). */
template <typename F>
auto with_lock(F&& f) -> decltype(f(static_cast<V*>(nullptr))) {
#ifdef STRUCPP_THREADED
std::lock_guard<std::mutex> lg(mtx_);
#endif
return f(&value);
}
#ifdef STRUCPP_THREADED
private:
mutable std::mutex mtx_;
#endif
};
} // namespace strucpp
#endif // STRUCPP_IEC_GLOBAL_HPP