This project provides a complete, containerized environment for simulating a fleet of multiple LinuxCNC machines (three, by default). It is designed for testing and validating distributed monitoring and control systems.
The architecture is built around a central OPC UA server that acts as the single source of truth for the entire machine cell. Each simulated LinuxCNC machine runs independently in its own container, connects to this central server, and reports its real-time status (e.g., Machine On, E-Stop Active).
Finally, an OpenSCADA service provides a web-based dashboard, offering a top-down view of the entire fleet by consuming data from the OPC UA server.
linuxcnc_sim-main/
│
├── docker-compose.yml # Master file: defines and launches all services.
├── Dockerfile # Builds the LinuxCNC simulation container.
├── server.Dockerfile # Builds the lightweight OPC UA server container.
│
├── host_opcua_server.py # Python code for the central OPC UA server.
├── entrypoint.sh # Helper script to start LinuxCNC inside its container.
├── requirements.txt # Python requirements.
│
├── linuxcnc-configs/ # Directory for machine-specific configs.
│ ├── cnc_1/
│ │ ├── axis_mm.ini # LinuxCNC config (axes, kinematics).
│ │ ├── opcua_postgui.hal # HAL file to load the OPC UA client.
│ │ └── hal_opcua_client.py # Python script (the OPC UA client).
│ ├── cnc_2/ # (Identical structure for machine 2)
│ └── cnc_3/ # (Identical structure for machine 3)
│
├── linuxcnc-nc_files/ # G-code files folder, connected by Samba.
│
├── openscada-config/
│ └── oscada.xml # OpenSCADA configuration for the web dashboard.
│
└── Pictures/ # Project images
The entire system is orchestrated by docker-compose.yml and runs on a dedicated bridge network (cnc-net) to allow services to communicate using their container names as hostnames.
The system consists of five key services:
-
opcua-server(The Hub):- Built from
server.Dockerfile, this is a minimal Python container running thehost_opcua_server.pyscript. - It acts as the central data broker. Upon launch, it receives a command-line argument (
3) instructing it to create an OPC UA namespace for three machines:Lathe_1,Lathe_2, andLathe_3.
- Built from
-
linuxcnc-1,linuxcnc-2,linuxcnc-3(The Machines):- These three identical containers are the core simulations, built from the main
Dockerfile. - This
Dockerfilecompiles LinuxCNC 2.9 from source and sets up a complete simulation environment. - Each container runs a full LinuxCNC instance with its
AXISGUI, which is displayed on the host's X11 server. - Crucially, each container also runs the
hal_opcua_client.pyscript. This script connects to theopcua-servercontainer and acts as a two-way "data bridge."
- These three identical containers are the core simulations, built from the main
-
openscada(The Dashboard):- This service runs a standard
dudanov/openscadaimage. - Its configuration is loaded from
oscada.xml, which instructs it to connect toopc.tcp://opcua-server:4840/linuxcnc/as a client. - It reads the status variables from all lathes and presents them on a simple web dashboard, accessible from the host.
- This service runs a standard
- A user clicks the "Machine On" button in the
linuxcnc-1GUI. - A HAL signal (
halui.machine.is-on) inside that container goesTrue. - The
opcua_postgui.halfile routes this signal to thehal_opcua_client.pyscript. - The Python script, knowing its
MACHINE_IDisLathe_1, writesTrueto theLathe_1.Sensors.Machine_Onnode on the centralopcua-server. - The
openscadaservice, which is subscribed to this node, reads the change and updates the web dashboard athttp://localhost:8080.
-
docker-compose.yml- Purpose: The master orchestration file. It defines all services, builds their respective images, connects them to the
cnc-netnetwork, and maps host volumes (like configuration files and X11 sockets). It is the single entry point for launching the entire simulation.
- Purpose: The master orchestration file. It defines all services, builds their respective images, connects them to the
-
Dockerfile- Purpose: A "heavyweight" build script that installs all dependencies for, and compiles LinuxCNC 2.9 from source inside an
ubuntu:22.04image. It also creates thelinuxcncuser and sets up the environment required to run the simulation GUI.
- Purpose: A "heavyweight" build script that installs all dependencies for, and compiles LinuxCNC 2.9 from source inside an
-
server.Dockerfile- Purpose: A minimal, lightweight build script. It uses a
python:3.10-slimimage and installs only theasyncualibrary. This creates a fast-to-launch, low-resource container for the sole purpose of running the central server.
- Purpose: A minimal, lightweight build script. It uses a
-
host_opcua_server.py- Purpose: The code for the central OPC UA server. It's an
asyncuaserver that, when run, takes a number as an argument (e.g.,3). It then dynamically builds a namespace containing an object for each machine (e.g.,Lathe_1,Lathe_2,Lathe_3), each with its ownSensorsandCommandsvariables.
- Purpose: The code for the central OPC UA server. It's an
-
hal_opcua_client.py- Purpose: This is the "data bridge" that runs inside each LinuxCNC container. It is a Python script that creates a HAL component (
opcua) and also connects as a client to the centralopcua-server. It continuously reads its HAL input pins (likemachine-status) and writes those values to the OPC UA server, and vice-versa for commands.
- Purpose: This is the "data bridge" that runs inside each LinuxCNC container. It is a Python script that creates a HAL component (
-
opcua_postgui.hal- Purpose: A HAL configuration file loaded by LinuxCNC after the GUI starts. Its one and only job is to load the
hal_opcua_client.pyscript as a real-time component (loadusr) and connect its HAL pins (e.g.,opcua.machine-status) to the main LinuxCNC HAL signals (e.g.,halui.machine.is-on).
- Purpose: A HAL configuration file loaded by LinuxCNC after the GUI starts. Its one and only job is to load the
-
axis_mm.ini- Purpose: The standard LinuxCNC configuration file. It defines the machine's properties (kinematics, axes limits, velocities) and, most importantly, specifies which HAL files to load (like
opcua_postgui.hal).
- Purpose: The standard LinuxCNC configuration file. It defines the machine's properties (kinematics, axes limits, velocities) and, most importantly, specifies which HAL files to load (like
-
oscada.xml- Purpose: The configuration file for the OpenSCADA service. It defines the data source (our
opcua-server) and maps specific OPC UA nodes (e.t.,2:"Lathe_1.Sensors.Machine_On") to internal SCADA tags, which are then displayed on its web interface.
- Purpose: The configuration file for the OpenSCADA service. It defines the data source (our
-
entrypoint.sh- Purpose: A utility script used by the
Dockerfileto start the LinuxCNC container. It correctly sets up theXDG_RUNTIME_DIRand provides a flexible way to launch thelinuxcnccommand with the correct configuration file.
- Purpose: A utility script used by the
This project is managed entirely by Docker Compose.
Prerequisites:
- Docker
- Docker Compose
- An X11 server (for viewing the LinuxCNC GUIs)
Launch:
- Clone the repository (or ensure you are in the project's root directory).
- Set up external volumes: The
docker-compose.ymlfile maps local directories (e.g.,$HOME/linuxcnc_sim/linuxcnc-configs) into the containers. Ensure these directories exist on your host machine. - Launch the simulation stack:
docker-compose up --build
- Observe: You should see:
- Three LinuxCNC
AXISGUIs appear on your desktop. - The
opcua-serverlog showing it has configured 3 instances. - The OpenSCADA service start.
- Three LinuxCNC
- Launch: Start the stack using
docker-compose up. - Verify Connections: Confirm three
AXISGUIs are visible and theopcua-serverlog is running. - Connect OPC UA Client:
- Use a client like UaExpert to connect to the server at:
opc.tcp://localhost:4840/linuxcnc/ - Verify you see
Lathe_1,Lathe_2, andLathe_3in the namespace, each withSensorsandCommandsfolders.
- Use a client like UaExpert to connect to the server at:
- Connect SCADA Client:
- Open a web browser to
http://localhost:8080. - You should see the OpenSCADA dashboard, showing the status of all three lathes (defaulting to 'False' or 'Off').
- Open a web browser to
- Test Instance 1 (CNC -> SCADA):
- In the
linuxcnc-1GUI, click the "Machine On" (F2) button. - Expected Result: Watch the
Lathe_1.Sensors.Machine_Onvariable flip toTruein UaExpert. - Expected Result: Watch the "Lathe 1 Machine On" status update to
Trueon the OpenSCADA web dashboard.
- In the
- Test Instance 2 (SCADA -> CNC):
- In UaExpert, find the
Lathe_2.Commands.Execute_EStopnode, set it toTrue. - Expected Result: The
linuxcnc-2GUI should immediately enter an E-Stop state. TheLathe_2.Sensors.EStop_Activenode should flip toTruein UaExpert and OpenSCADA.
- In UaExpert, find the