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 regionwriteOutputs(QVector<bool>)mapped from O: memory region
Analog I/O (mapped to N: integer region)¶
readAnalogInputs()→QVector<qint32>with per-channel N: word mappingwriteAnalogOutputs(QVector<qint32>)from per-channel N: word mappinganalogInputMappings()/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