In production environments, PLC programs rarely fail because an input turns on exactly when expected. Failures are more commonly triggered by edge cases: contacts chatter, sensors intermittently drop, inputs remain stuck, or events occur in a non-ideal order during startup, e-stop recovery, or manual intervention.
A relay-based input simulator provides a safe, bench-friendly way to inject controlled disturbances into PLC input circuits without requiring the full machine to be present. When paired with deterministic pseudo-random patterns, the same “ugly” scenario can be reproduced after program changes for regression testing.
The Relay DDL HAT is controlled over I²C (not direct GPIO per channel). On Raspberry Pi platforms, the HAT communicates through the standard I²C bus exposed on the 40-pin header (SDA/SCL).
/dev/i2c-1)Enable I²C using the Raspberry Pi configuration utility:
sudo raspi-config
# Interface Options → I2C → Enable
sudo reboot
Install I²C utilities and scan the bus:
sudo apt update
sudo apt install -y i2c-tools
i2cdetect -y 1
Example successful detection for the Relay DDL board:
0 1 2 3 4 5 6 7 8 9 a b c d e f
00: -- -- -- -- -- -- -- --
10: 10 -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
...
The 10 entry indicates an I²C address of 0x10. This document assumes the Relay DDL
address is 0x10. If your scan shows a different address, update scripts accordingly.
Install Python tooling and create a dedicated virtual environment:
sudo apt update
sudo apt install -y python3 python3-venv python3-pip
python3 -m venv ~/venvs/plc_sim
source ~/venvs/plc_sim/bin/activate
python -m pip install --upgrade pip
Install the I²C Python library used in this implementation:
pip install smbus2
~/venvs/plc_sim/bin/python so your run configuration executes on the Pi where the I²C hardware exists.
Before writing application logic, validate that relays respond at the command line. The Relay DDL convention used
here is register 0x01..0x04 for relay channels and values 0xFF (ON) /
0x00 (OFF).
# Relay 1 ON / OFF
sudo i2cset -y 1 0x10 0x01 0xff
sudo i2cset -y 1 0x10 0x01 0x00
# Relay 2 ON / OFF
sudo i2cset -y 1 0x10 0x02 0xff
sudo i2cset -y 1 0x10 0x02 0x00
If relays switch correctly via i2cset, higher-level Python control is typically straightforward.
The script below toggles all four relay channels ON and OFF for a defined number of cycles. This is useful for proving wiring and confirming basic switching behavior prior to PLC integration.
#!/usr/bin/env python3
import time
from smbus2 import SMBus
BUS = 1
ADDR = 0x10
RELAYS = (1, 2, 3, 4)
ON_VALUE = 0xFF
OFF_VALUE = 0x00
def set_relay(bus: SMBus, relay_num: int, on: bool) -> None:
reg = relay_num # 0x01..0x04
val = ON_VALUE if on else OFF_VALUE
bus.write_byte_data(ADDR, reg, val)
def set_all(on: bool) -> None:
with SMBus(BUS) as bus:
for r in RELAYS:
set_relay(bus, r, on)
def main() -> None:
on_time_s = 1.0
off_time_s = 1.0
cycles = 5
for _ in range(cycles):
set_all(True)
time.sleep(on_time_s)
set_all(False)
time.sleep(off_time_s)
set_all(False) # safe end state
if __name__ == "__main__":
main()
For PLC stress testing, use a deterministic seed so the same pattern can be replayed after a logic change. The example below generates short bursts of toggling on random channels with variable dwell times.
#!/usr/bin/env python3
import time
import random
from smbus2 import SMBus
BUS = 1
ADDR = 0x10
RELAYS = (1, 2, 3, 4)
def write_relay(bus: SMBus, relay: int, on: bool) -> None:
bus.write_byte_data(ADDR, relay, 0xFF if on else 0x00)
def main() -> None:
seed = 1337
duration_s = 30
min_dwell = 0.05
max_dwell = 0.40
rng = random.Random(seed)
t_end = time.time() + duration_s
with SMBus(BUS) as bus:
# Start known-safe
for r in RELAYS:
write_relay(bus, r, False)
while time.time() < t_end:
r = rng.choice(RELAYS)
on = rng.choice([True, False])
write_relay(bus, r, on)
time.sleep(rng.uniform(min_dwell, max_dwell))
# End known-safe
for r in RELAYS:
write_relay(bus, r, False)
if __name__ == "__main__":
main()
Most PLC input modules are designed for structured electrical conventions (sourcing/sinking, commons, and defined current thresholds). A relay simulator works best when it emulates a real field device:
i2cdetect -y 1 and i2cset before writing scripts.For commissioning teams and maintenance engineering, this approach can materially reduce debug time by exposing logic weaknesses early—before a machine is fully assembled or before production schedules make experimentation risky.