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.
Key Features
- IoBus — Abstract fieldbus master interface
- ProcessImage — Double-buffered IEC 61131-3 memory layout
- IEC 61131-3 Types — Complete elementary type mapping
- STArray — Bounds-checked arrays with arbitrary indices
- VAR_IN_OUT — Reference wrapper for in-out parameters
- Conversions — Full IEC 61131-3 type conversion table
- Math Functions — IEC 61131-3 math operations (SHL, SHR, SIN, COS, etc.)
- TO_* Functions — Generic template conversions (
TO_UDINT(x)) - Time Literals — IEC-style T# parsing (T#10s, T#100ms)
- Version Info — Compile-time and runtime version information
- Zero Dependencies — Standard C++17 only
- CMake Integration — find_package(undoCore) support
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
| Method | Description |
|---|---|
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:
| Area | AT Prefix | Direction | Usage |
|---|---|---|---|
| Inputs | %I* | Bus → PLC | Read by PLC tasks |
| Outputs | %Q* | PLC → Bus | Written by PLC tasks |
| Markers | %M* | Internal | PLC 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 Type | C++ Type | Size |
|---|---|---|
| BOOL | bool | 1 byte |
| SINT | Int8 | 1 byte |
| INT | Int16 | 2 bytes |
| DINT | Int32 | 4 bytes |
| LINT | Int64 | 8 bytes |
| REAL | Float | 4 bytes |
| LREAL | Double | 8 bytes |
| BYTE | Byte | 1 byte |
| WORD | Word | 2 bytes |
| DWORD | Dword | 4 bytes |
| LWORD | Lword | 8 bytes |
| TIME | Time (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
| Category | Example Functions |
|---|---|
| Integer → Integer | INT_TO_DINT, DINT_TO_INT, DINT_TO_LINT |
| Integer → Floating-point | INT_TO_REAL, DINT_TO_LREAL |
| Floating-point → Integer (IEC rounding) | REAL_TO_INT, LREAL_TO_DINT |
| Floating-point → Integer (truncation) | TRUNC (explicit) |
| Boolean ↔ Integer | BOOL_TO_INT, INT_TO_BOOL |
| Bit string ↔ Integer | WORD_TO_INT, DWORD_TO_DINT |
| BCD | BCD_TO_USINT, USINT_TO_BCD, BCD_TO_INT |
| String ↔ Numeric | INT_TO_STRING, STRING_TO_REAL, BOOL_TO_STRING |
| STRING ↔ WSTRING | WSTRING_TO_STRING, STRING_TO_WSTRING |
| Time / Date | DT_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
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"
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
| Category | Functions |
|---|---|
| Arithmetic | ABS, SQRT, LN, LOG, EXP |
| Trigonometric | SIN, COS, TAN, ASIN, ACOS, ATAN, ATAN2 |
| Power | EXPT (x^y) |
| Truncation | FLOOR, CEIL, TRUNC, ROUND |
| Bit shifts | SHL, SHR |
| Bit rotations | ROL, ROR |
| Selection | MIN, MAX, MUX, LIMIT |
| Type checking | IS_NAN, IS_INFINITE |
| Bitwise | NOT, AND, OR, XOR |
| Integer division | DIV, 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)
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
| Macro | Description |
|---|---|
UNDOCORE_VERSION_MAJOR | Major version number |
UNDOCORE_VERSION_MINOR | Minor version number |
UNDOCORE_VERSION_PATCH | Patch version number |
UNDOCORE_VERSION_STRING | Version as a string (e.g., "0.2.1") |
UNDOCORE_BUILD_DATE | Date and time of compilation |
UNDOCORE_COMPILER | Compiler 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;
CMake Integration
Add undoCore to your CMake project using find_package:
find_package(undoCore REQUIRED)
target_link_libraries(your_app undoCore::undoCore)
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
| Component | Description |
|---|---|
IoBus | Abstract interface for fieldbus master synchronization |
ProcessImage | Double-buffered IEC 61131-3 memory layout |
STArray | Bounds-checked array with arbitrary indices |
VAR_INOUT | Reference wrapper for in-out parameters |
types.hpp | Complete IEC 61131-3 type mapping |
conversions.hpp | IEC 61131-3 standard type conversions + generic TO_* |
undoMath.hpp | IEC 61131-3 math and bitwise operations |
undoCoreVer.hpp | Version and build information |
undoCore.hpp | Aggregator header - include this for everything |
API Reference
Full API documentation is generated with Doxygen: Browse API Reference
undoCore