wrps-demo-kit/03-plc/as-built/strucpp_runtime/include/iec_var.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

540 lines
20 KiB
C++

// SPDX-License-Identifier: GPL-3.0-or-later WITH STruCpp-runtime-exception
// Copyright (C) 2025 Autonomy / OpenPLC Project
// This file is part of the STruC++ Runtime Library and is covered by the
// STruC++ Runtime Library Exception. See COPYING.RUNTIME for details.
//
// ============================================================================
// WARNING: KEEP THIS HEADER C++14-CLEAN.
// ----------------------------------------------------------------------------
// strucpp targets C++17 and its EMITTED code is compiled as C++17 — but this
// header is part of the C/C++ Function Block include chain, which is NOT.
// OpenPLC Editor's Arduino flow emits `c_blocks_code.cpp`, including
// `iec_var.hpp` + `iec_string.hpp` (which transitively pull in `iec_traits.hpp`
// and `iec_types.hpp`). That translation unit is compiled under whatever
// `-std=` the Arduino core picks, and every mbed-based core — Nano RP2040
// Connect, Nano 33 BLE, Opta, GIGA, Portenta, Edge — hard-codes `-std=gnu++14`.
// So any C++17/20 construct reachable from here breaks the user's C/C++ POU
// build, even though the rest of strucpp is happily on C++17.
//
// In this header (and anything it includes) do NOT use C++17/20 features
// unguarded. In particular:
// * `std::trait_v<T>` -> `std::trait<T>::value`
// * `if constexpr` -> SFINAE / tag dispatch
// * inline variables / `inline constexpr`
// * `auto` non-type template params -> typed NTTPs
// * C++17/20 library headers (<optional>, <variant>, <string_view>,
// <concepts>, ...) -> include ONLY behind `#if __cplusplus >= ...`
// (see the guarded <concepts> block in iec_types.hpp for the pattern).
//
// Boundary introduced in commit be85d8a. If you change which headers
// `c_blocks_code.cpp` pulls in, update this set of warnings accordingly.
// ============================================================================
/**
* STruC++ Runtime - IEC Variable Wrapper
*
* This header defines the IECVar template class that wraps IEC types
* with support for variable forcing (a key OpenPLC feature).
*
* Located variables (AT %IX0.0, etc.) use this same wrapper, and the
* raw_ptr() method provides access to the underlying storage for
* runtime binding to I/O image tables.
*/
#pragma once
#include "iec_types.hpp"
#include <type_traits>
#include <utility>
namespace strucpp {
// Forward declaration for pointer-to-integer assignment
template<typename T> class IEC_Ptr;
// =============================================================================
// IEC Variable Wrapper
// =============================================================================
/**
* Template wrapper for IEC variables with forcing support.
*
* This class wraps any IEC type and provides:
* - Normal get/set operations
* - Variable forcing (override value for debugging/testing)
* - Implicit conversion for natural syntax
* - Arithmetic operators for numeric types
*
* @tparam T The underlying C++ type (e.g., int16_t for INT)
*/
template<typename T>
class IECVar {
public:
using value_type = T;
// =========================================================================
// Constructors
// =========================================================================
/** Default constructor - initializes to zero/false */
IECVar() noexcept : value_{}, forced_{false}, forced_value_{} {}
/** Construct with initial value (non-explicit to allow IEC_INT val = 10 syntax) */
IECVar(T v) noexcept : value_{v}, forced_{false}, forced_value_{} {}
/** Cross-type converting constructor: IECVar<SINT_t> → IECVar<INT_t> etc.
* Enables implicit widening when struct fields (now IECVar-wrapped) are passed
* to functions expecting a wider IECVar type. Without this, C++ would need
* two user-defined conversions (IECVar<U>→U→T→IECVar<T>) which is disallowed. */
template<typename U, std::enable_if_t<
std::is_convertible<U, T>::value && !std::is_same<U, T>::value, int> = 0>
IECVar(const IECVar<U>& other) noexcept
: value_{static_cast<T>(other.get())}, forced_{false}, forced_value_{} {}
/** Copy constructor — fresh IECVar starts unforced regardless of source. */
IECVar(const IECVar& other) noexcept
: value_{other.get()}, forced_{false}, forced_value_{} {}
/** Move constructor — same semantics as copy. */
IECVar(IECVar&& other) noexcept
: value_{other.get()}, forced_{false}, forced_value_{} {}
/**
* Copy assignment.
*
* Assigning FROM another IECVar must go through `set()` so forcing
* state is preserved on the destination. A memberwise copy would
* clobber `forced_` / `forced_value_`, silently unforcing variables
* that the debugger is holding — precisely what generated PLC code
* does every scan cycle with `BLINK := TOF0.Q`.
*/
IECVar& operator=(const IECVar& other) noexcept {
set(other.get());
return *this;
}
/** Move assignment — same semantics as copy. */
IECVar& operator=(IECVar&& other) noexcept {
set(other.get());
return *this;
}
// =========================================================================
// Value Access
// =========================================================================
/**
* Get the current value.
* Returns the forced value if forcing is active, otherwise the normal value.
*/
T get() const noexcept {
return forced_ ? forced_value_ : value_;
}
/**
* Set the value.
* If forcing is active, the set is ignored to ensure drivers reading
* the raw storage always see the forced value for output variables.
*/
void set(T v) noexcept {
if (!forced_) {
value_ = v;
}
}
/**
* Get the underlying value (ignoring forcing).
* Useful for debugging to see what the program would have set.
*/
T get_underlying() const noexcept {
return value_;
}
// =========================================================================
// Forcing Support
// =========================================================================
/**
* Force the variable to a specific value.
* While forced, get() will return the forced value regardless of set() calls.
* Also updates the raw storage so drivers reading via raw_ptr() see the forced value.
*/
void force(T v) noexcept {
forced_ = true;
forced_value_ = v;
value_ = v; // Update raw value so external readers (plugins) see forced value
}
/**
* Remove forcing and return to normal operation.
*/
void unforce() noexcept {
forced_ = false;
}
/**
* Check if the variable is currently forced.
*/
bool is_forced() const noexcept {
return forced_;
}
/**
* Get the forced value (only valid if is_forced() is true).
*/
T get_forced_value() const noexcept {
return forced_value_;
}
// =========================================================================
// Raw Pointer Access (for Located Variables)
// =========================================================================
/**
* Get a pointer to the underlying raw storage.
* Used by the runtime to bind located variables to I/O image tables.
* Plugins and drivers read/write through this pointer.
*
* For inputs: drivers write to this pointer, get() returns forced value when forced
* For outputs: force() updates this storage, so drivers always read the forced value
*/
T* raw_ptr() noexcept { return &value_; }
/**
* Get a const pointer to the underlying raw storage.
*/
const T* raw_ptr() const noexcept { return &value_; }
// =========================================================================
// Implicit Conversions
// =========================================================================
/** Implicit conversion to underlying type for natural syntax */
operator T() const noexcept {
return get();
}
/** Assignment from raw value */
IECVar& operator=(T v) noexcept {
set(v);
return *this;
}
/** Cross-type assignment: IECVar<SINT_t> → IECVar<INT_t> etc.
* Resolves ambiguity when assigning between different IECVar specializations
* by providing a direct match (template is preferred over two indirect paths
* that each require one user-defined conversion). */
template<typename U, std::enable_if_t<
std::is_convertible<U, T>::value && !std::is_same<U, T>::value, int> = 0>
IECVar& operator=(const IECVar<U>& other) noexcept {
set(static_cast<T>(other.get()));
return *this;
}
/** Assignment from IEC_Ptr (CODESYS: DWORD_VAR := PT stores address as integer).
* WARNING: On 64-bit platforms, assigning to types narrower than pointer width
* (e.g., DWORD) truncates the address. Use ULINT, LWORD, or PTR_INT_t for
* portable pointer-to-integer storage. */
template<typename U, typename V = T, std::enable_if_t<std::is_integral<V>::value, int> = 0>
IECVar& operator=(const IEC_Ptr<U>& ptr) noexcept {
set(static_cast<T>(ptr.to_addr()));
return *this;
}
/** Assignment from a raw pointer — stores the address as an integer.
* Used by the ADR(x) lowering `_TMP : __XWORD := &(x)`. Integral targets
* only; routed through uintptr_t so it is pointer-width-correct per
* target (no truncation when T is __XWORD/XWORD_t). */
template<typename U, typename V = T, std::enable_if_t<std::is_integral<V>::value, int> = 0>
IECVar& operator=(U* p) noexcept {
set(static_cast<T>(reinterpret_cast<std::uintptr_t>(p)));
return *this;
}
// =========================================================================
// Container Access Forwarding (for array/struct types)
// =========================================================================
/** Forward operator-> to underlying type (struct/FB member access) */
template<typename U = T, std::enable_if_t<std::is_class<U>::value, int> = 0>
T* operator->() noexcept { return &value_; }
template<typename U = T, std::enable_if_t<std::is_class<U>::value, int> = 0>
const T* operator->() const noexcept { return &value_; }
/** Forward operator[] to underlying type (1D array access) */
template<typename Index>
auto operator[](Index i) noexcept -> decltype(std::declval<T&>()[i]) {
return value_[i];
}
template<typename Index>
auto operator[](Index i) const noexcept -> decltype(std::declval<const T&>()[i]) {
return value_[i];
}
/** Forward operator() to underlying type (2D+ array access) */
template<typename... Args>
auto operator()(Args... args) noexcept -> decltype(std::declval<T&>()(args...)) {
return value_(args...);
}
template<typename... Args>
auto operator()(Args... args) const noexcept -> decltype(std::declval<const T&>()(args...)) {
return value_(args...);
}
// =========================================================================
// Arithmetic Operators
// =========================================================================
IECVar& operator+=(T v) noexcept {
set(get() + v);
return *this;
}
IECVar& operator-=(T v) noexcept {
set(get() - v);
return *this;
}
IECVar& operator*=(T v) noexcept {
set(get() * v);
return *this;
}
IECVar& operator/=(T v) noexcept {
set(get() / v);
return *this;
}
IECVar& operator%=(T v) noexcept {
set(get() % v);
return *this;
}
// Prefix increment
IECVar& operator++() noexcept {
set(get() + 1);
return *this;
}
// Postfix increment
IECVar operator++(int) noexcept {
IECVar tmp = *this;
++(*this);
return tmp;
}
// Prefix decrement
IECVar& operator--() noexcept {
set(get() - 1);
return *this;
}
// Postfix decrement
IECVar operator--(int) noexcept {
IECVar tmp = *this;
--(*this);
return tmp;
}
// =========================================================================
// Bitwise Operators (for bit string types)
// =========================================================================
IECVar& operator&=(T v) noexcept {
set(get() & v);
return *this;
}
IECVar& operator|=(T v) noexcept {
set(get() | v);
return *this;
}
IECVar& operator^=(T v) noexcept {
set(get() ^ v);
return *this;
}
private:
T value_; ///< The actual value
bool forced_; ///< Whether forcing is active
T forced_value_; ///< The forced value (when forced_ is true)
};
// =============================================================================
// Binary Operators
// =============================================================================
template<typename T>
inline IECVar<T> operator+(const IECVar<T>& a, const IECVar<T>& b) noexcept {
return IECVar<T>(a.get() + b.get());
}
template<typename T>
inline IECVar<T> operator-(const IECVar<T>& a, const IECVar<T>& b) noexcept {
return IECVar<T>(a.get() - b.get());
}
template<typename T>
inline IECVar<T> operator*(const IECVar<T>& a, const IECVar<T>& b) noexcept {
return IECVar<T>(a.get() * b.get());
}
template<typename T>
inline IECVar<T> operator/(const IECVar<T>& a, const IECVar<T>& b) noexcept {
return IECVar<T>(a.get() / b.get());
}
template<typename T, typename = std::enable_if_t<std::is_integral<T>::value>>
inline IECVar<T> operator%(const IECVar<T>& a, const IECVar<T>& b) noexcept {
return IECVar<T>(a.get() % b.get());
}
// Mixed-type arithmetic operators (IECVar<T> op T) and (T op IECVar<T>)
template<typename T> inline IECVar<T> operator+(const IECVar<T>& a, T b) noexcept { return IECVar<T>(a.get() + b); }
template<typename T> inline IECVar<T> operator+(T a, const IECVar<T>& b) noexcept { return IECVar<T>(a + b.get()); }
template<typename T> inline IECVar<T> operator-(const IECVar<T>& a, T b) noexcept { return IECVar<T>(a.get() - b); }
template<typename T> inline IECVar<T> operator-(T a, const IECVar<T>& b) noexcept { return IECVar<T>(a - b.get()); }
template<typename T> inline IECVar<T> operator*(const IECVar<T>& a, T b) noexcept { return IECVar<T>(a.get() * b); }
template<typename T> inline IECVar<T> operator*(T a, const IECVar<T>& b) noexcept { return IECVar<T>(a * b.get()); }
template<typename T> inline IECVar<T> operator/(const IECVar<T>& a, T b) noexcept { return IECVar<T>(a.get() / b); }
template<typename T> inline IECVar<T> operator/(T a, const IECVar<T>& b) noexcept { return IECVar<T>(a / b.get()); }
template<typename T, typename = std::enable_if_t<std::is_integral<T>::value>> inline IECVar<T> operator%(const IECVar<T>& a, T b) noexcept { return IECVar<T>(a.get() % b); }
template<typename T, typename = std::enable_if_t<std::is_integral<T>::value>> inline IECVar<T> operator%(T a, const IECVar<T>& b) noexcept { return IECVar<T>(a % b.get()); }
// =============================================================================
// Comparison Operators
// =============================================================================
template<typename T>
inline bool operator==(const IECVar<T>& a, const IECVar<T>& b) noexcept {
return a.get() == b.get();
}
template<typename T>
inline bool operator!=(const IECVar<T>& a, const IECVar<T>& b) noexcept {
return a.get() != b.get();
}
template<typename T>
inline bool operator<(const IECVar<T>& a, const IECVar<T>& b) noexcept {
return a.get() < b.get();
}
template<typename T>
inline bool operator>(const IECVar<T>& a, const IECVar<T>& b) noexcept {
return a.get() > b.get();
}
template<typename T>
inline bool operator<=(const IECVar<T>& a, const IECVar<T>& b) noexcept {
return a.get() <= b.get();
}
template<typename T>
inline bool operator>=(const IECVar<T>& a, const IECVar<T>& b) noexcept {
return a.get() >= b.get();
}
// Mixed-type comparison operators
template<typename T> inline bool operator==(const IECVar<T>& a, T b) noexcept { return a.get() == b; }
template<typename T> inline bool operator==(T a, const IECVar<T>& b) noexcept { return a == b.get(); }
template<typename T> inline bool operator!=(const IECVar<T>& a, T b) noexcept { return a.get() != b; }
template<typename T> inline bool operator!=(T a, const IECVar<T>& b) noexcept { return a != b.get(); }
template<typename T> inline bool operator<(const IECVar<T>& a, T b) noexcept { return a.get() < b; }
template<typename T> inline bool operator<(T a, const IECVar<T>& b) noexcept { return a < b.get(); }
template<typename T> inline bool operator>(const IECVar<T>& a, T b) noexcept { return a.get() > b; }
template<typename T> inline bool operator>(T a, const IECVar<T>& b) noexcept { return a > b.get(); }
template<typename T> inline bool operator<=(const IECVar<T>& a, T b) noexcept { return a.get() <= b; }
template<typename T> inline bool operator<=(T a, const IECVar<T>& b) noexcept { return a <= b.get(); }
template<typename T> inline bool operator>=(const IECVar<T>& a, T b) noexcept { return a.get() >= b; }
template<typename T> inline bool operator>=(T a, const IECVar<T>& b) noexcept { return a >= b.get(); }
// =============================================================================
// Bitwise Operators
// =============================================================================
template<typename T>
inline IECVar<T> operator&(const IECVar<T>& a, const IECVar<T>& b) noexcept {
return IECVar<T>(a.get() & b.get());
}
template<typename T>
inline IECVar<T> operator|(const IECVar<T>& a, const IECVar<T>& b) noexcept {
return IECVar<T>(a.get() | b.get());
}
template<typename T>
inline IECVar<T> operator^(const IECVar<T>& a, const IECVar<T>& b) noexcept {
return IECVar<T>(a.get() ^ b.get());
}
// Mixed-type bitwise operators
template<typename T> inline IECVar<T> operator&(const IECVar<T>& a, T b) noexcept { return IECVar<T>(a.get() & b); }
template<typename T> inline IECVar<T> operator&(T a, const IECVar<T>& b) noexcept { return IECVar<T>(a & b.get()); }
template<typename T> inline IECVar<T> operator|(const IECVar<T>& a, T b) noexcept { return IECVar<T>(a.get() | b); }
template<typename T> inline IECVar<T> operator|(T a, const IECVar<T>& b) noexcept { return IECVar<T>(a | b.get()); }
template<typename T> inline IECVar<T> operator^(const IECVar<T>& a, T b) noexcept { return IECVar<T>(a.get() ^ b); }
template<typename T> inline IECVar<T> operator^(T a, const IECVar<T>& b) noexcept { return IECVar<T>(a ^ b.get()); }
template<typename T>
inline IECVar<T> operator~(const IECVar<T>& a) noexcept {
return IECVar<T>(~a.get());
}
// =============================================================================
// IEC Type Aliases with Forcing Support
// =============================================================================
// Boolean
using IEC_BOOL = IECVar<BOOL_t>;
// Bit strings
using IEC_BYTE = IECVar<BYTE_t>;
using IEC_WORD = IECVar<WORD_t>;
using IEC_DWORD = IECVar<DWORD_t>;
using IEC_LWORD = IECVar<LWORD_t>;
// CODESYS __XWORD — pointer-width unsigned (see XWORD_t in iec_types.hpp).
using IEC_XWORD = IECVar<XWORD_t>;
// Signed integers
using IEC_SINT = IECVar<SINT_t>;
using IEC_INT = IECVar<INT_t>;
using IEC_DINT = IECVar<DINT_t>;
using IEC_LINT = IECVar<LINT_t>;
// Unsigned integers
using IEC_USINT = IECVar<USINT_t>;
using IEC_UINT = IECVar<UINT_t>;
using IEC_UDINT = IECVar<UDINT_t>;
using IEC_ULINT = IECVar<ULINT_t>;
// Real numbers
using IEC_REAL = IECVar<REAL_t>;
using IEC_LREAL = IECVar<LREAL_t>;
// Time types
using IEC_TIME = IECVar<TIME_t>;
using IEC_DATE = IECVar<DATE_t>;
using IEC_TOD = IECVar<TOD_t>;
using IEC_DT = IECVar<DT_t>;
// IEC v3 Long time types
using IEC_LTIME = IECVar<LTIME_t>;
using IEC_LDATE = IECVar<LDATE_t>;
using IEC_LTOD = IECVar<LTOD_t>;
using IEC_LDT = IECVar<LDT_t>;
// Character types
using IEC_CHAR = IECVar<CHAR_t>;
using IEC_WCHAR = IECVar<WCHAR_t>;
// Aliases for compatibility
using IEC_TIME_OF_DAY = IEC_TOD;
using IEC_DATE_AND_TIME = IEC_DT;
using IEC_LONG_TIME_OF_DAY = IEC_LTOD;
using IEC_LONG_DATE_AND_TIME = IEC_LDT;
} // namespace strucpp