HomeBlog › Simulate Modbus Slave Without PLC

Simulate Modbus Slave Without PLC: 5 Methods Compared (2026)

Waiting for hardware to arrive? Need to test your SCADA or HMI before the PLC shows up? Here are all five ways to simulate a Modbus slave without real hardware — with honest tradeoffs for each.

In This Article

  1. Why Simulate Instead of Using Real Hardware
  2. 5-Method Comparison Table
  3. Method 1: Dedicated Simulator Software (ModbusSimulator)
  4. Method 2: Python pymodbus (Code-Based)
  5. Method 3: Arduino as Modbus Slave
  6. Method 4: Raspberry Pi with pymodbus
  7. Method 5: Virtual PLC (CODESYS / OpenPLC)
  8. Which Method for Which Use Case?
  9. Simulating Modbus RTU Without Serial Hardware
  10. FAQ

Why Simulate a Modbus Slave Without PLC Hardware?

Every automation engineer hits this problem eventually. You're building a SCADA system, an HMI, or a data acquisition application that reads from a Modbus device — energy meter, VFD, PLC, temperature controller. But the actual device won't arrive for weeks, or you need to test edge cases (exception responses, register overflows, communication timeouts) that are painful or impossible to trigger on real hardware.

Simulating the Modbus slave solves this in multiple scenarios:

The core requirement: your simulation needs to respond to Modbus function codes (FC01-FC06, FC15-FC16, FC23) with correct data formats, register sizes, and error codes. Anything that does this correctly is a valid Modbus slave simulation.

5-Method Comparison Table

Method Setup Time Cost TCP RTU GUI Multi-slave Data Logging Best For
ModbusSimulator 5 min Free trial / ~$40 ✓ (247) Fast, professional, all use cases
Python pymodbus 30-60 min Free ✓ (custom) Custom logic, CI/CD testing
Arduino 1-2 hours $5-25 (hardware) RTU serial testing with real cable
Raspberry Pi 2-4 hours $35-75 (hardware) Persistent slave on network
Virtual PLC (CODESYS) 2-8 hours Free / $300+ Full PLC logic simulation

Method 1: Dedicated Simulator Software (ModbusSimulator)

Method 2: Python pymodbus (Code-Based Simulation)

Python + pymodbus Library

Code Required Free

pymodbus is the most mature Python Modbus library. Writing a Modbus slave (server) in Python gives you full flexibility — custom logic, dynamic register values, integration with databases, test automation frameworks, and CI/CD pipelines.

Installation

bash
pip install pymodbus

Modbus TCP Slave — Minimal Example

Python — TCP slave on localhost:502
import asyncio
from pymodbus.server import StartAsyncTcpServer
from pymodbus.datastore import (
    ModbusSequentialDataBlock,
    ModbusSlaveContext,
    ModbusServerContext,
)

def create_slave_context():
    # Initialize registers: coils, discrete inputs, holding regs, input regs
    # Arguments: address, count, value
    store = ModbusSlaveContext(
        di=ModbusSequentialDataBlock(0, [0] * 100),   # Discrete Inputs
        co=ModbusSequentialDataBlock(0, [0] * 100),   # Coils
        hr=ModbusSequentialDataBlock(0, [0] * 200),   # Holding Registers
        ir=ModbusSequentialDataBlock(0, [0] * 200),   # Input Registers
    )
    # Set some initial values (holding register 0-4)
    store.setValues(3, 0, [1234, 5678, 9012, 3456, 7890])
    return store

async def run_server():
    context = ModbusServerContext(
        slaves={1: create_slave_context()},  # unit ID 1
        single=False
    )
    print("Starting Modbus TCP slave on 0.0.0.0:502")
    await StartAsyncTcpServer(
        context=context,
        address=("0.0.0.0", 502)
    )

if __name__ == "__main__":
    asyncio.run(run_server())

Modbus RTU Slave — Serial Port Example

