undoCore v0.2

undoCore Released

Header-only C++17 core infrastructure providing fundamental contracts and data structures for the undoRT ecosystem.

Overview

undoCore is the shared foundation library used across all undoRT projects. It defines the common interfaces and data structures that ensure seamless interoperability between components like undoPLC, undoBUS, and st2cpp.

Header-only: Zero compilation overhead — just include and use. Perfect for integrating into any C++17 project with minimal friction.

Key Features

Installation

Using CMake (Recommended)

find_package(undoCore REQUIRED)
target_link_libraries(your_app undoCore::undoCore)

Manual Installation

Copy the include/undoCore/ directory to your project's include path.

As a Submodule

git submodule add https://github.com/undoRT/undoCore.git third_party/undoCore

Quick Start

1. Include the Library

#include <undoCore/undoCore.hpp>

using namespace undoCore;

2. Use Process Image

// 1024 inputs, 512 outputs
ProcessImage<1024, 512> pi;

// Read inputs
bool start = pi.readInputBit(0, 0);
uint16_t analog = pi.readInputWord(4);

// Write outputs
pi.writeOutputBit(0, 0, true);
pi.writeOutputWord(8, 0x1234);

3. Implement IoBus

class MyBus : public IoBus {
public:
    bool start() override { /* ... */ }
    void stop() override { /* ... */ }
    bool waitCycle(uint32_t timeoutUs) override {
        // Wait for hardware
        processImage.copyIn();  // Freeze inputs
        return true;
    }
    void notifyDone() override {
        processImage.copyOut(); // Commit outputs
    }
    // ... other methods
};

4. Use IEC Types and Math

DINT counter = 0;
REAL temperature = 23.5;
BOOL enabled = true;

TIME delay = TIME_LITERAL("T#100ms");
TIME timeout = TIME_LITERAL("T#5s");

STArray<INT, 1, 10> values;  // indices 1..10

// Math functions
DINT result = ABS(-42);                    // 42
REAL sqrtVal = SQRT(144.0f);               // 12.0f
WORD shifted = SHL(WORD(0x00FF), 4);       // 0x0FF0
DWORD rotated = ROL(DWORD(0x80000000), 1); // 0x00000001

// Generic TO_* conversions
UDINT u = TO_UDINT(42);                    // 42
DINT d = TO_DINT(3.14f);                   // 3 (truncation)
DINT r = TO_DINT_ROUND(3.14f);             // 3 (IEC rounding)
DINT t = TO_DINT_TRUNC(3.14f);             // 3 (truncation)

// Type checking
BOOL isNan = IS_NAN(0.0 / 0.0);            // TRUE

IoBus Interface

The IoBus abstract class defines the synchronization contract between undoPLC and fieldbus implementations:

/**
 * Synchronization protocol:
 *
 *   [Bus]  cycleStart()  → copyIn() → notifyPlc()
 *   [PLC]                                          → execute() → notifyBus()
 *   [Bus]  copyOut() → sendFrame()
 */

Key Methods

MethodDescription
waitCycle(timeoutUs)Blocks until bus signals a new cycle. Copies inputs before returning.
notifyDone()Signals that PLC execution is complete. Copies outputs.
start()Initialize and start the fieldbus master.
stop()Stop the fieldbus master gracefully.
cycleCount()Returns the current cycle counter.

Process Image

Double-buffered memory layout with three distinct areas:

AreaAT PrefixDirectionUsage
Inputs%I*Bus → PLCRead by PLC tasks
Outputs%Q*PLC → BusWritten by PLC tasks
Markers%M*InternalPLC internal memory

Memory Access

// Bit-level
pi.readInputBit(byte, bit);        // %IX0.0
pi.writeOutputBit(byte, bit, val); // %QX0.0

// Word-level
uint16_t val = pi.readInputWord(4);    // %IW4
pi.writeOutputWord(8, 0x1234);         // %QW8

// DWord-level
uint32_t val = pi.readInputDword(4);   // %ID4
pi.writeOutputDword(8, 0x12345678);    // %QD8

IEC 61131-3 Type Mapping

IEC TypeC++ TypeSize
BOOLbool1 byte
SINTInt81 byte
INTInt162 bytes
DINTInt324 bytes
LINTInt648 bytes
REALFloat4 bytes
LREALDouble8 bytes
BYTEByte1 byte
WORDWord2 bytes
DWORDDword4 bytes
LWORDLword8 bytes
TIMETime (uint32_t)4 bytes

Time Literals

