@codesys/mcp-toolkit 1.1.4 → 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/settings.local.json +25 -0
- package/CHANGELOG.md +36 -26
- package/CLAUDE.md +26 -0
- package/CONTRIBUTING.md +72 -72
- package/LICENSE +21 -21
- package/README.md +152 -145
- package/README.md.enhanced +155 -155
- package/codesys-mcp-toolkit-1.2.0.tgz +0 -0
- package/codesys-mcp-toolkit-1.2.1.tgz +0 -0
- package/dist/codesys_interop.js +29 -2
- package/dist/scripts/check_status.py +21 -0
- package/dist/scripts/compile_project.py +65 -0
- package/dist/scripts/create_method.py +80 -0
- package/dist/scripts/create_pou.py +96 -0
- package/dist/scripts/create_project.py +54 -0
- package/dist/scripts/create_property.py +78 -0
- package/dist/scripts/ensure_project_open.py +154 -0
- package/dist/scripts/find_object_by_path.py +111 -0
- package/dist/scripts/get_pou_code.py +91 -0
- package/dist/scripts/get_project_structure.py +72 -0
- package/dist/scripts/open_project.py +25 -0
- package/dist/scripts/save_project.py +29 -0
- package/dist/scripts/set_pou_code.py +104 -0
- package/dist/server.js +124 -11
- package/dist/sessionManager.js +401 -0
- package/docs/index.md +26 -26
- package/docs/installation.md +84 -84
- package/docs/session_management.md +84 -0
- package/examples/README.md +78 -78
- package/examples/sample_config.json +32 -32
- package/package.json +51 -48
- package/scriptDoc.txt +8679 -8679
- package/scripts/build.js +25 -0
package/README.md.enhanced
CHANGED
|
@@ -1,155 +1,155 @@
|
|
|
1
|
-
# @codesys/mcp-toolkit
|
|
2
|
-
|
|
3
|
-

|
|
4
|
-