Python — RTU slave on COM3 (Windows) / /dev/ttyUSB0 (Linux)
import asyncio
from pymodbus.server import StartAsyncSerialServer
from pymodbus.datastore import ModbusSequentialDataBlock, ModbusSlaveContext, ModbusServerContext

async def run_rtu_server():
    store = ModbusSlaveContext(
        hr=ModbusSequentialDataBlock(0, [100, 200, 300, 400, 500] + [0]*195)
    )
    context = ModbusServerContext(slaves={1: store}, single=False)

    print("Starting Modbus RTU slave on COM3, 9600 baud")
    await StartAsyncSerialServer(
        context=context,
        port="COM3",           # Windows: COM3, COM4... Linux: /dev/ttyUSB0
        baudrate=9600,
        bytesize=8,
        parity="N",
        stopbits=1,
        framer="rtu"
    )

asyncio.run(run_rtu_server())

Adding Dynamic Register Values

One advantage of Python is dynamic register updates — simulate a running process:

Python — updating registers every second
import asyncio
import random
from pymodbus.server import StartAsyncTcpServer
from pymodbus.datastore import ModbusSequentialDataBlock, ModbusSlaveContext, ModbusServerContext

async def update_registers(context):
    """Update registers every second to simulate a running process."""
    counter = 0
    while True:
        await asyncio.sleep(1.0)
        counter += 1
        slave = context[1]  # unit ID 1
        # Simulate: temperature (reg 0), current (reg 1), power (reg 2)
        values = [
            int(220 + random.uniform(-5, 5)),   # voltage ~ 220V
            int(10 + random.uniform(-1, 1)),     # current ~ 10A
            counter % 65535,                      # energy accumulator
            int(random.uniform(20, 80)),         # temperature
        ]
        slave.setValues(3, 0, values)  # FC03 holding registers

async def run_server():
    store = ModbusSlaveContext(
        hr=ModbusSequentialDataBlock(0, [0] * 100)
    )
    context = ModbusServerContext(slaves={1: store}, single=False)

    await asyncio.gather(
        StartAsyncTcpServer(context=context, address=("0.0.0.0", 502)),
        update_registers(context)
    )

asyncio.run(run_server())

Pros

  • Free, cross-platform
  • Full custom logic in code
  • CI/CD integration (pytest)
  • Dynamic register values
  • Database integration possible

Cons

  • Requires Python knowledge
  • 30-60 min setup for complex cases
  • No GUI for register monitoring
  • Debugging is harder

Method 3: Arduino as Modbus RTU Slave

Arduino + ModbusSlave or Modbus-Arduino Library

Hardware Required

An Arduino (Uno, Mega, Nano) with a Modbus library acts as a real serial Modbus RTU slave device. This is useful when you need a physical RS485 connection — to test your RS485 wiring, terminators, and physical layer before the real PLC arrives.

Hardware Needed

Arduino Sketch (ModbusSlave Library)

Arduino — Modbus RTU slave, unit ID 1, baud 9600
#include <ModbusSlave.h>

// Modbus slave instance: Serial, unit ID=1, RS485 DE/RE pin=-1 (RS232 mode)
Modbus slave(Serial, 1, -1);

// Holding registers storage
uint16_t holdingRegisters[10] = {0};

// Callback to handle FC03/FC06/FC16 (read/write holding registers)
uint8_t readHoldingRegisters(uint8_t fc, uint16_t address, uint16_t length) {
    for (uint16_t i = 0; i < length; i++) {
        slave.writeRegisterToBuffer(i, holdingRegisters[address + i]);
    }
    return STATUS_OK;
}

uint8_t writeHoldingRegisters(uint8_t fc, uint16_t address, uint16_t length) {
    for (uint16_t i = 0; i < length; i++) {
        holdingRegisters[address + i] = slave.readRegisterFromBuffer(i);
    }
    return STATUS_OK;
}

void setup() {
    Serial.begin(9600, SERIAL_8N1);
    // Set initial register values
    holdingRegisters[0] = 1234;   // Temperature * 10 = 123.4°C
    holdingRegisters[1] = 2200;   // Voltage * 10 = 220.0V
    holdingRegisters[2] = 500;    // Current * 100 = 5.00A

    slave.cbVector[CB_READ_HOLDING_REGISTERS] = readHoldingRegisters;
    slave.cbVector[CB_WRITE_HOLDING_REGISTERS] = writeHoldingRegisters;
}

