@codesys/mcp-toolkit 1.2.4 → 1.3.0
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/dist/scripts/session_status.py +107 -143
- package/dist/scripts/start_session.py +94 -56
- package/dist/scripts/stop_session.py +93 -70
- package/dist/sessionManager.js +54 -49
- package/package.json +1 -1
|
@@ -1,168 +1,132 @@
|
|
|
1
|
-
import sys, os, json, time
|
|
1
|
+
import sys, os, json, time
|
|
2
|
+
import tempfile
|
|
2
3
|
|
|
3
4
|
# Define markers for script results
|
|
4
5
|
SCRIPT_SUCCESS_MARKER = "SCRIPT_SUCCESS"
|
|
5
6
|
SCRIPT_ERROR_MARKER = "SCRIPT_ERROR"
|
|
6
7
|
|
|
7
|
-
# Session state
|
|
8
|
+
# Session state and command files
|
|
8
9
|
SESSION_DIR = os.path.join(tempfile.gettempdir(), "codesys_session")
|
|
9
10
|
SESSION_STATE_FILE = os.path.join(SESSION_DIR, "session_state.json")
|
|
10
|
-
|
|
11
|
-
|
|
11
|
+
COMMAND_FILE = os.path.join(SESSION_DIR, "command.json")
|
|
12
|
+
RESULT_FILE = os.path.join(SESSION_DIR, "result.json")
|
|
13
|
+
SESSION_LOCK_FILE = os.path.join(SESSION_DIR, "session.lock")
|
|
12
14
|
|
|
13
15
|
def check_session_status():
|
|
14
|
-
"""Check the status of a CODESYS session
|
|
16
|
+
"""Check the status of a CODESYS session"""
|
|
15
17
|
try:
|
|
16
18
|
# Check if session directory exists
|
|
17
19
|
if not os.path.exists(SESSION_DIR):
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
# Check if state file exists
|
|
20
|
+
print(f"{SCRIPT_SUCCESS_MARKER}: No session active. Session directory not found.")
|
|
21
|
+
return False
|
|
22
|
+
|
|
23
|
+
# Check if session state file exists
|
|
25
24
|
if not os.path.exists(SESSION_STATE_FILE):
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
# Read the state file
|
|
25
|
+
print(f"{SCRIPT_SUCCESS_MARKER}: No session active. Session state file not found.")
|
|
26
|
+
return False
|
|
27
|
+
|
|
28
|
+
# Read session state
|
|
33
29
|
with open(SESSION_STATE_FILE, 'r') as f:
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
# Get session duration if available
|
|
40
|
-
start_time = session_state.get("start_time", 0)
|
|
41
|
-
if start_time > 0:
|
|
42
|
-
current_time = time.time()
|
|
43
|
-
duration_secs = int(current_time - start_time)
|
|
44
|
-
hours, remainder = divmod(duration_secs, 3600)
|
|
45
|
-
minutes, seconds = divmod(remainder, 60)
|
|
46
|
-
duration = f"{hours:02d}:{minutes:02d}:{seconds:02d}"
|
|
47
|
-
else:
|
|
48
|
-
duration = "unknown"
|
|
49
|
-
|
|
50
|
-
# Last update time
|
|
51
|
-
last_update = session_state.get("last_update", 0)
|
|
52
|
-
last_update_str = "never"
|
|
53
|
-
if last_update > 0:
|
|
54
|
-
seconds_since_update = int(time.time() - last_update)
|
|
55
|
-
if seconds_since_update < 60:
|
|
56
|
-
last_update_str = f"{seconds_since_update} seconds ago"
|
|
57
|
-
elif seconds_since_update < 3600:
|
|
58
|
-
last_update_str = f"{seconds_since_update // 60} minutes ago"
|
|
59
|
-
else:
|
|
60
|
-
last_update_str = f"{seconds_since_update // 3600} hours ago"
|
|
61
|
-
|
|
62
|
-
return {
|
|
63
|
-
"session_exists": True,
|
|
64
|
-
"active": session_state.get("active", False),
|
|
65
|
-
"project_open": session_state.get("project_open", False),
|
|
66
|
-
"project_name": session_state.get("project_name", "unknown"),
|
|
67
|
-
"project_path": session_state.get("project_path", "unknown"),
|
|
68
|
-
"duration": duration,
|
|
69
|
-
"pipes_exist": pipes_exist,
|
|
70
|
-
"last_update": last_update_str,
|
|
71
|
-
"message": "Session information retrieved successfully"
|
|
72
|
-
}
|
|
73
|
-
except Exception as e:
|
|
74
|
-
return {
|
|
75
|
-
"session_exists": True,
|
|
76
|
-
"active": False,
|
|
77
|
-
"error": str(e),
|
|
78
|
-
"message": f"Error reading session state: {e}"
|
|
79
|
-
}
|
|
30
|
+
try:
|
|
31
|
+
session_state = json.load(f)
|
|
32
|
+
except json.JSONDecodeError:
|
|
33
|
+
print(f"{SCRIPT_ERROR_MARKER}: Invalid session state file")
|
|
34
|
+
return False
|
|
80
35
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
if not os.path.exists(COMMAND_PIPE) or not os.path.exists(RESULT_PIPE):
|
|
85
|
-
return False
|
|
86
|
-
|
|
87
|
-
ping_command = {
|
|
88
|
-
"id": f"ping_{time.time()}",
|
|
89
|
-
"type": "execute_script",
|
|
90
|
-
"script": "print('SCRIPT_SUCCESS: Ping successful')"
|
|
91
|
-
}
|
|
36
|
+
# Check if session is active
|
|
37
|
+
active = session_state.get('active', False)
|
|
38
|
+
status = session_state.get('status', 'unknown')
|
|
92
39
|
|
|
93
|
-
#
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
40
|
+
# Check session lock file to see if it's been updated recently
|
|
41
|
+
if os.path.exists(SESSION_LOCK_FILE):
|
|
42
|
+
try:
|
|
43
|
+
with open(SESSION_LOCK_FILE, 'r') as f:
|
|
44
|
+
lock_time = float(f.read().strip())
|
|
45
|
+
current_time = time.time()
|
|
46
|
+
time_diff = current_time - lock_time
|
|
47
|
+
|
|
48
|
+
# If lock file hasn't been updated in 30 seconds, session might be dead
|
|
49
|
+
if time_diff > 30:
|
|
50
|
+
print(f"{SCRIPT_SUCCESS_MARKER}: Session appears to be inactive (lock file stale by {time_diff:.1f} seconds)")
|
|
51
|
+
# But we'll leave the active flag as is, since it might just be busy
|
|
52
|
+
except Exception as e:
|
|
53
|
+
print(f"Error reading lock file: {e}")
|
|
102
54
|
|
|
103
|
-
#
|
|
104
|
-
|
|
105
|
-
|
|
55
|
+
# Execute a ping test by sending a simple command
|
|
56
|
+
if active and status in ["running", "busy"]:
|
|
57
|
+
# Try to ping the session
|
|
58
|
+
try:
|
|
59
|
+
# Check if command file exists
|
|
60
|
+
if os.path.exists(COMMAND_FILE):
|
|
61
|
+
# Create ping command
|
|
62
|
+
ping_command = {
|
|
63
|
+
"id": f"ping_{int(time.time())}",
|
|
64
|
+
"type": "execute_script",
|
|
65
|
+
"script": "print('SCRIPT_SUCCESS: Ping successful')",
|
|
66
|
+
"timestamp": time.time()
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
# Write command to file
|
|
70
|
+
command_data = {
|
|
71
|
+
"status": "pending",
|
|
72
|
+
"command": ping_command,
|
|
73
|
+
"timestamp": time.time()
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
with open(COMMAND_FILE, 'w') as f:
|
|
77
|
+
json.dump(command_data, f)
|
|
78
|
+
|
|
79
|
+
# Wait for result (with short timeout)
|
|
80
|
+
timeout = 5 # seconds
|
|
81
|
+
start_time = time.time()
|
|
82
|
+
ping_success = False
|
|
83
|
+
|
|
84
|
+
while not ping_success and (time.time() - start_time) < timeout:
|
|
85
|
+
# Check if result file exists
|
|
86
|
+
if not os.path.exists(RESULT_FILE):
|
|
87
|
+
time.sleep(0.5)
|
|
88
|
+
continue
|
|
89
|
+
|
|
90
|
+
try:
|
|
91
|
+
with open(RESULT_FILE, 'r') as f:
|
|
92
|
+
result_data = json.load(f)
|
|
93
|
+
|
|
94
|
+
if result_data.get("status") == "complete" and result_data.get("result"):
|
|
95
|
+
result = result_data.get("result")
|
|
96
|
+
if result.get('id') == ping_command['id'] and result.get('success', False):
|
|
97
|
+
ping_success = True
|
|
98
|
+
break
|
|
99
|
+
except Exception as e:
|
|
100
|
+
print(f"Error reading result: {e}")
|
|
101
|
+
time.sleep(0.5)
|
|
102
|
+
|
|
103
|
+
if not ping_success:
|
|
104
|
+
print(f"{SCRIPT_SUCCESS_MARKER}: Session appears to be unresponsive to ping")
|
|
105
|
+
return False
|
|
106
|
+
except Exception as e:
|
|
107
|
+
print(f"Error during ping test: {e}")
|
|
106
108
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
os.close(res_fd)
|
|
109
|
+
# Build status message
|
|
110
|
+
project_info = ""
|
|
111
|
+
if session_state.get('project_open', False):
|
|
112
|
+
project_name = session_state.get('project_name', 'Unknown')
|
|
113
|
+
project_path = session_state.get('project_path', 'Unknown')
|
|
114
|
+
project_info = f", Project: {project_name} ({project_path})"
|
|
114
115
|
|
|
115
|
-
if
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
return result.get("success", False)
|
|
119
|
-
except json.JSONDecodeError:
|
|
120
|
-
return False
|
|
116
|
+
if active and status in ["running", "busy"]:
|
|
117
|
+
print(f"{SCRIPT_SUCCESS_MARKER}: Session is active (Status: {status}{project_info})")
|
|
118
|
+
return True
|
|
121
119
|
else:
|
|
120
|
+
print(f"{SCRIPT_SUCCESS_MARKER}: Session is not active (Status: {status})")
|
|
122
121
|
return False
|
|
123
|
-
|
|
124
|
-
except Exception as e:
|
|
125
|
-
print(f"Error pinging session: {e}")
|
|
126
|
-
return False
|
|
127
122
|
|
|
128
|
-
def main():
|
|
129
|
-
try:
|
|
130
|
-
# Get session status
|
|
131
|
-
status = check_session_status()
|
|
132
|
-
|
|
133
|
-
# Print status information
|
|
134
|
-
print(f"Session Exists: {status.get('session_exists', False)}")
|
|
135
|
-
print(f"Session Active: {status.get('active', False)}")
|
|
136
|
-
|
|
137
|
-
# If session exists, print more details
|
|
138
|
-
if status.get("session_exists", False):
|
|
139
|
-
if status.get("active", False):
|
|
140
|
-
# Try to ping the session
|
|
141
|
-
is_responsive = ping_session()
|
|
142
|
-
print(f"Session Responsive: {is_responsive}")
|
|
143
|
-
|
|
144
|
-
# Print project information if available
|
|
145
|
-
if "project_open" in status:
|
|
146
|
-
print(f"Project Open: {status['project_open']}")
|
|
147
|
-
if "project_name" in status:
|
|
148
|
-
print(f"Project Name: {status['project_name']}")
|
|
149
|
-
if "project_path" in status:
|
|
150
|
-
print(f"Project Path: {status['project_path']}")
|
|
151
|
-
if "duration" in status:
|
|
152
|
-
print(f"Session Duration: {status['duration']}")
|
|
153
|
-
if "last_update" in status:
|
|
154
|
-
print(f"Last Activity: {status['last_update']}")
|
|
155
|
-
else:
|
|
156
|
-
print("Session is not active (stopped or crashed)")
|
|
157
|
-
|
|
158
|
-
# Print the final result marker
|
|
159
|
-
print(f"{SCRIPT_SUCCESS_MARKER}: Session status check complete")
|
|
160
|
-
sys.exit(0)
|
|
161
|
-
|
|
162
123
|
except Exception as e:
|
|
163
|
-
print(f"Error checking session status
|
|
164
|
-
|
|
165
|
-
sys.exit(1)
|
|
124
|
+
print(f"{SCRIPT_ERROR_MARKER}: Error checking session status - {str(e)}")
|
|
125
|
+
return False
|
|
166
126
|
|
|
127
|
+
# Main script
|
|
167
128
|
if __name__ == "__main__":
|
|
168
|
-
|
|
129
|
+
if check_session_status():
|
|
130
|
+
sys.exit(0)
|
|
131
|
+
else:
|
|
132
|
+
sys.exit(1)
|
|
@@ -1,49 +1,60 @@
|
|
|
1
|
-
import sys, os,
|
|
1
|
+
import sys, os, json, time, tempfile
|
|
2
2
|
import scriptengine as script_engine
|
|
3
3
|
|
|
4
4
|
# Define markers for script results
|
|
5
5
|
SCRIPT_SUCCESS_MARKER = "SCRIPT_SUCCESS"
|
|
6
6
|
SCRIPT_ERROR_MARKER = "SCRIPT_ERROR"
|
|
7
7
|
|
|
8
|
-
# Session state
|
|
8
|
+
# Session state and command files
|
|
9
9
|
SESSION_DIR = os.path.join(tempfile.gettempdir(), "codesys_session")
|
|
10
10
|
SESSION_STATE_FILE = os.path.join(SESSION_DIR, "session_state.json")
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
COMMAND_FILE = os.path.join(SESSION_DIR, "command.json")
|
|
12
|
+
RESULT_FILE = os.path.join(SESSION_DIR, "result.json")
|
|
13
|
+
SESSION_LOCK_FILE = os.path.join(SESSION_DIR, "session.lock")
|
|
14
|
+
POLL_INTERVAL = 0.5 # seconds
|
|
13
15
|
|
|
14
16
|
def setup_session_dir():
|
|
15
|
-
"""Create the session directory and
|
|
17
|
+
"""Create the session directory and initial files"""
|
|
16
18
|
try:
|
|
17
19
|
# Create session directory
|
|
18
20
|
if not os.path.exists(SESSION_DIR):
|
|
19
21
|
os.makedirs(SESSION_DIR)
|
|
20
22
|
|
|
21
|
-
# Create named pipes if they don't exist
|
|
22
|
-
if not os.path.exists(COMMAND_PIPE):
|
|
23
|
-
os.mkfifo(COMMAND_PIPE)
|
|
24
|
-
|
|
25
|
-
if not os.path.exists(RESULT_PIPE):
|
|
26
|
-
os.mkfifo(RESULT_PIPE)
|
|
27
|
-
|
|
28
23
|
# Initialize session state
|
|
29
24
|
session_state = {
|
|
30
25
|
"active": True,
|
|
26
|
+
"session_id": str(time.time()),
|
|
31
27
|
"start_time": time.time(),
|
|
28
|
+
"status": "running",
|
|
32
29
|
"project_open": False,
|
|
33
30
|
"project_name": "",
|
|
34
31
|
"project_path": ""
|
|
35
32
|
}
|
|
36
33
|
|
|
34
|
+
# Write session state
|
|
37
35
|
with open(SESSION_STATE_FILE, 'w') as f:
|
|
38
36
|
json.dump(session_state, f)
|
|
39
37
|
|
|
38
|
+
# Create empty command file
|
|
39
|
+
with open(COMMAND_FILE, 'w') as f:
|
|
40
|
+
json.dump({"status": "ready", "command": None, "timestamp": time.time()}, f)
|
|
41
|
+
|
|
42
|
+
# Create empty result file
|
|
43
|
+
with open(RESULT_FILE, 'w') as f:
|
|
44
|
+
json.dump({"status": "none", "result": None, "timestamp": time.time()}, f)
|
|
45
|
+
|
|
46
|
+
# Create lock file
|
|
47
|
+
with open(SESSION_LOCK_FILE, 'w') as f:
|
|
48
|
+
f.write(str(time.time()))
|
|
49
|
+
|
|
50
|
+
print(f"Session directory setup at: {SESSION_DIR}")
|
|
40
51
|
return True
|
|
41
52
|
except Exception as e:
|
|
42
53
|
print(f"Failed to set up session directory: {e}")
|
|
43
54
|
print(f"{SCRIPT_ERROR_MARKER}: Session setup failed - {str(e)}")
|
|
44
55
|
return False
|
|
45
56
|
|
|
46
|
-
def update_session_state():
|
|
57
|
+
def update_session_state(status="running"):
|
|
47
58
|
"""Update the session state file with current CODESYS state"""
|
|
48
59
|
try:
|
|
49
60
|
# Get current state
|
|
@@ -75,6 +86,7 @@ def update_session_state():
|
|
|
75
86
|
session_state["project_name"] = project_name
|
|
76
87
|
session_state["project_path"] = project_path
|
|
77
88
|
session_state["last_update"] = time.time()
|
|
89
|
+
session_state["status"] = status
|
|
78
90
|
|
|
79
91
|
# Write updated state
|
|
80
92
|
with open(SESSION_STATE_FILE, 'w') as f:
|
|
@@ -86,9 +98,9 @@ def update_session_state():
|
|
|
86
98
|
return False
|
|
87
99
|
|
|
88
100
|
def execute_command(command_data):
|
|
89
|
-
"""Execute a command
|
|
101
|
+
"""Execute a command"""
|
|
90
102
|
try:
|
|
91
|
-
cmd_type = command_data.get("type", "")
|
|
103
|
+
cmd_type = command_data.get("type", "execute_script")
|
|
92
104
|
script = command_data.get("script", "")
|
|
93
105
|
cmd_id = command_data.get("id", "unknown")
|
|
94
106
|
|
|
@@ -140,15 +152,8 @@ def execute_command(command_data):
|
|
|
140
152
|
result["success"] = True
|
|
141
153
|
result["output"] = f"{SCRIPT_SUCCESS_MARKER}: Session stopping"
|
|
142
154
|
|
|
143
|
-
# Update session state to mark as
|
|
144
|
-
|
|
145
|
-
session_state = json.load(f)
|
|
146
|
-
|
|
147
|
-
session_state["active"] = False
|
|
148
|
-
session_state["end_time"] = time.time()
|
|
149
|
-
|
|
150
|
-
with open(SESSION_STATE_FILE, 'w') as f:
|
|
151
|
-
json.dump(session_state, f)
|
|
155
|
+
# Update session state to mark as stopping
|
|
156
|
+
update_session_state("stopping")
|
|
152
157
|
|
|
153
158
|
# Return the result and signal to stop the loop
|
|
154
159
|
return result, True
|
|
@@ -171,54 +176,84 @@ def execute_command(command_data):
|
|
|
171
176
|
"error": str(e)
|
|
172
177
|
}, False
|
|
173
178
|
|
|
179
|
+
def check_command():
|
|
180
|
+
"""Check if there is a new command to execute"""
|
|
181
|
+
try:
|
|
182
|
+
# Read the command file
|
|
183
|
+
with open(COMMAND_FILE, 'r') as f:
|
|
184
|
+
command_data = json.load(f)
|
|
185
|
+
|
|
186
|
+
# Check if there's a new command
|
|
187
|
+
if command_data.get("status") == "pending" and command_data.get("command"):
|
|
188
|
+
# Extract the command
|
|
189
|
+
command = command_data.get("command")
|
|
190
|
+
|
|
191
|
+
# Mark as processing
|
|
192
|
+
command_data["status"] = "processing"
|
|
193
|
+
with open(COMMAND_FILE, 'w') as f:
|
|
194
|
+
json.dump(command_data, f)
|
|
195
|
+
|
|
196
|
+
# Execute the command
|
|
197
|
+
result, stop_requested = execute_command(command)
|
|
198
|
+
|
|
199
|
+
# Write the result
|
|
200
|
+
result_data = {
|
|
201
|
+
"status": "complete",
|
|
202
|
+
"result": result,
|
|
203
|
+
"timestamp": time.time()
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
with open(RESULT_FILE, 'w') as f:
|
|
207
|
+
json.dump(result_data, f)
|
|
208
|
+
|
|
209
|
+
# Reset command file
|
|
210
|
+
command_data = {
|
|
211
|
+
"status": "ready",
|
|
212
|
+
"command": None,
|
|
213
|
+
"timestamp": time.time()
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
with open(COMMAND_FILE, 'w') as f:
|
|
217
|
+
json.dump(command_data, f)
|
|
218
|
+
|
|
219
|
+
return stop_requested
|
|
220
|
+
|
|
221
|
+
return False
|
|
222
|
+
except Exception as e:
|
|
223
|
+
print(f"Error checking command: {e}")
|
|
224
|
+
return False
|
|
225
|
+
|
|
174
226
|
def command_loop():
|
|
175
|
-
"""Main loop to
|
|
227
|
+
"""Main loop to check for and execute commands"""
|
|
176
228
|
stop_requested = False
|
|
177
229
|
|
|
178
230
|
print(f"Starting command loop...")
|
|
179
|
-
print(f"Command
|
|
180
|
-
print(f"Result
|
|
231
|
+
print(f"Command file: {COMMAND_FILE}")
|
|
232
|
+
print(f"Result file: {RESULT_FILE}")
|
|
181
233
|
|
|
182
234
|
while not stop_requested:
|
|
183
235
|
try:
|
|
184
|
-
#
|
|
185
|
-
with open(
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
result, stop_requested = execute_command(command_data)
|
|
194
|
-
|
|
195
|
-
# Write the result to the result pipe
|
|
196
|
-
with open(RESULT_PIPE, 'w') as res_pipe:
|
|
197
|
-
res_pipe.write(json.dumps(result))
|
|
198
|
-
|
|
199
|
-
except json.JSONDecodeError as e:
|
|
200
|
-
# Invalid JSON
|
|
201
|
-
print(f"Invalid command JSON: {e}")
|
|
202
|
-
error_result = {
|
|
203
|
-
"id": "unknown",
|
|
204
|
-
"success": False,
|
|
205
|
-
"output": f"{SCRIPT_ERROR_MARKER}: Invalid command format",
|
|
206
|
-
"error": str(e)
|
|
207
|
-
}
|
|
208
|
-
with open(RESULT_PIPE, 'w') as res_pipe:
|
|
209
|
-
res_pipe.write(json.dumps(error_result))
|
|
236
|
+
# Update session lock file to indicate we're alive
|
|
237
|
+
with open(SESSION_LOCK_FILE, 'w') as f:
|
|
238
|
+
f.write(str(time.time()))
|
|
239
|
+
|
|
240
|
+
# Check for new commands
|
|
241
|
+
stop_requested = check_command()
|
|
242
|
+
|
|
243
|
+
# Sleep for a bit
|
|
244
|
+
time.sleep(POLL_INTERVAL)
|
|
210
245
|
|
|
211
246
|
except Exception as e:
|
|
212
247
|
print(f"Error in command loop: {e}")
|
|
213
|
-
#
|
|
214
|
-
time.sleep(
|
|
248
|
+
# Sleep for a bit
|
|
249
|
+
time.sleep(POLL_INTERVAL)
|
|
215
250
|
|
|
216
251
|
print("Command loop stopped.")
|
|
217
252
|
|
|
218
253
|
# Main script
|
|
219
254
|
def main():
|
|
220
255
|
try:
|
|
221
|
-
# Setup session directory and
|
|
256
|
+
# Setup session directory and files
|
|
222
257
|
if not setup_session_dir():
|
|
223
258
|
sys.exit(1)
|
|
224
259
|
|
|
@@ -229,6 +264,9 @@ def main():
|
|
|
229
264
|
print(f"{SCRIPT_SUCCESS_MARKER}: CODESYS session started")
|
|
230
265
|
command_loop()
|
|
231
266
|
|
|
267
|
+
# Update final state
|
|
268
|
+
update_session_state("stopped")
|
|
269
|
+
|
|
232
270
|
print(f"{SCRIPT_SUCCESS_MARKER}: CODESYS session stopped cleanly")
|
|
233
271
|
sys.exit(0)
|
|
234
272
|
except Exception as e:
|
|
@@ -1,94 +1,117 @@
|
|
|
1
|
-
import sys, os, json, time
|
|
1
|
+
import sys, os, json, time
|
|
2
|
+
import tempfile
|
|
2
3
|
|
|
3
4
|
# Define markers for script results
|
|
4
5
|
SCRIPT_SUCCESS_MARKER = "SCRIPT_SUCCESS"
|
|
5
6
|
SCRIPT_ERROR_MARKER = "SCRIPT_ERROR"
|
|
6
7
|
|
|
7
|
-
# Session state
|
|
8
|
+
# Session state and command files
|
|
8
9
|
SESSION_DIR = os.path.join(tempfile.gettempdir(), "codesys_session")
|
|
9
10
|
SESSION_STATE_FILE = os.path.join(SESSION_DIR, "session_state.json")
|
|
10
|
-
|
|
11
|
-
|
|
11
|
+
COMMAND_FILE = os.path.join(SESSION_DIR, "command.json")
|
|
12
|
+
RESULT_FILE = os.path.join(SESSION_DIR, "result.json")
|
|
13
|
+
SESSION_LOCK_FILE = os.path.join(SESSION_DIR, "session.lock")
|
|
12
14
|
|
|
13
|
-
def
|
|
14
|
-
"""
|
|
15
|
+
def stop_session():
|
|
16
|
+
"""Stop a running CODESYS session"""
|
|
15
17
|
try:
|
|
18
|
+
# Check if session directory exists
|
|
19
|
+
if not os.path.exists(SESSION_DIR):
|
|
20
|
+
print(f"{SCRIPT_ERROR_MARKER}: No session directory found at {SESSION_DIR}")
|
|
21
|
+
return False
|
|
22
|
+
|
|
23
|
+
# Check if session state file exists
|
|
16
24
|
if not os.path.exists(SESSION_STATE_FILE):
|
|
17
|
-
print(f"No session state file found at {SESSION_STATE_FILE}")
|
|
25
|
+
print(f"{SCRIPT_ERROR_MARKER}: No session state file found at {SESSION_STATE_FILE}")
|
|
18
26
|
return False
|
|
19
|
-
|
|
27
|
+
|
|
28
|
+
# Read session state
|
|
20
29
|
with open(SESSION_STATE_FILE, 'r') as f:
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
return False
|
|
30
|
+
try:
|
|
31
|
+
session_state = json.load(f)
|
|
32
|
+
except json.JSONDecodeError:
|
|
33
|
+
print(f"{SCRIPT_ERROR_MARKER}: Invalid session state file")
|
|
34
|
+
return False
|
|
27
35
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
36
|
+
# Check if session is active
|
|
37
|
+
if not session_state.get('active', False):
|
|
38
|
+
print(f"{SCRIPT_SUCCESS_MARKER}: Session is already stopped")
|
|
39
|
+
return True
|
|
40
|
+
|
|
41
|
+
# Check if command file exists
|
|
42
|
+
if not os.path.exists(COMMAND_FILE):
|
|
43
|
+
print(f"{SCRIPT_ERROR_MARKER}: Command file not found at {COMMAND_FILE}")
|
|
33
44
|
return False
|
|
34
|
-
|
|
45
|
+
|
|
46
|
+
# Create stop command
|
|
35
47
|
stop_command = {
|
|
36
|
-
"id": f"stop_{time.time()}",
|
|
48
|
+
"id": f"stop_{int(time.time())}",
|
|
37
49
|
"type": "stop",
|
|
38
|
-
"
|
|
50
|
+
"timestamp": time.time()
|
|
39
51
|
}
|
|
40
|
-
|
|
41
|
-
#
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
52
|
+
|
|
53
|
+
# Write command to file
|
|
54
|
+
command_data = {
|
|
55
|
+
"status": "pending",
|
|
56
|
+
"command": stop_command,
|
|
57
|
+
"timestamp": time.time()
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
with open(COMMAND_FILE, 'w') as f:
|
|
61
|
+
json.dump(command_data, f)
|
|
62
|
+
|
|
63
|
+
print(f"Stop command sent to session")
|
|
64
|
+
|
|
65
|
+
# Wait for result (with timeout)
|
|
66
|
+
timeout = 10 # seconds
|
|
67
|
+
start_time = time.time()
|
|
68
|
+
result_received = False
|
|
69
|
+
|
|
70
|
+
while not result_received and (time.time() - start_time) < timeout:
|
|
71
|
+
# Check if result file exists
|
|
72
|
+
if not os.path.exists(RESULT_FILE):
|
|
73
|
+
time.sleep(0.5)
|
|
74
|
+
continue
|
|
75
|
+
|
|
50
76
|
try:
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
77
|
+
with open(RESULT_FILE, 'r') as f:
|
|
78
|
+
result_data = json.load(f)
|
|
79
|
+
|
|
80
|
+
if result_data.get("status") == "complete" and result_data.get("result"):
|
|
81
|
+
result = result_data.get("result")
|
|
82
|
+
if result.get('id') == stop_command['id']:
|
|
83
|
+
result_received = True
|
|
84
|
+
print(f"Session stop result: {result.get('output', 'No output')}")
|
|
85
|
+
break
|
|
86
|
+
except Exception as e:
|
|
87
|
+
print(f"Error reading result: {e}")
|
|
88
|
+
time.sleep(0.5)
|
|
63
89
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
if not check_session_active():
|
|
68
|
-
print(f"{SCRIPT_SUCCESS_MARKER}: No active session found")
|
|
69
|
-
sys.exit(0)
|
|
70
|
-
|
|
71
|
-
# Send stop command
|
|
72
|
-
print("Sending stop command to CODESYS session...")
|
|
73
|
-
if send_stop_command():
|
|
74
|
-
print(f"{SCRIPT_SUCCESS_MARKER}: Session stop command sent successfully")
|
|
75
|
-
|
|
76
|
-
# Give the session a moment to clean up
|
|
77
|
-
time.sleep(1)
|
|
90
|
+
# Update session state to indicate it's stopped
|
|
91
|
+
with open(SESSION_STATE_FILE, 'r') as f:
|
|
92
|
+
session_state = json.load(f)
|
|
78
93
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
94
|
+
session_state['active'] = False
|
|
95
|
+
session_state['status'] = "stopped"
|
|
96
|
+
session_state['end_time'] = time.time()
|
|
97
|
+
|
|
98
|
+
with open(SESSION_STATE_FILE, 'w') as f:
|
|
99
|
+
json.dump(session_state, f)
|
|
100
|
+
|
|
101
|
+
if result_received:
|
|
102
|
+
print(f"{SCRIPT_SUCCESS_MARKER}: Session stopped successfully")
|
|
84
103
|
else:
|
|
85
|
-
print(f"{SCRIPT_ERROR_MARKER}:
|
|
86
|
-
|
|
87
|
-
|
|
104
|
+
print(f"{SCRIPT_ERROR_MARKER}: Timeout waiting for session to stop")
|
|
105
|
+
|
|
106
|
+
return result_received
|
|
107
|
+
|
|
88
108
|
except Exception as e:
|
|
89
|
-
print(f"Error stopping session
|
|
90
|
-
|
|
91
|
-
sys.exit(1)
|
|
109
|
+
print(f"{SCRIPT_ERROR_MARKER}: Error stopping session - {str(e)}")
|
|
110
|
+
return False
|
|
92
111
|
|
|
112
|
+
# Main script
|
|
93
113
|
if __name__ == "__main__":
|
|
94
|
-
|
|
114
|
+
if stop_session():
|
|
115
|
+
sys.exit(0)
|
|
116
|
+
else:
|
|
117
|
+
sys.exit(1)
|
package/dist/sessionManager.js
CHANGED
|
@@ -63,11 +63,12 @@ const scriptLoader_1 = require("./scriptLoader");
|
|
|
63
63
|
const DEFAULT_SCRIPT_TIMEOUT = 60000; // 60 seconds
|
|
64
64
|
const DEFAULT_SESSION_IDLE_TIMEOUT = 300000; // 5 minutes
|
|
65
65
|
const DEFAULT_SESSION_POLL_INTERVAL = 1000; // 1 second
|
|
66
|
-
// Session directory and
|
|
66
|
+
// Session directory and file names
|
|
67
67
|
const SESSION_DIR = path.join(os.tmpdir(), 'codesys_session');
|
|
68
|
-
const
|
|
69
|
-
const
|
|
68
|
+
const COMMAND_FILE = 'command.json';
|
|
69
|
+
const RESULT_FILE = 'result.json';
|
|
70
70
|
const STATE_FILE = 'session_state.json';
|
|
71
|
+
const SESSION_LOCK_FILE = 'session.lock';
|
|
71
72
|
// Define expected success/error markers from the Python scripts
|
|
72
73
|
const SCRIPT_SUCCESS_MARKER = 'SCRIPT_SUCCESS';
|
|
73
74
|
const SCRIPT_ERROR_MARKER = 'SCRIPT_ERROR';
|
|
@@ -120,11 +121,12 @@ class CodesysSessionManager extends events_1.EventEmitter {
|
|
|
120
121
|
this.codesysProfileName = codesysProfileName;
|
|
121
122
|
this.idleTimeoutMs = idleTimeoutMs;
|
|
122
123
|
this.sessionId = (0, uuid_1.v4)();
|
|
123
|
-
// Set up session directory and
|
|
124
|
+
// Set up session directory and files
|
|
124
125
|
this.sessionDir = path.join(SESSION_DIR, this.sessionId);
|
|
125
|
-
this.
|
|
126
|
-
this.
|
|
126
|
+
this.commandFilePath = path.join(this.sessionDir, COMMAND_FILE);
|
|
127
|
+
this.resultFilePath = path.join(this.sessionDir, RESULT_FILE);
|
|
127
128
|
this.stateFilePath = path.join(this.sessionDir, STATE_FILE);
|
|
129
|
+
this.lockFilePath = path.join(this.sessionDir, SESSION_LOCK_FILE);
|
|
128
130
|
// Log status changes through event emitter
|
|
129
131
|
this.on(exports.SESSION_EVENTS.STARTED, () => {
|
|
130
132
|
console.error(`SESSION_MANAGER: Session started successfully`);
|
|
@@ -247,8 +249,8 @@ class CodesysSessionManager extends events_1.EventEmitter {
|
|
|
247
249
|
// Interpolate parameters
|
|
248
250
|
const scriptContent = (0, scriptLoader_1.interpolateScript)(scriptTemplate, {
|
|
249
251
|
SESSION_DIR: this.sessionDir,
|
|
250
|
-
|
|
251
|
-
|
|
252
|
+
COMMAND_FILE: COMMAND_FILE,
|
|
253
|
+
RESULT_FILE: RESULT_FILE,
|
|
252
254
|
STATE_FILE: STATE_FILE,
|
|
253
255
|
SESSION_ID: this.sessionId,
|
|
254
256
|
});
|
|
@@ -319,10 +321,10 @@ class CodesysSessionManager extends events_1.EventEmitter {
|
|
|
319
321
|
const startTime = Date.now();
|
|
320
322
|
while (Date.now() - startTime < timeoutMs) {
|
|
321
323
|
try {
|
|
322
|
-
// Check if
|
|
323
|
-
const
|
|
324
|
-
const
|
|
325
|
-
if (
|
|
324
|
+
// Check if files exist
|
|
325
|
+
const commandFileExists = fs.existsSync(this.commandFilePath);
|
|
326
|
+
const resultFileExists = fs.existsSync(this.resultFilePath);
|
|
327
|
+
if (commandFileExists && resultFileExists) {
|
|
326
328
|
// Read state file to check if session is ready
|
|
327
329
|
const stateContent = yield (0, promises_1.readFile)(this.stateFilePath, 'utf8');
|
|
328
330
|
const stateData = JSON.parse(stateContent);
|
|
@@ -366,14 +368,14 @@ class CodesysSessionManager extends events_1.EventEmitter {
|
|
|
366
368
|
}
|
|
367
369
|
try {
|
|
368
370
|
// Check if we have a running session
|
|
369
|
-
if (fs.existsSync(this.sessionDir) && fs.existsSync(this.
|
|
371
|
+
if (fs.existsSync(this.sessionDir) && fs.existsSync(this.commandFilePath)) {
|
|
370
372
|
// Load the stop_session script template
|
|
371
373
|
const scriptTemplate = yield (0, scriptLoader_1.loadScriptTemplate)('stop_session');
|
|
372
374
|
// Interpolate parameters
|
|
373
375
|
const scriptContent = (0, scriptLoader_1.interpolateScript)(scriptTemplate, {
|
|
374
376
|
SESSION_DIR: this.sessionDir,
|
|
375
|
-
|
|
376
|
-
|
|
377
|
+
COMMAND_FILE: COMMAND_FILE,
|
|
378
|
+
RESULT_FILE: RESULT_FILE,
|
|
377
379
|
STATE_FILE: STATE_FILE,
|
|
378
380
|
SESSION_ID: this.sessionId,
|
|
379
381
|
});
|
|
@@ -524,15 +526,15 @@ class CodesysSessionManager extends events_1.EventEmitter {
|
|
|
524
526
|
this.resetIdleTimeout();
|
|
525
527
|
// Mark the session as busy
|
|
526
528
|
this.sessionState = exports.SessionState.BUSY;
|
|
527
|
-
// Check if the command
|
|
528
|
-
if (!fs.existsSync(this.
|
|
529
|
+
// Check if the command file exists
|
|
530
|
+
if (!fs.existsSync(this.commandFilePath)) {
|
|
529
531
|
this.sessionState = exports.SessionState.ERROR;
|
|
530
|
-
throw new Error(`Command
|
|
532
|
+
throw new Error(`Command file not found at ${this.commandFilePath}`);
|
|
531
533
|
}
|
|
532
|
-
// Check if the result
|
|
533
|
-
if (!fs.existsSync(this.
|
|
534
|
+
// Check if the result file exists
|
|
535
|
+
if (!fs.existsSync(this.resultFilePath)) {
|
|
534
536
|
this.sessionState = exports.SessionState.ERROR;
|
|
535
|
-
throw new Error(`Result
|
|
537
|
+
throw new Error(`Result file not found at ${this.resultFilePath}`);
|
|
536
538
|
}
|
|
537
539
|
// Return a promise that will be resolved when script execution completes
|
|
538
540
|
return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
|
|
@@ -571,15 +573,21 @@ class CodesysSessionManager extends events_1.EventEmitter {
|
|
|
571
573
|
// Create the command to send
|
|
572
574
|
const command = {
|
|
573
575
|
id: scriptId,
|
|
576
|
+
type: "execute_script",
|
|
574
577
|
script: scriptContent,
|
|
575
578
|
timestamp: Date.now()
|
|
576
579
|
};
|
|
577
|
-
//
|
|
578
|
-
const
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
580
|
+
// Prepare the command data
|
|
581
|
+
const commandData = {
|
|
582
|
+
status: "pending",
|
|
583
|
+
command: command,
|
|
584
|
+
timestamp: Date.now()
|
|
585
|
+
};
|
|
586
|
+
// Send the command by writing to the command file
|
|
587
|
+
fs.writeFileSync(this.commandFilePath, JSON.stringify(commandData));
|
|
588
|
+
console.error(`SESSION_MANAGER: Command written to file`);
|
|
589
|
+
// Start reading the result file
|
|
590
|
+
this.readResultFile(scriptId);
|
|
583
591
|
}
|
|
584
592
|
catch (error) {
|
|
585
593
|
console.error(`SESSION_MANAGER: Error executing script: ${error.message}`);
|
|
@@ -599,12 +607,12 @@ class CodesysSessionManager extends events_1.EventEmitter {
|
|
|
599
607
|
});
|
|
600
608
|
}
|
|
601
609
|
/**
|
|
602
|
-
* Read the result
|
|
610
|
+
* Read the result file for a specific script execution
|
|
603
611
|
* @param scriptId The script ID to read results for
|
|
604
612
|
*/
|
|
605
|
-
|
|
606
|
-
console.error(`SESSION_MANAGER: Starting to read result
|
|
607
|
-
// We'll use a polling approach to read from the
|
|
613
|
+
readResultFile(scriptId) {
|
|
614
|
+
console.error(`SESSION_MANAGER: Starting to read result file for script ${scriptId}`);
|
|
615
|
+
// We'll use a polling approach to read from the file
|
|
608
616
|
const pollInterval = setInterval(() => __awaiter(this, void 0, void 0, function* () {
|
|
609
617
|
try {
|
|
610
618
|
if (!this.pendingScripts.has(scriptId)) {
|
|
@@ -612,21 +620,19 @@ class CodesysSessionManager extends events_1.EventEmitter {
|
|
|
612
620
|
clearInterval(pollInterval);
|
|
613
621
|
return;
|
|
614
622
|
}
|
|
615
|
-
// Check if the result
|
|
616
|
-
if (!fs.existsSync(this.
|
|
617
|
-
console.error(`SESSION_MANAGER: Result
|
|
623
|
+
// Check if the result file exists
|
|
624
|
+
if (!fs.existsSync(this.resultFilePath)) {
|
|
625
|
+
console.error(`SESSION_MANAGER: Result file not found at ${this.resultFilePath}`);
|
|
618
626
|
return;
|
|
619
627
|
}
|
|
620
|
-
// Read the result
|
|
621
|
-
const resultContent = yield (0, promises_1.readFile)(this.
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
if
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
const result = JSON.parse(line);
|
|
629
|
-
// Check if this result is for our script
|
|
628
|
+
// Read the result file
|
|
629
|
+
const resultContent = yield (0, promises_1.readFile)(this.resultFilePath, 'utf8');
|
|
630
|
+
try {
|
|
631
|
+
// Parse the result data
|
|
632
|
+
const resultData = JSON.parse(resultContent);
|
|
633
|
+
// Check if the result is complete and for our script
|
|
634
|
+
if (resultData.status === 'complete' && resultData.result) {
|
|
635
|
+
const result = resultData.result;
|
|
630
636
|
if (result.id === scriptId) {
|
|
631
637
|
console.error(`SESSION_MANAGER: Found result for script ${scriptId}`);
|
|
632
638
|
// Remove from pending scripts
|
|
@@ -647,14 +653,13 @@ class CodesysSessionManager extends events_1.EventEmitter {
|
|
|
647
653
|
return;
|
|
648
654
|
}
|
|
649
655
|
}
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
}
|
|
656
|
+
}
|
|
657
|
+
catch (parseError) {
|
|
658
|
+
console.error(`SESSION_MANAGER: Error parsing result data: ${parseError}`);
|
|
654
659
|
}
|
|
655
660
|
}
|
|
656
661
|
catch (error) {
|
|
657
|
-
console.error(`SESSION_MANAGER: Error reading result
|
|
662
|
+
console.error(`SESSION_MANAGER: Error reading result file: ${error}`);
|
|
658
663
|
}
|
|
659
664
|
}), 100); // Poll every 100ms
|
|
660
665
|
// Set a timeout to stop polling after the script timeout
|