Modbus Creating Server Simulation

Creating a Modbus Server: Simulating Industrial Devices

Imagine you’re a chef developing a new recipe. You wouldn’t test it on paying customers first—you’d practice in your kitchen with friends. Similarly, when developing Modbus clients, you need a reliable server to test against. Creating a Modbus server simulation lets you test your client applications without needing physical devices.

In this guide, we’ll walk you through creating Modbus server simulations using two popular approaches:

1. Python with pymodbus (for coders)

2. Node-RED (for visual, low-code users)

We’ll cover data point mapping, handling multiple requests, simulating realistic device behavior, and testing with client tools.

Why Simulate a Modbus Server?

Before we dive in, let’s understand the benefits of creating a Modbus server simulation:

  • Parallel Development: Allow client and server development to proceed independently

Prerequisites

For Python Approach

  • A Modbus client tool (Modbus Poll demo or Simply Modbus)

For Node-RED Approach

  • Modbus nodes for Node-RED (`node-red-contrib-modbus`)

Part 1: Python Modbus Server with pymodbus

Python’s pymodbus library makes it easy to create Modbus servers. We’ll build a server that simulates a temperature controller with the following features:

  • Realistic Behavior: Temperature that changes over time

Example 1: Basic Modbus TCP Server

“`python

save as basic_modbus_server.py

from pymodbus.server import StartTcpServer

from pymodbus.device import ModbusDeviceIdentification

from pymodbus.datastore import ModbusSequentialDataBlock, ModbusSlaveContext, ModbusServerContext

import time

import threading

Create a datastore for our Modbus server

This will hold all our registers, coils, etc.

store = ModbusSlaveContext(

# Digital Inputs (read-only): [initial values]

di=ModbusSequentialDataBlock(0, [0]*10),

# Coils (read/write): [initial values]

co=ModbusSequentialDataBlock(0, [0]*10),

# Holding Registers (read/write): [initial values]

hr=ModbusSequentialDataBlock(0, [250, 500, 1000, 2000, 3000]), # Temp:25.0°C, Press:50.0kPa, Setpt1:100.0, Setpt2:200.0, Setpt3:300.0

# Input Registers (read-only): [initial values]

ir=ModbusSequentialDataBlock(0, [220, 450, 1023, 0]) # Temp:22.0°C, Press:45.0kPa, Humidity:100%, Status:0

)

Create a server context with our datastore

context = ModbusServerContext(slaves=store, single=True)

Configure server identification

identity = ModbusDeviceIdentification()

identity.VendorName = ‘SimulatedDevices’

identity.ProductCode = ‘SM’

identity.VendorUrl = ‘http://simulateddevices.example.com’

identity.ProductName = ‘Simulated Modbus Server’

identity.ModelName = ‘SMS-1000’

identity.MajorMinorRevision = ‘1.0’

Function to update input registers with simulated values

def update_sensors():

“””Simulate changing sensor values”””

while True:

try:

# Get current temperature (scaled by 10: 220 = 22.0°C)

current_temp = store.getValues(3, 0)[0] # 3 = Input Registers, 0 = starting address

# Simulate temperature fluctuation (±0.1°C)

import random

new_temp = current_temp + random.randint(-1, 1)

# Keep temperature within realistic range (15-35°C)

if 150 <= new_temp <= 350:

# Update input register 0 (temperature)

store.setValues(3, 0, [new_temp])

# Simulate pressure change (±0.5 kPa)

current_press = store.getValues(3, 1)[0] # Input Register 1 = pressure

new_press = current_press + random.randint(-5, 5)

if 300 <= new_press <= 600:

store.setValues(3, 1, [new_press])

time.sleep(1) # Update every second

except Exception as e:

print(f”Error updating sensors: {e}”)

time.sleep(1)

if __name__ == “__main__”:

print(“Starting Modbus TCP Server…”)

print(“Server Address: 0.0.0.0 (all interfaces)”)

print(“Port: 502”)

print(“\nPress Ctrl+C to stop the server”)

# Start sensor update thread

sensor_thread = threading.Thread(target=update_sensors)

sensor_thread.daemon = True # Allow thread to exit when main program exits

sensor_thread.start()

try:

# Start the Modbus server

StartTcpServer(

context=context,

identity=identity,

address=(”, 502) # Listen on all interfaces, port 502

)

except KeyboardInterrupt:

print(“\nStopping Modbus TCP Server…”)

except Exception as e:

print(f”Error starting server: {e}”)

“`

