PiPLC Contact Decorators¶
Decorators extend contact behavior with timing, edge detection, and counting capabilities without requiring separate timer/counter rungs.
Overview¶
Decorators are attached to contact instructions (XIC/XIO) and modify how the contact's output affects rung power flow. They provide a clean way to implement common patterns that would otherwise require multiple rungs and additional memory addresses.
How Decorators Work¶
- The base contact evaluates its bit state (XIC: TRUE when bit=1, XIO: TRUE when bit=0)
- This result passes through the decorator chain in order
- Each decorator processes the previous output and produces a new output
- The final output determines rung power flow
Memory Integration¶
Timer and counter decorators (TON, TOF, Counter) are automatically assigned T: or C: memory addresses when created. This enables:
- RES instruction support: Use a RES instruction targeting the decorator's T:/C: address to reset its state during runtime
- Memory synchronization: Decorator state (ACC, DN, TT, EN) is synced with the PLC memory manager each scan cycle
- Symbol table entries: Auto-created variables (e.g.,
DEC_TON_0,DEC_TOF_1,DEC_CTR_0) appear in the symbol table for easy reference
Address assignment is automatic and avoids conflicts with standalone timer/counter instructions. Addresses are persisted in project files and restored on load. Legacy projects without decorator addresses are automatically migrated.
Runtime State Inspection¶
During simulation, right-click on a decorator glyph to open a live state dialog showing:
- Timer decorators (TON/TOF): Address, PRE/ACC values, progress bar, DN/TT/EN indicators
- Counter decorators: Address, PRE/ACC values, progress bar, DN indicator
- Debounce decorators: Debounce time, accumulated time, output state
- Edge decorators (OSR/OSF): Triggered state
The dialog refreshes at 100ms intervals for real-time monitoring.
Decorator Chaining¶
Multiple decorators can be applied to a single contact. They execute in order from first (innermost) to last (outermost).
Example Chain: [Debounce:50ms] → [TON:2000ms] → [OSR]
- Contact evaluates I:0/0
- Debounce filters rapid changes (stable after 50ms)
- TON delays the stable signal (TRUE after 2 seconds)
- OSR converts to single pulse (one scan on rising edge)
Available Decorators¶
| Type | Description | Parameters |
|---|---|---|
| TON | Timer On-Delay | Preset (ms) |
| TOF | Timer Off-Delay | Preset (ms) |
| OSR | One-Shot Rising | None |
| OSF | One-Shot Falling | None |
| Debounce | Input debounce filter | Time (ms) |
| Counter | Count rising edges | Preset (count) |
TON - Timer On-Delay¶
Output becomes TRUE after input has been TRUE for the preset time. Resets immediately when input goes FALSE.
Parameters¶
| Parameter | Type | Default | Description |
|---|---|---|---|
| preset | qint32 | 1000 | Time in milliseconds |
Behavior¶
| Input | Action |
|---|---|
| TRUE | Accumulate time; output TRUE when ACC >= PRE |
| FALSE | Reset ACC=0; output FALSE immediately |
Timing Diagram¶
Use Cases¶
- Hold-to-activate: Require button held for safety
- Startup delay: Wait before enabling output
- Anti-chatter: Ignore brief input transitions
Example¶
// Create contact with TON decorator
auto *contact = new ContactInstruction(InstructionType::XIC, Address("I:0/0"));
contact->addDecorator(new TONDecorator(2000)); // 2 second delay
// Button I:0/0 must be held for 2 seconds before contact passes power
XML Representation¶
<Instruction type="XIC" address="I:0/0">
<Decorators>
<Decorator type="TON" preset="2000"/>
</Decorators>
</Instruction>
TOF - Timer Off-Delay¶
Output goes TRUE immediately when input becomes TRUE. Remains TRUE for the preset time after input goes FALSE.
Parameters¶
| Parameter | Type | Default | Description |
|---|---|---|---|
| preset | qint32 | 1000 | Time in milliseconds |
Behavior¶
| Input | Action |
|---|---|
| TRUE | ACC=0; output TRUE immediately |
| FALSE | Accumulate time; output TRUE while ACC < PRE, FALSE when ACC >= PRE |
Timing Diagram¶
Use Cases¶
- Extended run: Keep output on after trigger stops
- Motion-activated lights: Stay on after motion stops
- Cooldown periods: Allow process to complete
Example¶
// Create contact with TOF decorator
auto *contact = new ContactInstruction(InstructionType::XIC, Address("I:0/0"));
contact->addDecorator(new TOFDecorator(5000)); // 5 second extension
// Output stays TRUE for 5 seconds after I:0/0 goes FALSE
XML Representation¶
<Instruction type="XIC" address="I:0/0">
<Decorators>
<Decorator type="TOF" preset="5000"/>
</Decorators>
</Instruction>
OSR - One-Shot Rising¶
Output is TRUE for exactly one scan when input transitions from FALSE to TRUE. No parameters required.
Behavior¶
| Transition | Output |
|---|---|
| FALSE → TRUE | TRUE (one scan only) |
| TRUE → TRUE | FALSE |
| TRUE → FALSE | FALSE |
| FALSE → FALSE | FALSE |
Timing Diagram¶
Use Cases¶
- Single pulse: Generate one pulse per button press
- Edge trigger: Execute action once per transition
- Event counting: Convert level to countable edge
Example¶
// Create contact with OSR decorator
auto *contact = new ContactInstruction(InstructionType::XIC, Address("I:0/0"));
contact->addDecorator(new OSRDecorator());
// Generates one pulse each time I:0/0 transitions TRUE
XML Representation¶
<Instruction type="XIC" address="I:0/0">
<Decorators>
<Decorator type="OSR"/>
</Decorators>
</Instruction>
OSF - One-Shot Falling¶
Output is TRUE for exactly one scan when input transitions from TRUE to FALSE. No parameters required.
Behavior¶
| Transition | Output |
|---|---|
| TRUE → FALSE | TRUE (one scan only) |
| FALSE → FALSE | FALSE |
| FALSE → TRUE | FALSE |
| TRUE → TRUE | FALSE |
Timing Diagram¶
Use Cases¶
- Release detection: Detect button release
- Completion pulse: Signal end of condition
- Toggle logic: Trigger on release instead of press
Example¶
// Create contact with OSF decorator
auto *contact = new ContactInstruction(InstructionType::XIC, Address("I:0/0"));
contact->addDecorator(new OSFDecorator());
// Generates one pulse each time I:0/0 transitions FALSE
XML Representation¶
<Instruction type="XIC" address="I:0/0">
<Decorators>
<Decorator type="OSF"/>
</Decorators>
</Instruction>
Debounce - Input Debounce Filter¶
Filters rapid input changes by requiring the input to remain stable for the debounce time before the output changes.
Parameters¶
| Parameter | Type | Default | Description |
|---|---|---|---|
| debounceTime | qint32 | 50 | Time in milliseconds |
Behavior¶
| Condition | Action |
|---|---|
| Input changes | Reset timer; start timing |
| Input stable for debounce time | Output changes to match input |
| Input changes before timer expires | Restart timer |
Timing Diagram¶
Note: Bouncing transitions are filtered; only stable transitions pass.
Use Cases¶
- Mechanical switches: Filter contact bounce
- Noisy inputs: Stabilize signals
- Sensor debounce: Ignore transient triggers
Important Timing Note¶
When input changes, the debounce decorator: 1. Resets accumulated time to 0 2. Does NOT count that scan's elapsed time 3. Begins accumulating on subsequent scans
This means the first scan after a change always outputs the previous stable value.
Example¶
// Create contact with Debounce decorator
auto *contact = new ContactInstruction(InstructionType::XIC, Address("I:0/0"));
contact->addDecorator(new DebounceDecorator(50)); // 50ms debounce
// I:0/0 must be stable for 50ms before output changes
XML Representation¶
<Instruction type="XIC" address="I:0/0">
<Decorators>
<Decorator type="Debounce" debounceTime="50"/>
</Decorators>
</Instruction>
Counter - Rising Edge Counter¶
Output becomes TRUE after input has transitioned from FALSE to TRUE (rising edge) N times. Similar to CTU instruction but integrated into contact evaluation.
Parameters¶
| Parameter | Type | Default | Description |
|---|---|---|---|
| preset | qint32 | 1 | Number of rising edges required |
Behavior¶
| Transition | Action |
|---|---|
| FALSE → TRUE | Increment count |
| Count reaches preset | Output TRUE |
| After done | Stays TRUE until reset |
Use Cases¶
- Cycle counting: Trigger after N cycles
- Batch counting: Signal after N parts
- Multi-press detection: Require multiple button presses
Example¶
// Create contact with Counter decorator
auto *contact = new ContactInstruction(InstructionType::XIC, Address("I:0/0"));
contact->addDecorator(new CounterDecorator(5)); // Count 5 cycles
// Output goes TRUE after I:0/0 transitions TRUE 5 times
XML Representation¶
<Instruction type="XIC" address="I:0/0">
<Decorators>
<Decorator type="Counter" preset="5"/>
</Decorators>
</Instruction>
Decorator Chaining Examples¶
Example 1: Debounced Hold-to-Start¶
Button must be stable (debounced) and held for 3 seconds.
auto *contact = new ContactInstruction(InstructionType::XIC, Address("I:0/0"));
contact->addDecorator(new DebounceDecorator(50)); // Filter bounce
contact->addDecorator(new TONDecorator(3000)); // 3 second hold
// Chain: Input → Debounce(50ms) → TON(3s) → Output
Example 2: Single Pulse After Delay¶
Generate one pulse 2 seconds after button press.
auto *contact = new ContactInstruction(InstructionType::XIC, Address("I:0/0"));
contact->addDecorator(new TONDecorator(2000)); // 2 second delay
contact->addDecorator(new OSRDecorator()); // Convert to pulse
// Chain: Input → TON(2s) → OSR → Output
// Output: One pulse when TON done bit goes TRUE
Example 3: Extended Pulse on Release¶
Generate pulse that stays on for 5 seconds after button release.
auto *contact = new ContactInstruction(InstructionType::XIC, Address("I:0/0"));
contact->addDecorator(new OSFDecorator()); // Detect release
contact->addDecorator(new TOFDecorator(5000)); // Extend 5 seconds
// Chain: Input → OSF → TOF(5s) → Output
// Output: 5 second pulse starting when button released
Example 4: Count Debounced Edges¶
Count stable button presses, ignoring bounce.
auto *contact = new ContactInstruction(InstructionType::XIC, Address("I:0/0"));
contact->addDecorator(new DebounceDecorator(50)); // Filter bounce
contact->addDecorator(new CounterDecorator(10)); // Count 10 presses
// Chain: Input → Debounce(50ms) → Counter(10) → Output
// Output: TRUE after 10 clean button presses
API Reference¶
Base Decorator Class¶
class Decorator : public QObject
{
// Get decorator type
DecoratorType type() const;
QString typeName() const;
// Evaluation
virtual bool evaluate(bool input, qint32 elapsedMs) = 0;
virtual void reset() = 0;
virtual Decorator* clone() const = 0;
// Memory address (for timer/counter decorators)
Address address() const;
void setAddress(const Address &address);
bool hasAddress() const;
// Memory synchronization (overridden by TON/TOF/Counter)
virtual void syncFromMemory(MemoryManager *memory);
virtual void syncToMemory(MemoryManager *memory);
// Status
virtual bool isDone() const;
virtual bool isActive() const;
// Description
virtual QString description() const = 0;
virtual QString shortDescription() const = 0;
};
ContactInstruction Decorator Methods¶
class ContactInstruction : public Instruction
{
// Add decorator to chain
void addDecorator(Decorator *decorator);
// Remove decorator from chain
void removeDecorator(Decorator *decorator);
// Clear all decorators
void clearDecorators();
// Get decorator list
const QList<Decorator*>& decorators() const;
// Check if has decorators
bool hasDecorators() const;
};
Quick Reference¶
| Decorator | Output TRUE When | Parameters |
|---|---|---|
| TON | Input TRUE for preset time | preset (ms) |
| TOF | Input TRUE OR timing after FALSE | preset (ms) |
| OSR | Rising edge (FALSE→TRUE) | none |
| OSF | Falling edge (TRUE→FALSE) | none |
| Debounce | Input stable for debounce time | debounceTime (ms) |
| Counter | N rising edges counted | preset (count) |
See Also¶
- INSTRUCTIONS.md - Complete instruction reference
- Schema Documentation - XML file format reference
- Examples - Sample ladder programs