Skip to content

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

  1. The base contact evaluates its bit state (XIC: TRUE when bit=1, XIO: TRUE when bit=0)
  2. This result passes through the decorator chain in order
  3. Each decorator processes the previous output and produces a new output
  4. The final output determines rung power flow
Contact → [Decorator1] → [Decorator2] → ... → Rung Power

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]

  1. Contact evaluates I:0/0
  2. Debounce filters rapid changes (stable after 50ms)
  3. TON delays the stable signal (TRUE after 2 seconds)
  4. 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

Input:  ____/‾‾‾‾‾‾‾‾‾‾‾‾‾‾\____
Output: ___________/‾‾‾‾‾‾\____
        |<-- PRE -->|

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

Input:  ____/‾‾‾‾\________________
Output: ____/‾‾‾‾‾‾‾‾‾‾‾‾‾\_______
             |<-- PRE -->|

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

Input:  ____/‾‾‾‾‾‾‾‾‾‾\____
Output: ____‾_______________
        ^ one scan pulse

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

Input:  ‾‾‾‾\____/‾‾‾‾‾‾‾
Output: ____‾____________
        ^ one scan pulse (on falling edge)

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

Input:   __|‾|_|‾‾‾‾‾‾‾‾‾‾‾|___|‾‾‾
Output:  ________|‾‾‾‾‾‾‾‾‾|_______|‾‾‾
                 |<-deb->|       |<-deb->|

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