Running the Python Server

1. Save the code as `basic_modbus_server.py`

2. Open a terminal and run:

“`bash

python basic_modbus_server.py

“`

3. The server will start and listen on port 502

4. Sensor values will update every second

Example 2: Multiple Slave IDs and Advanced Features

For more complex simulations, you can create a server with multiple slave IDs:

“`python

save as multi_slave_modbus_server.py

from pymodbus.server import StartTcpServer

from pymodbus.device import ModbusDeviceIdentification

from pymodbus.datastore import ModbusSequentialDataBlock, ModbusSlaveContext, ModbusServerContext

import time

import threading

Create datastores for multiple slaves

datastores = {

# Slave ID 1: Temperature Controller

1: ModbusSlaveContext(

di=ModbusSequentialDataBlock(0, [0]*10),

co=ModbusSequentialDataBlock(0, [0]*10),

hr=ModbusSequentialDataBlock(0, [250, 500, 1000]), # Temp:25.0°C, Setpt:50.0°C, Mode:1000

ir=ModbusSequentialDataBlock(0, [220, 450, 100]) # Input Temp:22.0°C, Press:45.0kPa, Humidity:10.0%

),

# Slave ID 2: Motor Drive

2: ModbusSlaveContext(

di=ModbusSequentialDataBlock(0, [1, 0, 1]), # Status bits: Running, Fault, Ready

co=ModbusSequentialDataBlock(0, [1, 0]), # Control bits: Start/Stop, Direction

hr=ModbusSequentialDataBlock(0, [1500, 200, 100]), # Speed:1500 RPM, Torque:200 Nm, Current:100 A

ir=ModbusSequentialDataBlock(0, [1498, 198, 99]) # Feedback:1498 RPM, Torque:198 Nm, Current:99 A

),

# Slave ID 3: Power Meter

3: ModbusSlaveContext(

di=ModbusSequentialDataBlock(0, [1, 1]), # Status: Active, Online

co=ModbusSequentialDataBlock(0, [0]), # Reset command

hr=ModbusSequentialDataBlock(0, [2300, 5000, 1000]), # Voltage:230.0V, Current:50.0A, Power:1000.0W

ir=ModbusSequentialDataBlock(0, [2305, 4998, 9995]) # Measured:230.5V, 49.98A, 999.5W

)

}

Create server context with multiple slaves

context = ModbusServerContext(slaves=datastores, single=False)

Configure server identification

identity = ModbusDeviceIdentification()

identity.VendorName = ‘MultiSlaveSimulator’

identity.ProductCode = ‘MSS’

identity.ProductName = ‘Multi-Slave Modbus Simulator’

identity.ModelName = ‘MSS-3000’

identity.MajorMinorRevision = ‘1.0’

Function to update all slave input registers

def update_all_sensors():

“””Update sensor values for all slaves”””

while True:

try:

import random

# Update Slave 1 (Temperature Controller)

current_temp = datastores[1].getValues(3, 0)[0]

new_temp = current_temp + random.randint(-1, 1)

if 150 <= new_temp <= 350:

datastores[1].setValues(3, 0, [new_temp])

# Update Slave 2 (Motor Drive) – simulate speed fluctuation

current_speed = datastores[2].getValues(3, 0)[0]

new_speed = current_speed + random.randint(-5, 5)

if 1000 <= new_speed <= 2000:

datastores[2].setValues(3, 0, [new_speed])

# Update Slave 3 (Power Meter) – simulate voltage variation

current_voltage = datastores[3].getValues(3, 0)[0]

new_voltage = current_voltage + random.randint(-10, 10)

if 2200 <= new_voltage <= 2400:

datastores[3].setValues(3, 0, [new_voltage])

time.sleep(1)

except Exception as e:

print(f”Error updating sensors: {e}”)

time.sleep(1)

if __name__ == “__main__”:

print(“Starting Multi-Slave Modbus TCP Server…”)

print(“Server Address: 0.0.0.0 (all interfaces)”)

print(“Port: 502”)

print(“Available Slave IDs: 1, 2, 3”)

print(“\nPress Ctrl+C to stop the server”)

# Start sensor update thread

sensor_thread = threading.Thread(target=update_all_sensors)

sensor_thread.daemon = True

sensor_thread.start()

try:

StartTcpServer(context=context, identity=identity, address=(”, 502))

except KeyboardInterrupt:

print(“\nStopping Modbus TCP Server…”)

except Exception as e:

print(f”Error starting server: {e}”)

“`