TIME t1 = TIME_LITERAL("T#100ms");  // 100 milliseconds
TIME t2 = TIME_LITERAL("T#5s");      // 5 seconds
TIME t3 = TIME_LITERAL("T#1m");      // 1 minute
TIME t4 = TIME_LITERAL("T#1h");      // 1 hour
TIME t5 = TIME_LITERAL("T#1d");      // 1 day

STArray

Bounds-checked array template compatible with IEC 61131-3 array semantics:

// 1D array with indices 1..10
STArray<INT, 1, 10> values;
values[1] = 42;    // OK
values[11] = 0;    // throws std::out_of_range

// 2D array (3x5 matrix)
STArray<STArray<REAL, 0, 4>, 1, 3> matrix;
matrix[2][3] = 3.14;

VAR_IN_OUT

Reference wrapper for IEC 61131-3 VAR_IN_OUT parameters:

void process(VAR_INOUT<DINT> value) {
    *value += 1;  // Modifies the original variable
}

DINT counter = 0;
process(counter);  // counter becomes 1

Conversions

conversions.hpp provides the complete set of IEC 61131-3 standard type conversion functions (Table 3 of the standard). All conversions are implemented as inline functions for zero overhead.

Rounding Convention

Conversions from floating-point to integer types follow IEC 61131-3 semantics: round to nearest, ties away from zero. This differs from a plain C++ static_cast, which truncates toward zero. Use the TRUNC variants when truncation is explicitly required.

Date / Time Convention

All calendar decomposition is performed in UTC using gmtime_r, never local time. This guarantees deterministic conversions across different host timezones.

Function Categories

CategoryExample Functions
Integer → IntegerINT_TO_DINT, DINT_TO_INT, DINT_TO_LINT
Integer → Floating-pointINT_TO_REAL, DINT_TO_LREAL
Floating-point → Integer (IEC rounding)REAL_TO_INT, LREAL_TO_DINT
Floating-point → Integer (truncation)TRUNC (explicit)
Boolean ↔ IntegerBOOL_TO_INT, INT_TO_BOOL
Bit string ↔ IntegerWORD_TO_INT, DWORD_TO_DINT
BCDBCD_TO_USINT, USINT_TO_BCD, BCD_TO_INT
String ↔ NumericINT_TO_STRING, STRING_TO_REAL, BOOL_TO_STRING
STRING ↔ WSTRINGWSTRING_TO_STRING, STRING_TO_WSTRING
Time / DateDT_TO_DATE, DT_TO_TOD, DATE_TOD_TO_DT, TIME_TO_STRING
Generic TO_*TO_UDINT(x), TO_DINT_ROUND(x), TO_DINT_TRUNC(x)

Generic TO_* Functions (New in v0.2.1)

conversions.hpp now includes generic template versions of all TO_* conversions:

// Generic conversions
UDINT u = TO_UDINT(42);              // 42
DINT d = TO_DINT(3.14f);             // 3 (truncation)
WORD w = TO_WORD(0x1234);            // 0x1234
STRING s = TO_STRING(42);            // "42"
STRING b = TO_STRING(TRUE);          // "TRUE"

// With IEC rounding (floating-point → integer)
DINT r = TO_DINT_ROUND(3.14f);       // 3
DINT r2 = TO_DINT_ROUND(3.5f);       // 4 (ties away from zero)

// With truncation
DINT t = TO_DINT_TRUNC(3.99f);       // 3
💡 Tip: The TO_* functions are type-safe and work with any numeric type, making generated code cleaner and more readable.

Example

DINT val = 123;
REAL f = DINT_TO_REAL(val);           // f = 123.0f

REAL pi = 3.14159f;
DINT rounded = REAL_TO_DINT(pi);      // 3 (ties away from zero)
DINT truncated = TRUNC(pi);           // 3 (truncation)

BYTE bcd = USINT_TO_BCD(42);          // 0x42 (0100 0010)
USINT dec = BCD_TO_USINT(0x42);       // 42

STRING s = INT_TO_STRING(val);        // "123"
INT i = STRING_TO_INT(s);             // 123

DT now = static_cast<DT>(time(nullptr));
DATE d = DT_TO_DATE(now);             // midnight UTC
TOD tod = DT_TO_TOD(now);             // milliseconds since midnight
STRING dateStr = DATE_TO_STRING(d);   // "2026-07-12"
Note: BCD functions throw std::invalid_argument on invalid BCD digits and std::out_of_range for out-of-range values.

Math Functions (New in v0.2.1)

undoMath.hpp provides the complete set of IEC 61131-3 standard mathematical and bitwise operations, implemented as inline functions for zero overhead.

Function Categories