|
|
5
|
-
|
|
6
|
-
A Model Context Protocol (MCP) server for CODESYS V3 programming environments. This toolkit enables seamless interaction between MCP clients (like Claude Desktop) and CODESYS, allowing automation of project management, POU creation, code editing, and compilation tasks.
|
|
7
|
-
|
|
8
|
-
<p align="center">
|
|
9
|
-
<img src="https://github.com/yourusername/codesys-mcp-toolkit/raw/main/docs/assets/diagram.png" alt="CODESYS MCP Toolkit Architecture" width="600">
|
|
10
|
-
</p>
|
|
11
|
-
|
|
12
|
-
## 🌟 Features
|
|
13
|
-
|
|
14
|
-
- **Project Management**
|
|
15
|
-
- Open existing CODESYS projects
|
|
16
|
-
- Create new projects from templates
|
|
17
|
-
- Save project changes
|
|
18
|
-
|
|
19
|
-
- **POU Management**
|
|
20
|
-
- Create Programs, Function Blocks, and Functions
|
|
21
|
-
- Set declaration and implementation code
|
|
22
|
-
- Create properties and methods for Function Blocks
|
|
23
|
-
- Compile projects
|
|
24
|
-
|
|
25
|
-
- **MCP Resources**
|
|
26
|
-
- `codesys://project/status`: Check scripting status and project state
|
|
27
|
-
- `codesys://project/{+project_path}/structure`: Retrieve project structure
|
|
28
|
-
- `codesys://project/{+project_path}/pou/{+pou_path}/code`: Read POU code
|
|
29
|
-
|
|
30
|
-
- **MCP Tools**
|
|
31
|
-
- `open_project`: Open specified CODESYS project
|
|
32
|
-
- `create_project`: Create new CODESYS project
|
|
33
|
-
- `save_project`: Save currently open project
|
|
34
|
-
- `create_pou`: Create new Program, Function Block, or Function
|
|
35
|
-
- `set_pou_code`: Update code for a specified POU
|
|
36
|
-
- `create_property`: Create property in a Function Block
|
|
37
|
-
- `create_method`: Create method in a Function Block
|
|
38
|
-
- `compile_project`: Compile the open project
|
|
39
|
-
|
|
40
|
-
## 📋 Prerequisites
|
|
41
|
-
|
|
42
|
-
- **CODESYS V3**: A working CODESYS V3 installation (tested with 3.5 SP21) with **Scripting Engine** component enabled
|
|
43
|
-
- **Node.js**: Version 18 or later required
|
|
44
|
-
- **MCP Client**: An MCP-enabled application (e.g., Claude Desktop)
|
|
45
|
-
|
|
46
|
-
## 🚀 Installation
|
|
47
|
-
|
|
48
|
-
### Option 1: Install from npm (Recommended)
|
|
49
|
-
|
|
50
|
-
```bash
|
|
51
|
-
npm install -g @codesys/mcp-toolkit
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
This installs the package globally, making the `codesys-mcp-toolkit` command available in your terminal.
|
|
55
|
-
|
|
56
|
-
### Option 2: Install from Source
|
|
57
|
-
|
|
58
|
-
```bash
|
|
59
|
-
# Clone the repository
|
|
60
|
-
git clone https://github.com/yourusername/codesys-mcp-toolkit.git
|
|
61
|
-
cd codesys-mcp-toolkit
|
|
62
|
-
|
|
63
|
-
# Install dependencies
|
|
64
|
-
npm install
|
|
65
|
-
|
|
66
|
-
# Build the project
|
|
67
|
-
npm run build
|
|
68
|
-
|
|
69
|
-
# Install globally
|
|
70
|
-
npm install -g .
|
|
71
|
-
```
|
|
72
|
-
|
|
73
|
-
## 🔧 Configuration
|
|
74
|
-
|
|
75
|
-
### Claude Desktop Configuration
|
|
76
|
-
|
|
77
|
-
1. Locate your Claude Desktop configuration file:
|
|
78
|
-
- Windows: `C:\Users\<YourUsername>\AppData\Roaming\Claude\claude_desktop_config.json`
|
|
79
|
-
|
|
80
|
-
2. Add the CODESYS MCP server entry:
|
|
81
|
-
|
|
82
|
-
```json
|
|
83
|
-
{
|
|
84
|
-
"mcpServers": {
|
|
85
|
-
"codesys_local": {
|
|
86
|
-
"command": "npx",
|
|
87
|
-
"args": [
|
|
88
|
-
"-y",
|
|
89
|
-
"@codesys/mcp-toolkit",
|
|
90
|
-
"--codesys-path", "C:\\Program Files\\CODESYS 3.5.21.0\\CODESYS\\Common\\CODESYS.exe",
|
|
91
|
-
"--codesys-profile", "CODESYS V3.5 SP21"
|
|
92
|
-
]
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
```
|
|
97
|
-
|
|
98
|
-
3. Customize the paths to match your CODESYS installation:
|
|
99
|
-
- `--codesys-path`: Path to your CODESYS.exe
|
|
100
|
-
- `--codesys-profile`: Your CODESYS profile name
|
|
101
|
-
|
|
102
|
-
4. Restart Claude Desktop to apply the changes
|
|
103
|
-
|
|
104
|
-
## 🛠️ Usage Examples
|
|
105
|
-
|
|
106
|
-
### Creating a Function Block with Properties
|
|
107
|
-
|
|
108
|
-
```
|
|
109
|
-
Ask Claude: "Using the CODESYS Local server, please create a Motor Controller function block with Start and Stop methods, plus CurrentSpeed property."
|
|
110
|
-
```
|
|
111
|
-
|
|
112
|
-
Claude will execute these MCP tool calls:
|
|
113
|
-
1. `create_pou` to create the MotorController Function Block
|
|
114
|
-
2. `create_property` to add the CurrentSpeed property
|
|
115
|
-
3. `create_method` to add Start and Stop methods
|
|
116
|
-
4. `set_pou_code` to implement each method and property
|
|
117
|
-
|
|
118
|
-
### Opening and Compiling a Project
|
|
119
|
-
|
|
120
|
-
```
|
|
121
|
-
Ask Claude: "Can you open the project at C:/MyProjects/TestProject.project using CODESYS Local and compile it?"
|
|
122
|
-
```
|
|
123
|
-
|
|
124
|
-
Claude will:
|
|
125
|
-
1. Use `open_project` to open the specified file
|
|
126
|
-
2. Use `compile_project` to build the application
|
|
127
|
-
|
|
128
|
-
## 🔍 Troubleshooting
|
|
129
|
-
|
|
130
|
-
### Common Issues
|
|
131
|
-
|
|
132
|
-
- **Connection Failures**: Check Claude Desktop logs for error messages
|
|
133
|
-
- **Path Issues**: Ensure CODESYS executable path is correct
|
|
134
|
-
- **Profile Mismatch**: Verify profile name matches exactly
|
|
135
|
-
- **Permission Problems**: Ensure Claude has appropriate permissions
|
|
136
|
-
- **CODESYS Already Running**: Close other CODESYS instances
|
|
137
|
-
|
|
138
|
-
### Debug Logs
|
|
139
|
-
|
|
140
|
-
Check logs at:
|
|
141
|
-
- Windows: `C:\Users\<YourUsername>\AppData\Roaming\Claude\logs\`
|
|
142
|
-
|
|
143
|
-
## 🤝 Contributing
|
|
144
|
-
|
|
145
|
-
Contributions are welcome! See [CONTRIBUTING.md](CONTRIBUTING.md) for details.
|
|
146
|
-
|
|
147
|
-
## 📝 License
|
|
148
|
-
|
|
149
|
-
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
|
150
|
-
|
|
151
|
-
## 🙏 Acknowledgements
|
|
152
|
-
|
|
153
|
-
- The [CODESYS](https://www.codesys.com/) team for their scripting engine
|
|
154
|
-
- [Model Context Protocol](https://github.com/m-ld/model-context-protocol) project
|
|
155
|
-
- All contributors who have helped improve this toolkit
|
|
1
|
+
# @codesys/mcp-toolkit
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+

|
|
5
|
+
|
|
6
|
+
A Model Context Protocol (MCP) server for CODESYS V3 programming environments. This toolkit enables seamless interaction between MCP clients (like Claude Desktop) and CODESYS, allowing automation of project management, POU creation, code editing, and compilation tasks.
|
|
7
|
+
|
|
8
|
+
<p align="center">
|
|
9
|
+
<img src="https://github.com/yourusername/codesys-mcp-toolkit/raw/main/docs/assets/diagram.png" alt="CODESYS MCP Toolkit Architecture" width="600">
|
|
10
|
+
</p>
|
|
11
|
+
|
|
12
|
+
## 🌟 Features
|
|
13
|
+
|
|
14
|
+
- **Project Management**
|
|
15
|
+
- Open existing CODESYS projects
|
|
16
|
+
- Create new projects from templates
|
|
17
|
+
- Save project changes
|
|
18
|
+
|
|
19
|
+
- **POU Management**
|
|
20
|
+
- Create Programs, Function Blocks, and Functions
|
|
21
|
+
- Set declaration and implementation code
|
|
22
|
+
- Create properties and methods for Function Blocks
|
|
23
|
+
- Compile projects
|
|
24
|
+
|
|
25
|
+
- **MCP Resources**
|
|
26
|
+
- `codesys://project/status`: Check scripting status and project state
|
|
27
|
+
- `codesys://project/{+project_path}/structure`: Retrieve project structure
|
|
28
|
+
- `codesys://project/{+project_path}/pou/{+pou_path}/code`: Read POU code
|
|
29
|
+
|
|
30
|
+
- **MCP Tools**
|
|
31
|
+
- `open_project`: Open specified CODESYS project
|
|
32
|
+
- `create_project`: Create new CODESYS project
|
|
33
|
+
- `save_project`: Save currently open project
|
|
34
|
+
- `create_pou`: Create new Program, Function Block, or Function
|
|
35
|
+
- `set_pou_code`: Update code for a specified POU
|
|
36
|
+
- `create_property`: Create property in a Function Block
|
|
37
|
+
- `create_method`: Create method in a Function Block
|
|
38
|
+
- `compile_project`: Compile the open project
|
|
39
|
+
|
|
40
|
+
## 📋 Prerequisites
|
|
41
|
+
|
|
42
|
+
- **CODESYS V3**: A working CODESYS V3 installation (tested with 3.5 SP21) with **Scripting Engine** component enabled
|
|
43
|
+
- **Node.js**: Version 18 or later required
|
|
44
|
+
- **MCP Client**: An MCP-enabled application (e.g., Claude Desktop)
|
|
45
|
+
|
|
46
|
+
## 🚀 Installation
|
|
47
|
+
|
|
48
|
+
### Option 1: Install from npm (Recommended)
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
npm install -g @codesys/mcp-toolkit
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
This installs the package globally, making the `codesys-mcp-toolkit` command available in your terminal.
|
|
55
|
+
|
|
56
|
+
### Option 2: Install from Source
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
# Clone the repository
|
|
60
|
+
git clone https://github.com/yourusername/codesys-mcp-toolkit.git
|
|
61
|
+
cd codesys-mcp-toolkit
|
|
62
|
+
|
|
63
|
+
# Install dependencies
|
|
64
|
+
npm install
|
|
65
|
+
|
|
66
|
+
# Build the project
|
|
67
|
+
npm run build
|
|
68
|
+
|
|
69
|
+
# Install globally
|
|
70
|
+
npm install -g .
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## 🔧 Configuration
|
|
74
|
+
|
|
75
|
+
### Claude Desktop Configuration
|
|
76
|
+
|
|
77
|
+
1. Locate your Claude Desktop configuration file:
|
|
78
|
+
- Windows: `C:\Users\<YourUsername>\AppData\Roaming\Claude\claude_desktop_config.json`
|
|
79
|
+
|
|
80
|
+
2. Add the CODESYS MCP server entry:
|
|
81
|
+
|
|
82
|
+
```json
|
|
83
|
+
{
|
|
84
|
+
"mcpServers": {
|
|
85
|
+
"codesys_local": {
|
|
86
|
+
"command": "npx",
|
|
87
|
+
"args": [
|
|
88
|
+
"-y",
|
|
89
|
+
"@codesys/mcp-toolkit",
|
|
90
|
+
"--codesys-path", "C:\\Program Files\\CODESYS 3.5.21.0\\CODESYS\\Common\\CODESYS.exe",
|
|
91
|
+
"--codesys-profile", "CODESYS V3.5 SP21"
|
|
92
|
+
]
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
3. Customize the paths to match your CODESYS installation:
|
|
99
|
+
- `--codesys-path`: Path to your CODESYS.exe
|
|
100
|
+
- `--codesys-profile`: Your CODESYS profile name
|
|
101
|
+
|
|
102
|
+
4. Restart Claude Desktop to apply the changes
|
|
103
|
+
|
|
104
|
+
## 🛠️ Usage Examples
|
|
105
|
+
|
|
106
|
+
### Creating a Function Block with Properties
|
|
107
|
+
|
|
108
|
+
```
|
|
109
|
+
Ask Claude: "Using the CODESYS Local server, please create a Motor Controller function block with Start and Stop methods, plus CurrentSpeed property."
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Claude will execute these MCP tool calls:
|
|
113
|
+
1. `create_pou` to create the MotorController Function Block
|
|
114
|
+
2. `create_property` to add the CurrentSpeed property
|
|
115
|
+
3. `create_method` to add Start and Stop methods
|
|
116
|
+
4. `set_pou_code` to implement each method and property
|
|
117
|
+
|
|
118
|
+
### Opening and Compiling a Project
|
|
119
|
+
|
|
120
|
+
```
|
|
121
|
+
Ask Claude: "Can you open the project at C:/MyProjects/TestProject.project using CODESYS Local and compile it?"
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Claude will:
|
|
125
|
+
1. Use `open_project` to open the specified file
|
|
126
|
+
2. Use `compile_project` to build the application
|
|
127
|
+
|
|
128
|
+
## 🔍 Troubleshooting
|
|
129
|
+
|
|
130
|
+
### Common Issues
|
|
131
|
+
|
|
132
|
+
- **Connection Failures**: Check Claude Desktop logs for error messages
|
|
133
|
+
- **Path Issues**: Ensure CODESYS executable path is correct
|
|
134
|
+
- **Profile Mismatch**: Verify profile name matches exactly
|
|
135
|
+
- **Permission Problems**: Ensure Claude has appropriate permissions
|
|
136
|
+
- **CODESYS Already Running**: Close other CODESYS instances
|
|
137
|
+
|
|
138
|
+
### Debug Logs
|
|
139
|
+
|
|
140
|
+
Check logs at:
|
|
141
|
+
- Windows: `C:\Users\<YourUsername>\AppData\Roaming\Claude\logs\`
|
|
142
|
+
|
|
143
|
+
## 🤝 Contributing
|
|
144
|
+
|
|
145
|
+
Contributions are welcome! See [CONTRIBUTING.md](CONTRIBUTING.md) for details.
|
|
146
|
+
|
|
147
|
+
## 📝 License
|
|
148
|
+
|
|
149
|
+
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
|
150
|
+
|
|
151
|
+
## 🙏 Acknowledgements
|
|
152
|
+
|
|
153
|
+
- The [CODESYS](https://www.codesys.com/) team for their scripting engine
|
|
154
|
+
- [Model Context Protocol](https://github.com/m-ld/model-context-protocol) project
|
|
155
|
+
- All contributors who have helped improve this toolkit
|
|
Binary file
|
|
Binary file
|
package/dist/codesys_interop.js
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* - Creating temporary Python script files
|
|
8
8
|
* - Executing them via CODESYS's scripting engine
|
|
9
9
|
* - Capturing and processing results
|
|
10
|
+
* - Supporting both one-off script execution and persistent sessions
|
|
10
11
|
*
|
|
11
12
|
* IMPORTANT: Path handling for Windows is critical - paths with spaces require
|
|
12
13
|
* special handling to avoid the 'C:\Program' not recognized error.
|
|
@@ -60,6 +61,8 @@ const promises_1 = require("fs/promises");
|
|
|
60
61
|
const path = __importStar(require("path"));
|
|
61
62
|
const os = __importStar(require("os"));
|
|
62
63
|
const fs = __importStar(require("fs")); // Import fs for existsSync check
|
|
64
|
+
// Optional import for session manager
|
|
65
|
+
const sessionManager_1 = require("./sessionManager");
|
|
63
66
|
// Define expected success/error markers from the Python scripts
|
|
64
67
|
const SCRIPT_SUCCESS_MARKER = 'SCRIPT_SUCCESS';
|
|
65
68
|
const SCRIPT_ERROR_MARKER = 'SCRIPT_ERROR';
|
|
@@ -69,12 +72,13 @@ const SCRIPT_ERROR_MARKER = 'SCRIPT_ERROR';
|
|
|
69
72
|
* @param scriptContent The Python script code to execute.
|
|
70
73
|
* @param codesysExePath The full path to the CODESYS.exe executable (can contain spaces).
|
|
71
74
|
* @param codesysProfileName The name of the CODESYS profile to use for scripting.
|
|
75
|
+
* @param sessionManager Optional session manager for persistent session execution
|
|
72
76
|
* @returns A promise resolving to an object containing the success status and the script's output.
|
|
73
77
|
*
|
|
74
78
|
* NOTE: Using shell: true with careful command string quoting to handle
|
|
75
79
|
* CODESYS argument parsing quirks when launched non-interactively.
|
|
76
80
|
*/
|
|
77
|
-
function executeCodesysScript(scriptContent, codesysExePath, codesysProfileName) {
|
|
81
|
+
function executeCodesysScript(scriptContent, codesysExePath, codesysProfileName, sessionManager) {
|
|
78
82
|
return __awaiter(this, void 0, void 0, function* () {
|
|
79
83
|
// --- Pre-checks ---
|
|
80
84
|
if (!codesysExePath)
|
|
@@ -84,6 +88,29 @@ function executeCodesysScript(scriptContent, codesysExePath, codesysProfileName)
|
|
|
84
88
|
if (!fs.existsSync(codesysExePath))
|
|
85
89
|
throw new Error(`CODESYS executable not found at provided path: ${codesysExePath}`);
|
|
86
90
|
// --- End Pre-checks ---
|
|
91
|
+
// --- Check if we should use the persistent session ---
|
|
92
|
+
if (sessionManager) {
|
|
93
|
+
const sessionState = sessionManager.getState();
|
|
94
|
+
if (sessionState === sessionManager_1.SessionState.RUNNING || sessionState === sessionManager_1.SessionState.BUSY) {
|
|
95
|
+
process.stderr.write(`INTEROP: Using persistent session for script execution\n`);
|
|
96
|
+
try {
|
|
97
|
+
const result = yield sessionManager.executeScript(scriptContent);
|
|
98
|
+
return {
|
|
99
|
+
success: result.success,
|
|
100
|
+
output: result.output
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
catch (error) {
|
|
104
|
+
process.stderr.write(`INTEROP: Error executing script in persistent session: ${error.message}\n`);
|
|
105
|
+
process.stderr.write(`INTEROP: Falling back to one-time execution mode\n`);
|
|
106
|
+
// Continue with the non-persistent approach as fallback
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
process.stderr.write(`INTEROP: Session not in RUNNING/BUSY state (current: ${sessionState}). Using one-time execution.\n`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
// --- Standard one-time script execution ---
|
|
87
114
|
const tempDir = os.tmpdir();
|
|
88
115
|
const tempFileName = `codesys_script_${Date.now()}_${Math.random().toString(36).substring(2, 9)}.py`;
|
|
89
116
|
const tempFilePath = path.join(tempDir, tempFileName); // Path module uses OS-specific separators ('\' on Windows)
|
|
@@ -118,7 +145,7 @@ function executeCodesysScript(scriptContent, codesysExePath, codesysProfileName)
|
|
|
118
145
|
process.stderr.write(`INTEROP ENV: Forcing CWD for spawn: ${codesysDir}\n`); // Re-enabled CWD change
|
|
119
146
|
// --- Create modified environment (Re-enabled) ---
|
|
120
147
|
const spawnEnv = Object.assign({}, process.env);
|
|
121
|
-
const pathSeparator = ';'
|
|
148
|
+
const pathSeparator = os.platform() === 'win32' ? ';' : ':';
|
|
122
149
|
const originalPath = spawnEnv.PATH || '';
|
|
123
150
|
spawnEnv.PATH = `${codesysDir}${pathSeparator}${originalPath}`; // Prepend CODESYS dir to PATH
|
|
124
151
|
process.stderr.write(`INTEROP ENV: MODIFIED PATH for spawn (prepended): ${spawnEnv.PATH.substring(0, 100)}...\n`); // Re-enabled ENV change
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import sys, scriptengine as script_engine, os, traceback
|
|
2
|
+
project_open = False; project_name = "No project open"; project_path = "N/A"; scripting_ok = False
|
|
3
|
+
try:
|
|
4
|
+
scripting_ok = True; primary_project = script_engine.projects.primary
|
|
5
|
+
if primary_project:
|
|
6
|
+
project_open = True
|
|
7
|
+
try:
|
|
8
|
+
project_path = os.path.normcase(os.path.abspath(primary_project.path))
|
|
9
|
+
try:
|
|
10
|
+
project_name = primary_project.get_name() # Might fail
|
|
11
|
+
if not project_name: project_name = "Unnamed (path: %s)" % os.path.basename(project_path)
|
|
12
|
+
except: project_name = "Unnamed (path: %s)" % os.path.basename(project_path)
|
|
13
|
+
except Exception as e_path: project_path = "N/A (Error: %s)" % e_path; project_name = "Unnamed (Path Error)"
|
|
14
|
+
print("Project Open: %s" % project_open); print("Project Name: %s" % project_name)
|
|
15
|
+
print("Project Path: %s" % project_path); print("Scripting OK: %s" % scripting_ok)
|
|
16
|
+
print("SCRIPT_SUCCESS: Status check complete."); sys.exit(0)
|
|
17
|
+
except Exception as e:
|
|
18
|
+
error_message = "Error during status check: %s" % e
|
|
19
|
+
print(error_message); print("Scripting OK: False")
|
|
20
|
+
# traceback.print_exc() # Optional traceback
|
|
21
|
+
print("SCRIPT_ERROR: %s" % error_message); sys.exit(1)
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import sys, scriptengine as script_engine, os, traceback
|
|
2
|
+
|
|
3
|
+
# Import ensure_project_open function from the template
|
|
4
|
+
# This will be prepended by the script loader
|
|
5
|
+
def ensure_project_open(target_project_path):
|
|
6
|
+
# Function implementation will be included from ensure_project_open.py
|
|
7
|
+
pass
|
|
8
|
+
|
|
9
|
+
try:
|
|
10
|
+
print("DEBUG: compile_project script: Project='%s'" % PROJECT_FILE_PATH)
|
|
11
|
+
primary_project = ensure_project_open(PROJECT_FILE_PATH)
|
|
12
|
+
project_name = os.path.basename(PROJECT_FILE_PATH)
|
|
13
|
+
target_app = None
|
|
14
|
+
app_name = "N/A"
|
|
15
|
+
|
|
16
|
+
# Try getting active application first
|
|
17
|
+
try:
|
|
18
|
+
target_app = primary_project.active_application
|
|
19
|
+
if target_app:
|
|
20
|
+
app_name = getattr(target_app, 'get_name', lambda: "Unnamed App (Active)")()
|
|
21
|
+
print("DEBUG: Found active application: %s" % app_name)
|
|
22
|
+
except Exception as active_err:
|
|
23
|
+
print("WARN: Could not get active application: %s. Searching..." % active_err)
|
|
24
|
+
|
|
25
|
+
# If no active app, search for the first one
|
|
26
|
+
if not target_app:
|
|
27
|
+
print("DEBUG: Searching for first compilable application...")
|
|
28
|
+
apps = []
|
|
29
|
+
try:
|
|
30
|
+
# Search recursively through all project objects
|
|
31
|
+
all_children = primary_project.get_children(True)
|
|
32
|
+
for child in all_children:
|
|
33
|
+
# Check using the marker property and if build method exists
|
|
34
|
+
if hasattr(child, 'is_application') and child.is_application and hasattr(child, 'build'):
|
|
35
|
+
app_name_found = getattr(child, 'get_name', lambda: "Unnamed App")()
|
|
36
|
+
print("DEBUG: Found potential application object: %s" % app_name_found)
|
|
37
|
+
apps.append(child)
|
|
38
|
+
break # Take the first one found
|
|
39
|
+
except Exception as find_err: print("WARN: Error finding application object: %s" % find_err)
|
|
40
|
+
|
|
41
|
+
if not apps: raise RuntimeError("No compilable application found in project '%s'" % project_name)
|
|
42
|
+
target_app = apps[0]
|
|
43
|
+
app_name = getattr(target_app, 'get_name', lambda: "Unnamed App (First Found)")()
|
|
44
|
+
print("WARN: Compiling first found application: %s" % app_name)
|
|
45
|
+
|
|
46
|
+
print("DEBUG: Calling build() on app '%s'..." % app_name)
|
|
47
|
+
if not hasattr(target_app, 'build'):
|
|
48
|
+
raise TypeError("Selected object '%s' is not an application or doesn't support build()." % app_name)
|
|
49
|
+
|
|
50
|
+
# Execute the build
|
|
51
|
+
target_app.build();
|
|
52
|
+
print("DEBUG: Build command executed for application '%s'." % app_name)
|
|
53
|
+
|
|
54
|
+
# Check messages is harder without direct access to message store from script.
|
|
55
|
+
# Rely on CODESYS UI or log output for now.
|
|
56
|
+
print("Compile Initiated For Application: %s" % app_name)
|
|
57
|
+
print("In Project: %s" % project_name)
|
|
58
|
+
print("SCRIPT_SUCCESS: Application compilation initiated.")
|
|
59
|
+
sys.exit(0)
|
|
60
|
+
except Exception as e:
|
|
61
|
+
detailed_error = traceback.format_exc()
|
|
62
|
+
error_message = "Error initiating compilation for project %s: %s\\n%s" % (PROJECT_FILE_PATH, e, detailed_error)
|
|
63
|
+
print(error_message)
|
|
64
|
+
print("SCRIPT_ERROR: %s" % error_message)
|
|
65
|
+
sys.exit(1)
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import sys, scriptengine as script_engine, os, traceback
|
|
2
|
+
|
|
3
|
+
# Import ensure_project_open function from the template
|
|
4
|
+
# This will be prepended by the script loader
|
|
5
|
+
def ensure_project_open(target_project_path):
|
|
6
|
+
# Function implementation will be included from ensure_project_open.py
|
|
7
|
+
pass
|
|
8
|
+
|
|
9
|
+
# Import find_object_by_path function from the template
|
|
10
|
+
# This will be prepended by the script loader
|
|
11
|
+
def find_object_by_path_robust(start_node, full_path, target_type_name="object"):
|
|
12
|
+
# Function implementation will be included from find_object_by_path.py
|
|
13
|
+
pass
|
|
14
|
+
|
|
15
|
+
PARENT_POU_FULL_PATH = "{PARENT_POU_FULL_PATH}" # e.g., "Application/MyFB"
|
|
16
|
+
METHOD_NAME = "{METHOD_NAME}"
|
|
17
|
+
RETURN_TYPE = "{RETURN_TYPE}" # Can be empty string for no return type
|
|
18
|
+
# Optional: Language
|
|
19
|
+
# LANG_GUID_STR = "{LANG_GUID_STR}" # Example if needed
|
|
20
|
+
|
|
21
|
+
try:
|
|
22
|
+
print("DEBUG: create_method script: ParentPOU='%s', Name='%s', ReturnType='%s', Project='%s'" % (PARENT_POU_FULL_PATH, METHOD_NAME, RETURN_TYPE, PROJECT_FILE_PATH))
|
|
23
|
+
primary_project = ensure_project_open(PROJECT_FILE_PATH)
|
|
24
|
+
if not PARENT_POU_FULL_PATH: raise ValueError("Parent POU full path empty.")
|
|
25
|
+
if not METHOD_NAME: raise ValueError("Method name empty.")
|
|
26
|
+
# RETURN_TYPE can be empty
|
|
27
|
+
|
|
28
|
+
# Find the parent POU object
|
|
29
|
+
parent_pou_object = find_object_by_path_robust(primary_project, PARENT_POU_FULL_PATH, "parent POU")
|
|
30
|
+
if not parent_pou_object: raise ValueError("Parent POU object not found: %s" % PARENT_POU_FULL_PATH)
|
|
31
|
+
|
|
32
|
+
parent_pou_name = getattr(parent_pou_object, 'get_name', lambda: PARENT_POU_FULL_PATH)()
|
|
33
|
+
print("DEBUG: Found Parent POU object: %s" % parent_pou_name)
|
|
34
|
+
|
|
35
|
+
# Check if parent object supports creating methods (should implement ScriptIecLanguageMemberContainer)
|
|
36
|
+
if not hasattr(parent_pou_object, 'create_method'):
|
|
37
|
+
raise TypeError("Parent object '%s' of type %s does not support create_method." % (parent_pou_name, type(parent_pou_object).__name__))
|
|
38
|
+
|
|
39
|
+
# Default language to None (usually ST)
|
|
40
|
+
lang_guid = None
|
|
41
|
+
# Use None if RETURN_TYPE is empty string, otherwise use the string
|
|
42
|
+
actual_return_type = RETURN_TYPE if RETURN_TYPE else None
|
|
43
|
+
print("DEBUG: Calling create_method: Name='%s', ReturnType=%s, Lang=%s" % (METHOD_NAME, actual_return_type, lang_guid))
|
|
44
|
+
|
|
45
|
+
# Call the create_method method ON THE PARENT POU
|
|
46
|
+
new_method_object = parent_pou_object.create_method(
|
|
47
|
+
name=METHOD_NAME,
|
|
48
|
+
return_type=actual_return_type,
|
|
49
|
+
language=lang_guid # Pass None to use default
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
if new_method_object:
|
|
53
|
+
new_meth_name = getattr(new_method_object, 'get_name', lambda: METHOD_NAME)()
|
|
54
|
+
print("DEBUG: Method object created: %s" % new_meth_name)
|
|
55
|
+
|
|
56
|
+
# --- SAVE THE PROJECT TO PERSIST THE NEW METHOD OBJECT ---
|
|
57
|
+
try:
|
|
58
|
+
print("DEBUG: Saving Project (after method creation)...")
|
|
59
|
+
primary_project.save()
|
|
60
|
+
print("DEBUG: Project saved successfully after method creation.")
|
|
61
|
+
except Exception as save_err:
|
|
62
|
+
print("ERROR: Failed to save Project after creating method: %s" % save_err)
|
|
63
|
+
detailed_error = traceback.format_exc()
|
|
64
|
+
error_message = "Error saving Project after creating method '%s': %s\\n%s" % (METHOD_NAME, save_err, detailed_error)
|
|
65
|
+
print(error_message); print("SCRIPT_ERROR: %s" % error_message); sys.exit(1)
|
|
66
|
+
# --- END SAVING ---
|
|
67
|
+
|
|
68
|
+
print("Method Created: %s" % new_meth_name)
|
|
69
|
+
print("Parent POU: %s" % PARENT_POU_FULL_PATH)
|
|
70
|
+
print("Return Type: %s" % (RETURN_TYPE if RETURN_TYPE else "(None)"))
|
|
71
|
+
print("SCRIPT_SUCCESS: Method created successfully.")
|
|
72
|
+
sys.exit(0)
|
|
73
|
+
else:
|
|
74
|
+
error_message = "Failed to create method '%s' under '%s'. create_method returned None." % (METHOD_NAME, parent_pou_name)
|
|
75
|
+
print(error_message); print("SCRIPT_ERROR: %s" % error_message); sys.exit(1)
|
|
76
|
+
|
|
77
|
+
except Exception as e:
|
|
78
|
+
detailed_error = traceback.format_exc()
|
|
79
|
+
error_message = "Error creating method '%s' under POU '%s' in project '%s': %s\\n%s" % (METHOD_NAME, PARENT_POU_FULL_PATH, PROJECT_FILE_PATH, e, detailed_error)
|
|
80
|
+
print(error_message); print("SCRIPT_ERROR: %s" % error_message); sys.exit(1)
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import sys, scriptengine as script_engine, os, traceback
|
|
2
|
+
|
|
3
|
+
# Import ensure_project_open function from the template
|
|
4
|
+
# This will be prepended by the script loader
|
|
5
|
+
def ensure_project_open(target_project_path):
|
|
6
|
+
# Function implementation will be included from ensure_project_open.py
|
|
7
|
+
pass
|
|
8
|
+
|
|
9
|
+
# Import find_object_by_path function from the template
|
|
10
|
+
# This will be prepended by the script loader
|
|
11
|
+
def find_object_by_path_robust(start_node, full_path, target_type_name="object"):
|
|
12
|
+
# Function implementation will be included from find_object_by_path.py
|
|
13
|
+
pass
|
|
14
|
+
|
|
15
|
+
POU_NAME = "{POU_NAME}"
|
|
16
|
+
POU_TYPE_STR = "{POU_TYPE_STR}"
|
|
17
|
+
IMPL_LANGUAGE_STR = "{IMPL_LANGUAGE_STR}"
|
|
18
|
+
PARENT_PATH_REL = "{PARENT_PATH}"
|
|
19
|
+
|
|
20
|
+
pou_type_map = {
|
|
21
|
+
"Program": script_engine.PouType.Program,
|
|
22
|
+
"FunctionBlock": script_engine.PouType.FunctionBlock,
|
|
23
|
+
"Function": script_engine.PouType.Function
|
|
24
|
+
}
|
|
25
|
+
# Map common language names to ImplementationLanguages attributes if needed (optional, None usually works)
|
|
26
|
+
# lang_map = { "ST": script_engine.ImplementationLanguage.st, ... }
|
|
27
|
+
|
|
28
|
+
try:
|
|
29
|
+
print("DEBUG: create_pou script: Name='%s', Type='%s', Lang='%s', ParentPath='%s', Project='%s'" % (POU_NAME, POU_TYPE_STR, IMPL_LANGUAGE_STR, PARENT_PATH_REL, PROJECT_FILE_PATH))
|
|
30
|
+
primary_project = ensure_project_open(PROJECT_FILE_PATH)
|
|
31
|
+
if not POU_NAME: raise ValueError("POU name empty.")
|
|
32
|
+
if not PARENT_PATH_REL: raise ValueError("Parent path empty.")
|
|
33
|
+
|
|
34
|
+
# Resolve POU Type Enum
|
|
35
|
+
pou_type_enum = pou_type_map.get(POU_TYPE_STR)
|
|
36
|
+
if not pou_type_enum: raise ValueError("Invalid POU type string: %s. Use Program, FunctionBlock, or Function." % POU_TYPE_STR)
|
|
37
|
+
|
|
38
|
+
# Find parent object using the robust function
|
|
39
|
+
parent_object = find_object_by_path_robust(primary_project, PARENT_PATH_REL, "parent container")
|
|
40
|
+
if not parent_object: raise ValueError("Parent object not found for path: %s" % PARENT_PATH_REL)
|
|
41
|
+
|
|
42
|
+
parent_name = getattr(parent_object, 'get_name', lambda: str(parent_object))()
|
|
43
|
+
print("DEBUG: Using parent object: %s (Type: %s)" % (parent_name, type(parent_object).__name__))
|
|
44
|
+
|
|
45
|
+
# Check if parent object supports creating POUs (should implement ScriptIecLanguageObjectContainer)
|
|
46
|
+
if not hasattr(parent_object, 'create_pou'):
|
|
47
|
+
raise TypeError("Parent object '%s' of type %s does not support create_pou." % (parent_name, type(parent_object).__name__))
|
|
48
|
+
|
|
49
|
+
# Set language GUID to None (let CODESYS default based on parent/settings)
|
|
50
|
+
lang_guid = None
|
|
51
|
+
print("DEBUG: Setting language to None (will use default).")
|
|
52
|
+
# Example if mapping language string: lang_guid = lang_map.get(IMPL_LANGUAGE_STR, None)
|
|
53
|
+
|
|
54
|
+
print("DEBUG: Calling parent_object.create_pou: Name='%s', Type=%s, Lang=%s" % (POU_NAME, pou_type_enum, lang_guid))
|
|
55
|
+
|
|
56
|
+
# Call create_pou using keyword arguments
|
|
57
|
+
new_pou = parent_object.create_pou(
|
|
58
|
+
name=POU_NAME,
|
|
59
|
+
type=pou_type_enum,
|
|
60
|
+
language=lang_guid # Pass None
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
print("DEBUG: parent_object.create_pou returned: %s" % new_pou)
|
|
64
|
+
if new_pou:
|
|
65
|
+
new_pou_name = getattr(new_pou, 'get_name', lambda: POU_NAME)()
|
|
66
|
+
print("DEBUG: POU object created: %s" % new_pou_name)
|
|
67
|
+
|
|
68
|
+
# --- SAVE THE PROJECT TO PERSIST THE NEW POU ---
|
|
69
|
+
try:
|
|
70
|
+
print("DEBUG: Saving Project...")
|
|
71
|
+
primary_project.save() # Save the overall project file
|
|
72
|
+
print("DEBUG: Project saved successfully after POU creation.")
|
|
73
|
+
except Exception as save_err:
|
|
74
|
+
print("ERROR: Failed to save Project after POU creation: %s" % save_err)
|
|
75
|
+
detailed_error = traceback.format_exc()
|
|
76
|
+
error_message = "Error saving Project after creating POU '%s': %s\\n%s" % (new_pou_name, save_err, detailed_error)
|
|
77
|
+
print(error_message); print("SCRIPT_ERROR: %s" % error_message); sys.exit(1)
|
|
78
|
+
# --- END SAVING ---
|
|
79
|
+
|
|
80
|
+
print("POU Created: %s" % new_pou_name)
|
|
81
|
+
print("Type: %s" % POU_TYPE_STR)
|
|
82
|
+
print("Language: %s (Defaulted)" % IMPL_LANGUAGE_STR)
|
|
83
|
+
print("Parent Path: %s" % PARENT_PATH_REL)
|
|
84
|
+
print("SCRIPT_SUCCESS: POU created successfully.")
|
|
85
|
+
sys.exit(0)
|
|
86
|
+
else:
|
|
87
|
+
error_message = "Failed to create POU '%s'. create_pou returned None." % POU_NAME
|
|
88
|
+
print(error_message)
|
|
89
|
+
print("SCRIPT_ERROR: %s" % error_message)
|
|
90
|
+
sys.exit(1)
|
|
91
|
+
except Exception as e:
|
|
92
|
+
detailed_error = traceback.format_exc()
|
|
93
|
+
error_message = "Error creating POU '%s' in project '%s': %s\\n%s" % (POU_NAME, PROJECT_FILE_PATH, e, detailed_error)
|
|
94
|
+
print(error_message)
|
|
95
|
+
print("SCRIPT_ERROR: Error creating POU '%s': %s" % (POU_NAME, e))
|
|
96
|
+
sys.exit(1)
|