Part 2: Node-RED Modbus Server

Node-RED provides a visual way to create Modbus servers without writing much code. Let’s create a similar temperature controller simulation using Node-RED.

Step 1: Install Node-RED and Modbus Nodes

1. Install Node-RED:

“`bash

npm install -g node-red

“`

2. Start Node-RED:

“`bash

node-red

“`

3. Open a browser and go to `http://localhost:1880`

4. Install Modbus nodes:

– Click the menu (☰) in the top-right corner

– Select “Manage palette”

– Click the “Install” tab

– Search for “node-red-contrib-modbus”

– Click “Install”

Step 2: Create a Modbus Server Flow

1. Drag and drop the following nodes onto the workspace:

– `inject` (from input palette)

– `function` (from function palette)

– `modbus-server` (from modbus palette)

– `debug` (from output palette)

2. Connect them as follows:

– `inject` → `function` → `modbus-server`

– Connect `modbus-server` to `debug`

3. Configure the nodes:

Inject Node:

– Set “Repeat” to “Interval”

– Set “Interval” to “1 second”

– Click “Done”

Function Node:

– Double-click to open configuration

– Name it “Update Sensors”

– Add the following code:

“`javascript

// Initialize context variables if they don’t exist

if (!context.temperature) context.temperature = 220; // 22.0°C

if (!context.pressure) context.pressure = 450; // 45.0 kPa

// Simulate temperature fluctuation (±0.1°C)

context.temperature += Math.floor(Math.random() * 3) – 1;

// Keep within range (15-35°C)

if (context.temperature < 150) context.temperature = 150;

if (context.temperature > 350) context.temperature = 350;

// Simulate pressure fluctuation (±0.5 kPa)

context.pressure += Math.floor(Math.random() * 11) – 5;

// Keep within range (30-60 kPa)

if (context.pressure < 300) context.pressure = 300;

if (context.pressure > 600) context.pressure = 600;

// Output updated values

return {

payload: {

// Update input registers (read-only)

‘3:0’: context.temperature, // Input Register 0 = Temperature

‘3:1’: context.pressure, // Input Register 1 = Pressure

‘3:2’: Math.floor(Math.random() * 100) // Input Register 2 = Humidity

}

};

“`

– Click “Done”

Modbus Server Node:

– Double-click to open configuration

– Name it “Temperature Controller Server”

– Set “Server type” to “TCP”

– Set “Port” to “502”

– Under “Holding Registers”, add:

– Address: 0, Quantity: 4, Value: [250, 500, 1000, 2000]

– Under “Coils”, add:

– Address: 0, Quantity: 4, Value: [false, false, false, false]

– Under “Input Registers”, add:

– Address: 0, Quantity: 3, Value: [220, 450, 50]

– Under “Discrete Inputs”, add:

– Address: 0, Quantity: 4, Value: [true, false, true, false]

– Click “Done”

4. Deploy the flow by clicking the “Deploy” button in the top-right corner

Step 3: Test the Node-RED Server

Your Node-RED Modbus server is now running. The inject node will trigger the function every second, updating the input registers with simulated values.

Testing Your Modbus Simulation