CategoryFunctions
ArithmeticABS, SQRT, LN, LOG, EXP
TrigonometricSIN, COS, TAN, ASIN, ACOS, ATAN, ATAN2
PowerEXPT (x^y)
TruncationFLOOR, CEIL, TRUNC, ROUND
Bit shiftsSHL, SHR
Bit rotationsROL, ROR
SelectionMIN, MAX, MUX, LIMIT
Type checkingIS_NAN, IS_INFINITE
BitwiseNOT, AND, OR, XOR
Integer divisionDIV, MOD

Examples

#include <undoCore/undoMath.hpp>

// Absolute value
DINT a = ABS(-42);                    // 42
REAL b = ABS(-3.14f);                 // 3.14f

// Square root
REAL s = SQRT(144.0f);                // 12.0f

// Trigonometric
REAL angle = SIN(3.14159f / 2.0f);    // 1.0f

// Bit shifts (modulo bit width)
WORD w = SHL(WORD(0x00FF), 4);        // 0x0FF0 (8-bit shift? Actually 16-bit)
DWORD d = SHR(DWORD(0x80000000), 1);  // 0x40000000

// Rotations
DWORD r = ROL(DWORD(0x80000000), 1);  // 0x00000001

// Selection
DINT minVal = MIN(10, 20, 5, 15);     // 5
DINT maxVal = MAX(10, 20, 5, 15);     // 20
DINT limit = LIMIT(0, 42, 10);        // 10

// Multiplexer (select k-th argument)
DINT mux = MUX(2, 10, 20, 30, 40);    // 30 (0-indexed)

// Type checking
BOOL isNan = IS_NAN(0.0 / 0.0);       // TRUE
BOOL isInf = IS_INFINITE(1.0 / 0.0);  // TRUE

// Integer division and modulo (truncate toward zero)
DINT quotient = DIV(7, 3);            // 2
DINT remainder = MOD(7, 3);           // 1
DINT negRem = MOD(-7, 3);             // -1 (same sign as dividend)
Note: Bit operations (SHL, SHR, ROL, ROR) interpret the shift count modulo the bit width of the type, matching IEC 61131-3 semantics.

Version Info

undoCoreVer.hpp provides compile-time and runtime version information.

Macros

MacroDescription
UNDOCORE_VERSION_MAJORMajor version number
UNDOCORE_VERSION_MINORMinor version number
UNDOCORE_VERSION_PATCHPatch version number
UNDOCORE_VERSION_STRINGVersion as a string (e.g., "0.2.1")
UNDOCORE_BUILD_DATEDate and time of compilation
UNDOCORE_COMPILERCompiler name and version

Runtime Functions

#include <undoCore/undoCore.hpp>

// Get version string
std::string ver = getVersion();           // "X.Y.Z"

// Get full details
std::string full = getFullVersion();

// Structured version numbers
Version v = getVersionNumbers();
std::cout << v.major << "." << v.minor << "." << v.patch;
Useful for: Runtime diagnostics, logging, and feature detection.

CMake Integration

Add undoCore to your CMake project using find_package:

find_package(undoCore REQUIRED)
target_link_libraries(your_app undoCore::undoCore)
✓ Zero Build Time: As a header-only library, undoCore adds no compilation overhead to your project.

Examples

Custom IoBus Implementation

class SimulatedBus : public IoBus {
    ProcessImage<128, 128> pi;
    bool running = false;
    uint64_t cycle = 0;

public:
    bool start() override {
        running = true;
        return true;
    }

    void stop() override {
        running = false;
    }

    bool isRunning() const override {
        return running;
    }

    bool waitCycle(uint32_t timeoutUs) override {
        if (!running) return false;
        std::this_thread::sleep_for(std::chrono::milliseconds(1));
        pi.copyIn();
        return true;
    }

    void notifyDone() override {
        pi.copyOut();
        cycle++;
    }

    uint64_t cycleCount() const override { return cycle; }
    uint32_t lastCycleTimeUs() const override { return 1000; }
    uint32_t nominalCycleTimeUs() const override { return 1000; }
    int32_t lastError() const override { return 0; }
};

Architecture

Core Components

ComponentDescription
IoBusAbstract interface for fieldbus master synchronization
ProcessImageDouble-buffered IEC 61131-3 memory layout
STArrayBounds-checked array with arbitrary indices
VAR_INOUTReference wrapper for in-out parameters
types.hppComplete IEC 61131-3 type mapping
conversions.hppIEC 61131-3 standard type conversions + generic TO_*
undoMath.hppIEC 61131-3 math and bitwise operations
undoCoreVer.hppVersion and build information
undoCore.hppAggregator header - include this for everything

API Reference

Full API documentation is generated with Doxygen: Browse API Reference

📖 More documentation: Visit the GitHub repository for build instructions, examples, and contributing guidelines.