/**
* MechBase PLC — Ladder Logic Interpreter v2
*
* IEC 61131-3 compliant ladder logic interpreter for ESP32.
*
* Instructions:
* Contacts: NO, NC, Rising Edge, Falling Edge
* Outputs: OTE (Energize), OTL (Latch), OTU (Unlatch)
* Timers: TON, TOF, TP — with EN, TT, DN status bits
* Structure: Parallel branching, series/parallel networks
*/
#ifndef LADDER_INTERPRETER_H
#define LADDER_INTERPRETER_H
#include <Arduino.h>
#include <stdint.h>
#include <stdbool.h>
#include <vector>
#include <string>
// ─────────────────────────────────────────────────────────────────────────────
// Limits
// ─────────────────────────────────────────────────────────────────────────────
#define MAX_DIGITAL_INPUTS 16
#define MAX_DIGITAL_OUTPUTS 16
#define MAX_TIMER_COUNT 16
#define MAX_COUNTER_COUNT 16
#define MAX_INT_REGISTERS 32
#define MAX_BOOL_REGISTERS 64
#define MAX_NETWORKS 64
#define MAX_BRANCHES 16
#define MAX_ELEMENTS 32
// ─────────────────────────────────────────────────────────────────────────────
// Contact types
// ─────────────────────────────────────────────────────────────────────────────
enum ContactType : uint8_t {
CT_NONE = 0,
CT_NO = 1, // Normally Open --| |--
CT_NC = 2, // Normally Closed --|/|--
CT_RISING = 3, // Rising Edge --|P|--
CT_FALLING = 4, // Falling Edge --|N|--
CT_TON_DN = 5, // TON Done bit
CT_TON_TT = 6, // TON Timing bit
CT_TON_EN = 7, // TON Enable bit
CT_TOF_DN = 8, // TOF Done bit
CT_TOF_TT = 9, // TOF Timing bit
CT_TOF_EN = 10, // TOF Enable bit
CT_TP_DN = 11, // TP Done bit
CT_TP_TT = 12, // TP Timing bit
CT_TP_EN = 13, // TP Enable bit
CT_INT_GT = 14, // Integer Greater Than
CT_INT_LT = 15, // Integer Less Than
CT_INT_EQ = 16, // Integer Equal
CT_INT_GTE = 17, // Integer >=
CT_INT_LTE = 18, // Integer <=
};
// ─────────────────────────────────────────────────────────────────────────────
// Output types
// ─────────────────────────────────────────────────────────────────────────────
enum OutputType : uint8_t {
OT_NONE = 0,
OT_OTE = 1, // Output Energize --( )--
OT_OTL = 2, // Output Latch --(L)-- SET
OT_OTU = 3, // Output Unlatch --(U)-- RESET
OT_TON = 4, // Timer On-Delay
OT_TOF = 5, // Timer Off-Delay
OT_TP = 6, // Timer Pulse
OT_MVI = 7, // Move Integer
OT_ADD = 8, // Add Integer
OT_SUB = 9, // Subtract Integer
};
// ─────────────────────────────────────────────────────────────────────────────
// Timer struct with all status bits
// ─────────────────────────────────────────────────────────────────────────────
struct LadderTimer {
uint8_t id; // Timer index
uint16_t preset; // Preset in milliseconds
uint32_t elapsed; // Accumulated time
bool en; // Enable — controlling rung has power
bool tt; // Timing — timer is currently running
bool dn; // Done — elapsed >= preset
void reset() {
elapsed = 0;
tt = false;
dn = false;
}
void update(uint32_t current_ms, bool enabled, uint8_t timer_type) {
en = enabled;
if (timer_type == OT_TON) {
// TON: starts timing when enabled, done when elapsed >= preset
if (enabled) {
if (elapsed < preset) {
elapsed = min(elapsed + 100, preset + 1); // 100ms scan
tt = true;
}
dn = (elapsed >= preset);
} else {
elapsed = 0;
tt = false;
dn = false;
}
}
else if (timer_type == OT_TOF) {
// TOF: starts timing when DISABLED, done when elapsed >= preset
if (enabled) {
elapsed = 0;
tt = false;
dn = true;
} else {
if (elapsed < preset) {
elapsed = min(elapsed + 100, preset + 1);
tt = true;
}
dn = (elapsed >= preset);
}
}
else if (timer_type == OT_TP) {
// TP: pulses for preset duration on rising edge
static bool last_en = false;
bool rising_edge = enabled && !last_en;
last_en = enabled;
if (rising_edge || (enabled && elapsed < preset)) {
if (elapsed < preset) {
elapsed = min(elapsed + 100, preset + 1);
tt = true;
dn = (elapsed >= preset);
}
} else {
elapsed = 0;
tt = false;
dn = false;
}
}
}
};
// ─────────────────────────────────────────────────────────────────────────────
// Counter struct
// ─────────────────────────────────────────────────────────────────────────────
struct LadderCounter {
uint8_t id;
int16_t preset;
int16_t count;
bool cu; // Count Up input
bool cd; // Count Down input
bool reset; // Reset input
bool ov; // Overflow
bool un; // Underflow
bool dn; // Done (count >= preset)
void update() {
if (reset) {
count = 0;
ov = false;
un = false;
} else {
if (cu) count++;
if (cd) count--;
}
ov = (count > 32767);
un = (count < -32768);
dn = (count >= preset);
}
};
// ─────────────────────────────────────────────────────────────────────────────
// Element — a single contact or output in a network
// ─────────────────────────────────────────────────────────────────────────────
struct Element {
uint8_t type; // ContactType or OutputType
char tag[16]; // Tag name (e.g., "I0", "Q0", "T0", "M10", "INT0")
int16_t value; // Preset / compare value
uint8_t src_reg; // Source register index (for math/move)
uint8_t dst_reg; // Destination register index (for math/move)
bool last_state; // For edge detection
bool result; // Evaluation result
};
// ─────────────────────────────────────────────────────────────────────────────
// Branch — parallel group of elements
// ─────────────────────────────────────────────────────────────────────────────
struct Branch {
std::vector<Element> elements; // Series elements in this branch
bool power_flow; // Result of this branch
bool evaluate(LadderInterpreter* interp, bool is_output = false);
};
// ─────────────────────────────────────────────────────────────────────────────
// Network — a ladder rung with optional parallel branches
// ─────────────────────────────────────────────────────────────────────────────
struct Network {
uint16_t number;
std::vector<Branch> input_branches; // Input-side parallel branches
std::vector<Branch> output_branches; // Output-side parallel branches
bool power_flow;
bool evaluate(LadderInterpreter* interp);
};
// ─────────────────────────────────────────────────────────────────────────────
// Main interpreter
// ─────────────────────────────────────────────────────────────────────────────
class LadderInterpreter {
private:
bool di[MAX_DIGITAL_INPUTS]; // Digital inputs
bool do_[MAX_DIGITAL_OUTPUTS]; // Digital outputs
bool do_prev[MAX_DIGITAL_OUTPUTS];
bool m_bool[MAX_BOOL_REGISTERS]; // Boolean memory M0..M63
int16_t m_int[MAX_INT_REGISTERS]; // Integer memory INT0..INT31
LadderTimer timers[MAX_TIMER_COUNT];
LadderCounter counters[MAX_COUNTER_COUNT];
std::vector<Network> networks;
bool running;
bool simulating;
uint32_t scan_start;
public:
LadderInterpreter();
~LadderInterpreter();
// ── Lifecycle ────────────────────────────────────────────────────────
void begin();
void scan();
void run();
void stop();
// ── I/O ──────────────────────────────────────────────────────────────
void setDI(uint8_t idx, bool val);
bool getDI(uint8_t idx);
bool getDO(uint8_t idx);
void setDO(uint8_t idx, bool val);
// ── Memory ───────────────────────────────────────────────────────────
void setM(bool idx, bool val);
bool getM(bool idx);
void setINT(uint8_t idx, int16_t val);
int16_t getINT(uint8_t idx);
// ── Timer access ─────────────────────────────────────────────────────
LadderTimer& getTimer(uint8_t idx);
void resetTimer(uint8_t idx);
// ── Counter access ───────────────────────────────────────────────────
LadderCounter& getCounter(uint8_t idx);
void resetCounter(uint8_t idx);
// ── Program loading ──────────────────────────────────────────────────
Network& addNetwork(uint16_t num);
void loadFromJSON(const char* json);
// ── Helpers ──────────────────────────────────────────────────────────
bool readBool(const char* tag);
void writeBool(const char* tag, bool val);
int16_t readInt(const char* tag);
void writeInt(const char* tag, int16_t val);
uint8_t parseTimerIndex(const char* tag);
uint8_t parseCounterIndex(const char* tag);
// ── Debug ────────────────────────────────────────────────────────────
void dumpState();
bool isRunning() { return running; }
uint32_t getScanTime() { return millis() - scan_start; }
// ── Simulation ───────────────────────────────────────────────────────
void enableSimulation();
void disableSimulation();
bool getSimulation();
};
#endif // LADDER_INTERPRETER_H