Skip to content

I/O Provider Architecture

Overview

PiPLC uses an extensible I/O provider architecture for virtual simulation and hardware/protocol integration. GPIO on Linux ARM SBCs (Pine64, Raspberry Pi) is provided via libgpiod and /dev/gpiochipN character devices.

Architecture

┌─────────────────────────────────────────────┐
│  EngineContext                               │
│  ├─ ExecutionEngine (worker thread)          │
│  │   ├─ MemoryManager                        │
│  │   │   ├─ I: input words (digital)         │
│  │   │   ├─ O: output words (digital)        │
│  │   │   └─ N: integer words (analog)        │
│  │   ├─ LadderExecutor                       │
│  │   └─ IIOProvider* ──────────┐             │
│  │                              │             │
│  └─ Concrete Provider:          │             │
│     ┌───────────────────────────┘             │
│     │                                         │
│     ├─ VirtualIOProvider        (all platforms) │
│     │   In-memory I/O for simulation            │
│     ├─ GPIOProvider             (Linux + gpiod) │
│     │   Hardware GPIO via /dev/gpiochipN        │
│     ├─ ExplorerHatProProvider   (Linux ARM)     │
│     │   Fixed board mapping                      │
│     └─ ModbusTcpClientProvider  (all platforms) │
│         Network I/O via Modbus TCP              │
│                                               │
│  Configuration: .ioconfig JSON file           │
└─────────────────────────────────────────────┘

I/O Provider Interface

All providers implement IIOProvider, which defines:

Digital I/O (synchronous, called each scan cycle)

  • readInputs()QVector<bool> mapped to I: memory region
  • writeOutputs(QVector<bool>) mapped from O: memory region

Analog I/O (mapped to N: integer region)

  • readAnalogInputs()QVector<qint32> with per-channel N: word mapping
  • writeAnalogOutputs(QVector<qint32>) from per-channel N: word mapping
  • analogInputMappings() / analogOutputMappings() → channel-to-N:word index

Scan Cycle Flow

executeScan():
  1.  Read digital inputs  → I: region
  1b. Read analog inputs   → N: region (via mappings)
  2.  Execute ladder logic
  3.  Write digital outputs ← O: region
  3b. Write analog outputs  ← N: region (via mappings)

Configuration File (.ioconfig)

Provider configuration is stored in a separate JSON file, independent of .plcproj project files. This allows the same ladder program to run with different hardware configurations.

Example

{
    "version": 1,
    "provider": "gpio",
    "gpioChip": "/dev/gpiochip0",
    "digitalPins": [
        {
            "gpioLine": 17,
            "direction": "input",
            "activeLow": false,
            "bias": "pull_down",
            "plcWord": 0,
            "plcBit": 0,
            "label": "Start Button"
        },
        {
            "gpioLine": 22,
            "direction": "output",
            "activeLow": false,
            "bias": "disabled",
            "plcWord": 0,
            "plcBit": 0,
            "label": "Motor Relay"
        }
    ],
    "analogChannels": [
        {
            "channel": 0,
            "direction": "input",
            "plcNWord": 10,
            "rawMin": 0,
            "rawMax": 32767,
            "scaledMin": 0,
            "scaledMax": 10000,
            "label": "Temperature Sensor"
        }
    ],
    "virtualFallback": {
        "inputCount": 8,
        "outputCount": 8
    }
}

Fields

Field Description
provider "virtual", "gpio", "explorer_hat_pro", or "modbus_tcp_client"
boardName Optional board descriptor name used by GPIO provider
gpioChip libgpiod chip device path (e.g., /dev/gpiochip0)
digitalPins[].gpioLine GPIO line number on the chip
digitalPins[].direction "input" or "output"
digitalPins[].activeLow Invert logic level
digitalPins[].bias "disabled", "pull_up", or "pull_down"
digitalPins[].plcWord PLC address word (I:word/bit for inputs, O:word/bit for outputs)
digitalPins[].plcBit PLC address bit
analogChannels[].channel ADC/DAC channel number
analogChannels[].plcNWord N: region word index for this channel
analogChannels[].rawMin/rawMax Raw ADC/DAC range
analogChannels[].scaledMin/scaledMax Scaled engineering units range
virtualFallback Input/output counts when provider is "virtual"
modbusClient.* Connection and register mapping fields for the Modbus TCP Client provider

Platform Support

Platform Available Providers
Windows VirtualIOProvider, ModbusTcpClientProvider
Linux x86 VirtualIOProvider, GPIOProvider (if libgpiod present), ModbusTcpClientProvider
Linux ARM + libgpiod VirtualIOProvider, GPIOProvider, ExplorerHatProProvider (plugin), ModbusTcpClientProvider

The PIPLC_HAS_GPIO compile definition controls whether GPIOProvider is included. It is set automatically by CMake when libgpiod is detected via pkg-config on Linux.

I/O Config Panel (GUI Editor)

The IOConfigPanel is a dockable widget (bottom area, tabified with Signal Router) for creating and editing I/O configurations visually. Access via View > I/O Config or GPIO > Pin Configuration....

Layout

  • Toolbar: New, Open (.ioconfig), Save (.ioconfig), Import CSV, Export CSV
  • Provider Header: Combo for Virtual/GPIO/Explorer HAT Pro/Modbus TCP Client, context-sensitive fields (board + virtual counts + Modbus connection)
  • Two Tabs: Digital Pins table and Analog Channels table with Add/Remove buttons
  • Apply Button: Pushes the configuration to the active engine context

CSV Import/Export

The IOConfigCSV class supports a section-based CSV format for bulk editing in spreadsheet tools:

[DigitalPins]
gpioLine,direction,activeLow,bias,plcWord,plcBit,label
17,Input,false,PullDown,0,0,Start Button
22,Output,false,Disabled,0,0,Motor Relay

[AnalogChannels]
channel,direction,plcNWord,rawMin,rawMax,scaledMin,scaledMax,label
0,Input,10,0,4095,0,10000,Temperature Sensor

Apply Behavior

Clicking Apply validates all table rows, then calls EngineContext::loadIOConfig() on the active context. This: - Requires the engine to be stopped - Destroys the existing I/O provider and creates a new one matching the panel settings - Is context-specific (only affects the active engine context) - Does not save to file (use the Save toolbar button) or persist with the project

On platforms where a provider plugin is unavailable, selecting it shows a warning and falls back to Virtual provider behavior when applying.

Loading Configuration

Load via GPIO > Load I/O Config... menu, the IOConfigPanel's Open button, or programmatically:

EngineContext *ctx = engineManager->activeContext();
ctx->loadIOConfigFile("/path/to/my-board.ioconfig");

If no .ioconfig is loaded, the default VirtualIOProvider with 8 inputs and 8 outputs is used.

Future Extensions

  • MQTT I/O Provider: Subscribe to input topics, publish output state
  • Modbus RTU Provider: Serial Modbus for industrial I/O modules
  • I2C/SPI ADC drivers: ADS1115, MCP3008, etc. for real analog I/O
  • Composite Provider: Multiplex multiple providers into one I/O image
  • Project Persistence: Embed I/O config in .plcproj/.mplcproj files