void loop() {
    slave.poll();

    // Simulate changing values
    static unsigned long lastUpdate = 0;
    if (millis() - lastUpdate > 1000) {
        holdingRegisters[0] = 1200 + random(100);  // Temperature fluctuation
        lastUpdate = millis();
    }
}

Pros

  • Real physical RS485 port
  • Tests actual wiring and terminators
  • Cheap hardware (~$5-25)
  • Portable — power from USB

Cons

  • RTU serial only (no TCP)
  • Single slave only
  • Requires Arduino coding
  • No data logging or GUI
  • 1-2 hours setup

Method 4: Raspberry Pi with pymodbus

Raspberry Pi 4/5 — Linux-based persistent slave

Hardware Required

A Raspberry Pi running pymodbus is a solid option when you need a persistent Modbus slave on your network that doesn't tie up your workstation. The Pi can run 24/7, serve multiple test setups, and simulate both TCP and RTU (with USB-to-serial adapter).

Setup

bash (on Raspberry Pi)
# Install Python and pymodbus
sudo apt update && sudo apt install python3 python3-pip -y
pip3 install pymodbus

# For RTU via USB-serial adapter
pip3 install pyserial

# Create the server script (same code as Method 2)
# Run as a systemd service to start on boot:
sudo nano /etc/systemd/system/modbus-slave.service
systemd service file
[Unit]
Description=Modbus TCP Slave Simulator
After=network.target

[Service]
Type=simple
User=pi
ExecStart=/usr/bin/python3 /home/pi/modbus_slave.py
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target

Pros

  • Runs 24/7 without tying up PC
  • TCP + RTU support
  • Multiple slaves on same Pi
  • Remote access via SSH
  • Physical RS485 via GPIO/USB

Cons

  • $35-75 hardware cost
  • 2-4 hours setup (OS + config)
  • No GUI — command line only
  • Requires Linux knowledge

Method 5: Virtual PLC (CODESYS / OpenPLC)

CODESYS Runtime or OpenPLC — Full PLC Logic Simulation

CODESYS free runtime / OpenPLC free

If you need to simulate not just Modbus registers but actual PLC ladder logic — rungs, function blocks, timers, counters — a virtual PLC is the right approach. CODESYS offers a free Windows runtime. OpenPLC is a fully open-source IEC 61131-3 compliant PLC implementation.

When to Use a Virtual PLC

Note: CODESYS Virtual Control SL (full Modbus support) requires a paid licence after trial. The free runtime has limitations. OpenPLC is free but has more limited Modbus TCP server capabilities. For pure Modbus testing (register-level), a dedicated simulator is faster and cheaper.

Pros

  • Full IEC 61131-3 PLC logic
  • Simulates actual program behavior
  • Industry-standard environment
  • Both TCP and RTU Modbus

Cons

  • 2-8 hours setup
  • Steep learning curve
  • CODESYS licence cost ($300+)
  • Overkill for register-level testing

Which Method for Which Use Case?

Your SituationRecommended MethodWhy
SCADA/HMI development before hardware arrivesModbusSimulator (Method 1)Fast setup, GUI register map, auto-update simulation
Automated unit testing in CI/CD pipelinePython pymodbus (Method 2)Code integration with pytest, Docker-compatible
Test physical RS485 wiring and cableArduino (Method 3)Real serial hardware, real RS485 port
Persistent slave on team network for shared testingRaspberry Pi (Method 4)24/7 availability, network-accessible
Customer FAT with real PLC code executionCODESYS Virtual PLC (Method 5)Actual IEC 61131-3 program running
Training PLC/SCADA engineersModbusSimulator (Method 1)Zero setup, visual interface, good for demo
Debugging communication issues between master and slaveModbusSimulator (Method 1)Exception response control, detailed logging
IoT gateway testing (Modbus to MQTT/cloud)Python pymodbus (Method 2)Can integrate with IoT test frameworks