Now that we have our Modbus servers running, let’s test them with client tools.

Option 1: Using Modbus Poll (GUI Tool)

1. Download and install Modbus Poll demo from [Modbus Tools](https://www.modbustools.com/download.html)

2. Open Modbus Poll

3. Go to “Connection” → “Connect…”

4. Set connection type to “TCP/IP”

5. Enter “localhost” as the IP address

6. Leave port as 502

7. Click “OK”

8. Go to “Setup” → “Read/Write Definition…”

9. Set:

– Function: “03: Read Holding Registers”

– Address: 0

– Quantity: 4

– Slave ID: 1

10. Click “OK”

11. You should see the register values updating in real-time

Option 2: Using Simply Modbus (Free Tool)

1. Download Simply Modbus from [Simply Modbus](https://www.simplymodbus.ca/)

2. Open “Simply Modbus TCP Client”

3. Set IP address to “localhost”, port to 502

4. Set Slave ID to 1

5. Under “Read Holding Registers”:

– Start Address: 0

– Quantity: 4

– Poll Rate: 1000 ms

6. Click “Connect” then “Read/Write”

7. You should see the register values updating

Option 3: Using Python Client

“`python

save as test_modbus_server.py

from pymodbus.client import ModbusTcpClient

import time

Create client

client = ModbusTcpClient(host=”localhost”, port=502)

try:

# Connect to server

client.connect()

print(“Connected to Modbus server”)

# Test multiple slaves (if running multi-slave server)

slave_ids = [1, 2, 3]

for _ in range(5): # Run 5 iterations

for slave_id in slave_ids:

print(f”\n— Slave ID: {slave_id} —“)

# Read holding registers

holding_response = client.read_holding_registers(address=0, count=3, slave=slave_id)

if not holding_response.isError():

print(f”Holding Registers: {holding_response.registers}”)

# Read input registers (should be updating)

input_response = client.read_input_registers(address=0, count=3, slave=slave_id)

if not input_response.isError():

print(f”Input Registers: {input_response.registers}”)

# Read coils

coil_response = client.read_coils(address=0, count=3, slave=slave_id)

if not coil_response.isError():

print(f”Coils: {coil_response.bits[:3]}”)

time.sleep(2) # Wait 2 seconds between iterations

except Exception as e:

print(f”Error: {e}”)

finally:

client.close()

print(“\nDisconnected from Modbus server”)

“`

Run the test script:

“`bash

python test_modbus_server.py

“`

Simulating Realistic Device Behavior

To make your simulation more realistic, consider adding these features:

1. Sensor Correlation

  • Simulate flow rate changing based on valve position

2. Error Simulation

  • Return invalid data for specific addresses

3. Device State Management

  • Simulate device startup/shutdown sequences

4. Persistent Data

  • Simulate non-volatile memory

Advanced Features

Adding Error Simulation to Python Server

“`python

Add this to your Python server

import random

def error_simulation_hook(request):

“””Hook function to simulate errors”””

# 5% chance of returning an error

if random.random() < 0.05:

# Randomly choose an exception code

exception_code = random.choice([1, 2, 3, 4]) # Illegal function, illegal address, etc.

print(f”Simulating exception code {exception_code} for request: {request}”)

# Return error response

return request.createExceptionResponse(exception_code)

return request

Add this to your StartTcpServer call:

StartTcpServer(

context=context,

identity=identity,

address=(”, 502),

requestHook=error_simulation_hook # Add error simulation hook

)

“`

Conclusion

Creating a Modbus server simulation is a powerful way to test and develop Modbus client applications. Whether you prefer writing Python code or using visual tools like Node-RED, you can create realistic simulations that mimic industrial devices.

Key takeaways:

  • Advanced features like error simulation help test edge cases

By creating your own Modbus server simulations, you can accelerate development, improve testing coverage, and gain a deeper understanding of Modbus communication. This skills will serve you well when working with real Modbus devices in industrial environments.

Now it’s your turn to experiment! Try extending these examples with new device types, more complex behavior, or additional slave IDs. The possibilities are endless.