Simulating Modbus RTU Without a Serial Port

For RTU simulation without physical serial hardware, use virtual COM port pairs. On Windows, install com0com (free, open-source). This creates linked virtual COM port pairs — anything written to COM10 appears at COM11 and vice versa.

Setup com0com on Windows
# After installing com0com:
# 1. Open com0com setup utility
# 2. Create a port pair, e.g., COM10 <--> COM11
# 3. Run your Modbus RTU slave simulator on COM10
# 4. Connect your Modbus master (Modbus Poll, SCADA, etc.) to COM11
# Same baud rate, parity, stop bits on both sides

On Linux, use socat:

bash — create virtual serial pair on Linux
# Install socat
sudo apt install socat

# Create linked virtual serial ports: /tmp/ttyV0 and /tmp/ttyV1
socat -d -d pty,raw,echo=0,link=/tmp/ttyV0 pty,raw,echo=0,link=/tmp/ttyV1 &

# Run pymodbus RTU slave on /tmp/ttyV0
# Connect your Modbus master to /tmp/ttyV1

Key point: When using virtual COM ports, both the slave and master must use identical serial settings — same baud rate (9600, 19200, 115200, etc.), same parity (N, E, O), same stop bits (1 or 2). A mismatch will cause CRC errors or no response.

Testing Exception Responses

One of the most valuable things about simulation is testing exception responses — scenarios that are hard to trigger on real hardware. Key exception codes you should test your master against:

Exception CodeNameWhat to Test
01 (0x01)Illegal FunctionMaster sends unsupported function code
02 (0x02)Illegal Data AddressMaster reads register address beyond slave's range
03 (0x03)Illegal Data ValueMaster writes value outside valid range
04 (0x04)Slave Device FailureSlave internal error — test your master's retry logic
No responseTimeoutTest master timeout and retry behavior

ModbusSimulator lets you configure a slave to return any exception code for any request — useful for verifying that your SCADA raises the correct alarm and that your Modbus master handles errors gracefully without crashing or hanging.

Start Simulating in 5 Minutes

Download ModbusSimulator and have a Modbus TCP or RTU slave running before your coffee is ready. Free 30-day trial — no credit card required.

Download Free Trial

Frequently Asked Questions

How do I simulate a Modbus slave without a PLC?

Use dedicated simulator software (ModbusSimulator — fastest, GUI-based), Python pymodbus (flexible, code-based, free), Arduino (real serial, cheap hardware), Raspberry Pi (persistent network slave), or a virtual PLC (CODESYS/OpenPLC for full logic simulation). For most engineers testing SCADA or HMI, a dedicated Windows simulator is the fastest option.

What is the best free Modbus slave simulator?

ModbusSimulator.com offers a free 30-day trial with all features. Python pymodbus is permanently free but requires coding. For permanently free GUI-based simulation, options are limited — most commercial simulators require a licence.

Can I simulate Modbus TCP without hardware?

Yes. Modbus TCP runs entirely over standard TCP/IP. Run the slave simulator on your PC and connect your master to localhost (127.0.0.1) port 502. No hardware needed — works on a single machine or over a LAN.

How do I simulate Modbus RTU without a serial port?

Use virtual COM port software: com0com on Windows (free) or socat on Linux. This creates linked virtual serial ports — slave listens on COM10, master connects to COM11. No physical hardware required.

What Python library should I use for Modbus slave simulation?

pymodbus is the most widely used and actively maintained library. Use StartAsyncTcpServer() for TCP and StartAsyncSerialServer() for RTU. For simpler needs, the umodbus library is lighter but less feature-rich.

How many Modbus slave devices can I simulate simultaneously?

ModbusSimulator supports up to 247 slaves simultaneously (the full Modbus unit ID range, 1-247). Python pymodbus can handle multiple unit IDs on a single server instance. Arduino handles one slave per device. Raspberry Pi can run multiple Python server instances.