@codesys/mcp-toolkit 1.5.8 → 2.0.0-alpha.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/CHANGELOG.md +153 -189
- package/LICENSE +21 -21
- package/README.md +224 -107
- package/dist/actions.d.ts +28 -0
- package/dist/actions.js +98 -0
- package/dist/actions.js.map +1 -0
- package/dist/bridge/bridge.d.ts +105 -0
- package/dist/bridge/bridge.js +400 -0
- package/dist/bridge/bridge.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +196 -0
- package/dist/cli.js.map +1 -0
- package/dist/codesys/cache.d.ts +48 -0
- package/dist/codesys/cache.js +82 -0
- package/dist/codesys/cache.js.map +1 -0
- package/dist/codesys/changes.d.ts +57 -0
- package/dist/codesys/changes.js +133 -0
- package/dist/codesys/changes.js.map +1 -0
- package/dist/codesys/decl.d.ts +45 -0
- package/dist/codesys/decl.js +251 -0
- package/dist/codesys/decl.js.map +1 -0
- package/dist/codesys/devices.d.ts +34 -0
- package/dist/codesys/devices.js +82 -0
- package/dist/codesys/devices.js.map +1 -0
- package/dist/codesys/diff.d.ts +13 -0
- package/dist/codesys/diff.js +61 -0
- package/dist/codesys/diff.js.map +1 -0
- package/dist/codesys/install.d.ts +40 -0
- package/dist/codesys/install.js +144 -0
- package/dist/codesys/install.js.map +1 -0
- package/dist/codesys/library-browse.d.ts +19 -0
- package/dist/codesys/library-browse.js +57 -0
- package/dist/codesys/library-browse.js.map +1 -0
- package/dist/codesys/outline.d.ts +32 -0
- package/dist/codesys/outline.js +219 -0
- package/dist/codesys/outline.js.map +1 -0
- package/dist/codesys/simulation.d.ts +34 -0
- package/dist/codesys/simulation.js +57 -0
- package/dist/codesys/simulation.js.map +1 -0
- package/dist/codesys/st.d.ts +36 -0
- package/dist/codesys/st.js +69 -0
- package/dist/codesys/st.js.map +1 -0
- package/dist/daemon.d.ts +46 -0
- package/dist/daemon.js +173 -0
- package/dist/daemon.js.map +1 -0
- package/dist/driver.d.ts +25 -0
- package/dist/driver.js +33 -0
- package/dist/driver.js.map +1 -0
- package/dist/panel/sim-panel.html +357 -0
- package/dist/panel/viewer.html +126 -0
- package/dist/panel-types.d.ts +20 -0
- package/dist/panel-types.js +3 -0
- package/dist/panel-types.js.map +1 -0
- package/dist/panel.d.ts +27 -0
- package/dist/panel.js +208 -0
- package/dist/panel.js.map +1 -0
- package/dist/server.d.ts +5 -0
- package/dist/server.js +33 -974
- package/dist/server.js.map +1 -0
- package/dist/tools.d.ts +103 -0
- package/dist/tools.js +793 -0
- package/dist/tools.js.map +1 -0
- package/dist/viewer.d.ts +14 -0
- package/dist/viewer.js +125 -0
- package/dist/viewer.js.map +1 -0
- package/package.json +37 -21
- package/watcher/mcp_compat.py +147 -0
- package/watcher/mcp_ops.py +1178 -0
- package/watcher/mcp_sim.py +246 -0
- package/watcher/watcher.py +255 -0
- package/.claude/settings.local.json +0 -29
- package/CLAUDE.md +0 -26
- package/CONTRIBUTING.md +0 -72
- package/README.md.enhanced +0 -155
- package/codesys-mcp-toolkit-1.2.0.tgz +0 -0
- package/codesys-mcp-toolkit-1.2.1.tgz +0 -0
- package/codesys-mcp-toolkit-1.5.7.tgz +0 -0
- package/codesys-mcp-toolkit-1.5.8.tgz +0 -0
- package/dist/bin.js +0 -75
- package/dist/check_session_paths.js +0 -149
- package/dist/codesys_interop.js +0 -435
- package/dist/direct_session_command.js +0 -293
- package/dist/scriptLoader.js +0 -165
- package/dist/scripts/check_status.py +0 -21
- package/dist/scripts/compile_project.py +0 -65
- package/dist/scripts/create_method.py +0 -80
- package/dist/scripts/create_pou.py +0 -102
- package/dist/scripts/create_project.py +0 -116
- package/dist/scripts/create_property.py +0 -78
- package/dist/scripts/ensure_project_open.py +0 -154
- package/dist/scripts/find_object_by_path.py +0 -111
- package/dist/scripts/get_pou_code.py +0 -91
- package/dist/scripts/get_project_structure.py +0 -72
- package/dist/scripts/open_project.py +0 -25
- package/dist/scripts/save_project.py +0 -29
- package/dist/scripts/session_status.py +0 -281
- package/dist/scripts/set_pou_code.py +0 -109
- package/dist/scripts/start_session.py +0 -809
- package/dist/scripts/stop_session.py +0 -324
- package/dist/sessionManager.js +0 -798
- package/docs/configuration.md +0 -141
- package/docs/index.md +0 -26
- package/docs/installation.md +0 -84
- package/docs/session_management.md +0 -110
- package/examples/README.md +0 -78
- package/examples/sample_config.json +0 -32
- package/replacements_session_status.txt +0 -16
- package/scriptDoc.txt +0 -8679
- package/scripts/build.js +0 -25
- package/scripts/fix_fstrings.py +0 -76
|
@@ -1,72 +0,0 @@
|
|
|
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
|
-
def get_object_structure(obj, indent=0, max_depth=10): # Add max_depth
|
|
10
|
-
lines = []; indent_str = " " * indent
|
|
11
|
-
if indent > max_depth:
|
|
12
|
-
lines.append("%s- Max recursion depth reached." % indent_str)
|
|
13
|
-
return lines
|
|
14
|
-
try:
|
|
15
|
-
name = "Unnamed"; obj_type = type(obj).__name__
|
|
16
|
-
guid_str = ""
|
|
17
|
-
folder_str = ""
|
|
18
|
-
try:
|
|
19
|
-
name = getattr(obj, 'get_name', lambda: "Unnamed")() or "Unnamed" # Safer get_name
|
|
20
|
-
if hasattr(obj, 'guid'): guid_str = " {%s}" % obj.guid
|
|
21
|
-
if hasattr(obj, 'is_folder') and obj.is_folder: folder_str = " [Folder]"
|
|
22
|
-
except Exception as name_err:
|
|
23
|
-
print("WARN: Error getting name/guid/folder status for an object: %s" % name_err)
|
|
24
|
-
name = "!!! Error Getting Name !!!"
|
|
25
|
-
|
|
26
|
-
lines.append("%s- %s (%s)%s%s" % (indent_str, name, obj_type, folder_str, guid_str))
|
|
27
|
-
|
|
28
|
-
# Get children only if the object potentially has them
|
|
29
|
-
children = []
|
|
30
|
-
can_have_children = hasattr(obj, 'get_children') and (
|
|
31
|
-
not hasattr(obj, 'is_folder') or # If it's not clear if it's a folder (e.g., project root)
|
|
32
|
-
(hasattr(obj, 'is_folder') and obj.is_folder) or # If it is a folder
|
|
33
|
-
# Add known container types explicitly, check marker interfaces too
|
|
34
|
-
hasattr(obj, 'is_project') or hasattr(obj, 'is_application') or hasattr(obj, 'is_device') or hasattr(obj,'is_pou')
|
|
35
|
-
)
|
|
36
|
-
|
|
37
|
-
if can_have_children:
|
|
38
|
-
try:
|
|
39
|
-
children = obj.get_children(False)
|
|
40
|
-
# print("DEBUG: %s has %d children" % (name, len(children))) # Verbose
|
|
41
|
-
except Exception as get_child_err:
|
|
42
|
-
lines.append("%s ERROR getting children: %s" % (indent_str, get_child_err))
|
|
43
|
-
# traceback.print_exc() # Optional
|
|
44
|
-
|
|
45
|
-
for child in children:
|
|
46
|
-
lines.extend(get_object_structure(child, indent + 1, max_depth)) # Recurse
|
|
47
|
-
|
|
48
|
-
except Exception as e:
|
|
49
|
-
lines.append("%s- Error processing node: %s" % (indent_str, e))
|
|
50
|
-
traceback.print_exc() # Print detailed error for this node
|
|
51
|
-
return lines
|
|
52
|
-
|
|
53
|
-
try:
|
|
54
|
-
print("DEBUG: Getting structure for: %s" % PROJECT_FILE_PATH)
|
|
55
|
-
primary_project = ensure_project_open(PROJECT_FILE_PATH)
|
|
56
|
-
project_name = os.path.basename(PROJECT_FILE_PATH)
|
|
57
|
-
print("DEBUG: Getting structure for project: %s" % project_name)
|
|
58
|
-
# Use the project object obtained from ensure_project_open
|
|
59
|
-
structure_list = get_object_structure(primary_project, max_depth=15) # Set a reasonable depth
|
|
60
|
-
structure_output = "\\n".join(structure_list)
|
|
61
|
-
# Ensure markers are printed distinctly
|
|
62
|
-
print("\\n--- PROJECT STRUCTURE START ---")
|
|
63
|
-
print(structure_output)
|
|
64
|
-
print("--- PROJECT STRUCTURE END ---\\n")
|
|
65
|
-
print("SCRIPT_SUCCESS: Project structure retrieved.")
|
|
66
|
-
sys.exit(0)
|
|
67
|
-
except Exception as e:
|
|
68
|
-
detailed_error = traceback.format_exc()
|
|
69
|
-
error_message = "Error getting structure for %s: %s\\n%s" % (PROJECT_FILE_PATH, e, detailed_error)
|
|
70
|
-
print(error_message)
|
|
71
|
-
print("SCRIPT_ERROR: %s" % error_message)
|
|
72
|
-
sys.exit(1)
|
|
@@ -1,25 +0,0 @@
|
|
|
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
|
-
project = ensure_project_open(PROJECT_FILE_PATH)
|
|
11
|
-
# Get name from object if possible, otherwise use path basename
|
|
12
|
-
proj_name = "Unknown"
|
|
13
|
-
try:
|
|
14
|
-
if project: proj_name = project.get_name() or os.path.basename(PROJECT_FILE_PATH)
|
|
15
|
-
else: proj_name = os.path.basename(PROJECT_FILE_PATH) + " (ensure_project_open returned None?)"
|
|
16
|
-
except Exception:
|
|
17
|
-
proj_name = os.path.basename(PROJECT_FILE_PATH) + " (name retrieval failed)"
|
|
18
|
-
print("Project Opened: %s" % proj_name)
|
|
19
|
-
print("SCRIPT_SUCCESS: Project opened successfully.")
|
|
20
|
-
sys.exit(0)
|
|
21
|
-
except Exception as e:
|
|
22
|
-
error_message = "Error opening project %s: %s" % (PROJECT_FILE_PATH, e)
|
|
23
|
-
print(error_message)
|
|
24
|
-
traceback.print_exc()
|
|
25
|
-
print("SCRIPT_ERROR: %s" % error_message); sys.exit(1)
|
|
@@ -1,29 +0,0 @@
|
|
|
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
|
-
primary_project = ensure_project_open(PROJECT_FILE_PATH)
|
|
11
|
-
# Get name from object if possible, otherwise use path basename
|
|
12
|
-
project_name = "Unknown"
|
|
13
|
-
try:
|
|
14
|
-
if primary_project: project_name = primary_project.get_name() or os.path.basename(PROJECT_FILE_PATH)
|
|
15
|
-
else: project_name = os.path.basename(PROJECT_FILE_PATH) + " (ensure_project_open returned None?)"
|
|
16
|
-
except Exception:
|
|
17
|
-
project_name = os.path.basename(PROJECT_FILE_PATH) + " (name retrieval failed)"
|
|
18
|
-
|
|
19
|
-
print("DEBUG: Saving project: %s (%s)" % (project_name, PROJECT_FILE_PATH))
|
|
20
|
-
primary_project.save()
|
|
21
|
-
print("DEBUG: project.save() executed.")
|
|
22
|
-
print("Project Saved: %s" % project_name)
|
|
23
|
-
print("SCRIPT_SUCCESS: Project saved successfully.")
|
|
24
|
-
sys.exit(0)
|
|
25
|
-
except Exception as e:
|
|
26
|
-
error_message = "Error saving project %s: %s" % (PROJECT_FILE_PATH, e)
|
|
27
|
-
print(error_message)
|
|
28
|
-
traceback.print_exc()
|
|
29
|
-
print("SCRIPT_ERROR: %s" % error_message); sys.exit(1)
|
|
@@ -1,281 +0,0 @@
|
|
|
1
|
-
import sys, os, time, traceback
|
|
2
|
-
import tempfile
|
|
3
|
-
|
|
4
|
-
# Import JSON handling for IronPython 2.7 compatibility
|
|
5
|
-
try:
|
|
6
|
-
import json
|
|
7
|
-
except ImportError:
|
|
8
|
-
# For IronPython 2.7, we need to use simplejson instead
|
|
9
|
-
try:
|
|
10
|
-
import simplejson as json
|
|
11
|
-
except ImportError:
|
|
12
|
-
print("Neither json nor simplejson could be imported. Check IronPython installation.")
|
|
13
|
-
# Provide a minimal fallback implementation for basic needs
|
|
14
|
-
class DummyJson:
|
|
15
|
-
@staticmethod
|
|
16
|
-
def load(file_obj):
|
|
17
|
-
content = file_obj.read()
|
|
18
|
-
# Very basic parsing for our limited needs
|
|
19
|
-
if content.strip().startswith("{") and content.strip().endswith("}"):
|
|
20
|
-
result = {}
|
|
21
|
-
parts = content.strip()[1:-1].split(",")
|
|
22
|
-
for part in parts:
|
|
23
|
-
if ":" in part:
|
|
24
|
-
k, v = part.split(":", 1)
|
|
25
|
-
k = k.strip().strip('"\'')
|
|
26
|
-
v = v.strip().strip('"\'')
|
|
27
|
-
result[k] = v
|
|
28
|
-
return result
|
|
29
|
-
return {}
|
|
30
|
-
|
|
31
|
-
@staticmethod
|
|
32
|
-
def loads(s):
|
|
33
|
-
if s.strip().startswith("{") and s.strip().endswith("}"):
|
|
34
|
-
result = {}
|
|
35
|
-
parts = s.strip()[1:-1].split(",")
|
|
36
|
-
for part in parts:
|
|
37
|
-
if ":" in part:
|
|
38
|
-
k, v = part.split(":", 1)
|
|
39
|
-
k = k.strip().strip('"\'')
|
|
40
|
-
v = v.strip().strip('"\'')
|
|
41
|
-
result[k] = v
|
|
42
|
-
return result
|
|
43
|
-
return {}
|
|
44
|
-
|
|
45
|
-
@staticmethod
|
|
46
|
-
def dump(obj, file_obj, indent=None):
|
|
47
|
-
if isinstance(obj, dict):
|
|
48
|
-
pairs = []
|
|
49
|
-
for k, v in obj.items():
|
|
50
|
-
if isinstance(v, str):
|
|
51
|
-
pairs.append('"{0}": "{1}"'.format(k, v))
|
|
52
|
-
else:
|
|
53
|
-
pairs.append('"{0}": {1}'.format(k, v))
|
|
54
|
-
file_obj.write("{" + ", ".join(pairs) + "}")
|
|
55
|
-
else:
|
|
56
|
-
file_obj.write(str(obj))
|
|
57
|
-
|
|
58
|
-
json = DummyJson()
|
|
59
|
-
|
|
60
|
-
# Define markers for script results
|
|
61
|
-
SCRIPT_SUCCESS_MARKER = "SCRIPT_SUCCESS"
|
|
62
|
-
SCRIPT_ERROR_MARKER = "SCRIPT_ERROR"
|
|
63
|
-
|
|
64
|
-
# Session state and command files
|
|
65
|
-
SESSION_DIR = os.path.join(tempfile.gettempdir(), "codesys_session")
|
|
66
|
-
SESSION_STATE_FILE = os.path.join(SESSION_DIR, "session_state.json")
|
|
67
|
-
COMMAND_FILE = os.path.join(SESSION_DIR, "command.json")
|
|
68
|
-
RESULT_FILE = os.path.join(SESSION_DIR, "result.json")
|
|
69
|
-
SESSION_LOCK_FILE = os.path.join(SESSION_DIR, "session.lock")
|
|
70
|
-
|
|
71
|
-
# Print key information at start
|
|
72
|
-
print("Session status check script starting")
|
|
73
|
-
print("tempdir: {0}".format(tempfile.gettempdir()))
|
|
74
|
-
print("SESSION_DIR: {0}".format(SESSION_DIR))
|
|
75
|
-
print("SESSION_STATE_FILE: {0}".format(SESSION_STATE_FILE))
|
|
76
|
-
print("COMMAND_FILE: {0}".format(COMMAND_FILE))
|
|
77
|
-
print("RESULT_FILE: {0}".format(RESULT_FILE))
|
|
78
|
-
print("SESSION_LOCK_FILE: {0}".format(SESSION_LOCK_FILE))
|
|
79
|
-
|
|
80
|
-
def check_session_status():
|
|
81
|
-
"""Check the status of a CODESYS session"""
|
|
82
|
-
try:
|
|
83
|
-
# Check if session directory exists
|
|
84
|
-
if not os.path.exists(SESSION_DIR):
|
|
85
|
-
print("{0}: No session active. Session directory not found.".format(SCRIPT_SUCCESS_MARKER))
|
|
86
|
-
return False
|
|
87
|
-
|
|
88
|
-
# Check if session state file exists
|
|
89
|
-
if not os.path.exists(SESSION_STATE_FILE):
|
|
90
|
-
print("{0}: No session active. Session state file not found.".format(SCRIPT_SUCCESS_MARKER))
|
|
91
|
-
return False
|
|
92
|
-
|
|
93
|
-
# Read session state
|
|
94
|
-
try:
|
|
95
|
-
with open(SESSION_STATE_FILE, 'r') as f:
|
|
96
|
-
session_state = json.load(f)
|
|
97
|
-
except json.JSONDecodeError:
|
|
98
|
-
print("{0}: Invalid session state file".format(SCRIPT_ERROR_MARKER))
|
|
99
|
-
return False
|
|
100
|
-
except Exception as e:
|
|
101
|
-
print("{0}: Error reading session state file: {1}".format(SCRIPT_ERROR_MARKER, str(e)))
|
|
102
|
-
return False
|
|
103
|
-
|
|
104
|
-
# Check if session is active
|
|
105
|
-
active = session_state.get('active', False)
|
|
106
|
-
status = session_state.get('status', 'unknown')
|
|
107
|
-
|
|
108
|
-
# If the status indicates the session is not active, return immediately
|
|
109
|
-
if not active or status in ['stopped', 'error', 'stopping', 'timeout']:
|
|
110
|
-
print("{0}: Session is not active (Status: {1})".format(SCRIPT_SUCCESS_MARKER, status))
|
|
111
|
-
return False
|
|
112
|
-
|
|
113
|
-
# Check for session running too long (> 8 hours)
|
|
114
|
-
start_time = session_state.get('start_time', 0)
|
|
115
|
-
current_time = time.time()
|
|
116
|
-
uptime = current_time - start_time if start_time else 0
|
|
117
|
-
max_allowed_uptime = 60 * 60 * 8 # 8 hours in seconds
|
|
118
|
-
|
|
119
|
-
if uptime > max_allowed_uptime:
|
|
120
|
-
print("WARNING: Session has been running for {0:.1f} hours, exceeding the 8-hour safety limit".format(uptime / 3600))
|
|
121
|
-
|
|
122
|
-
# Update session state to indicate timeout due to excessive runtime
|
|
123
|
-
try:
|
|
124
|
-
session_state['status'] = 'timeout'
|
|
125
|
-
session_state['active'] = False
|
|
126
|
-
session_state['error_reason'] = 'Maximum runtime exceeded'
|
|
127
|
-
|
|
128
|
-
with open(SESSION_STATE_FILE, 'w') as f:
|
|
129
|
-
json.dump(session_state, f, indent=2)
|
|
130
|
-
|
|
131
|
-
print("Updated session state to timeout due to excessive runtime")
|
|
132
|
-
print("{0}: Session is not active (exceeded maximum runtime)".format(SCRIPT_SUCCESS_MARKER))
|
|
133
|
-
return False
|
|
134
|
-
except Exception as e:
|
|
135
|
-
print("Error updating session state: {0}".format(e))
|
|
136
|
-
|
|
137
|
-
# Check session lock file to see if it's been updated recently
|
|
138
|
-
lock_is_stale = False
|
|
139
|
-
if os.path.exists(SESSION_LOCK_FILE):
|
|
140
|
-
try:
|
|
141
|
-
with open(SESSION_LOCK_FILE, 'r') as f:
|
|
142
|
-
lock_time = float(f.read().strip())
|
|
143
|
-
time_diff = current_time - lock_time
|
|
144
|
-
|
|
145
|
-
# If lock file hasn't been updated in 30 seconds, session might be dead
|
|
146
|
-
if time_diff > 30:
|
|
147
|
-
print("WARNING: Session lock file is stale by {0:.1f} seconds".format(time_diff))
|
|
148
|
-
lock_is_stale = True
|
|
149
|
-
except Exception as e:
|
|
150
|
-
print("Error reading lock file: {0}".format(e))
|
|
151
|
-
lock_is_stale = True
|
|
152
|
-
else:
|
|
153
|
-
print("WARNING: Session lock file not found")
|
|
154
|
-
lock_is_stale = True
|
|
155
|
-
|
|
156
|
-
# If the lock file is stale but other checks suggest the session is active,
|
|
157
|
-
# we'll perform a ping test to be sure
|
|
158
|
-
ping_success = False
|
|
159
|
-
if status in ["running", "busy"]:
|
|
160
|
-
# Try to ping the session
|
|
161
|
-
try:
|
|
162
|
-
# Check if command file exists
|
|
163
|
-
if os.path.exists(COMMAND_FILE):
|
|
164
|
-
print("Sending ping command to verify session...")
|
|
165
|
-
|
|
166
|
-
# Create ping command
|
|
167
|
-
ping_command = {
|
|
168
|
-
"id": "ping_{0}".format(int(time.time())),
|
|
169
|
-
"type": "ping", # Use specific ping command type
|
|
170
|
-
"timestamp": time.time()
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
# Write command to file with pretty-printing
|
|
174
|
-
command_data = {
|
|
175
|
-
"status": "pending",
|
|
176
|
-
"command": ping_command,
|
|
177
|
-
"timestamp": time.time()
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
with open(COMMAND_FILE, 'w') as f:
|
|
181
|
-
json.dump(command_data, f, indent=2)
|
|
182
|
-
|
|
183
|
-
# Wait for result (with short timeout)
|
|
184
|
-
timeout = 5 # seconds
|
|
185
|
-
start_time = time.time()
|
|
186
|
-
|
|
187
|
-
while not ping_success and (time.time() - start_time) < timeout:
|
|
188
|
-
# Check if result file exists
|
|
189
|
-
if not os.path.exists(RESULT_FILE):
|
|
190
|
-
time.sleep(0.5)
|
|
191
|
-
continue
|
|
192
|
-
|
|
193
|
-
try:
|
|
194
|
-
with open(RESULT_FILE, 'r') as f:
|
|
195
|
-
result_data = json.load(f)
|
|
196
|
-
|
|
197
|
-
if result_data.get("status") == "complete" and result_data.get("result"):
|
|
198
|
-
result = result_data.get("result")
|
|
199
|
-
if result.get('id') == ping_command['id'] and result.get('success', False):
|
|
200
|
-
ping_success = True
|
|
201
|
-
print("Ping successful, session is responsive")
|
|
202
|
-
break
|
|
203
|
-
except Exception as e:
|
|
204
|
-
print("Error reading result: {0}".format(e))
|
|
205
|
-
time.sleep(0.5)
|
|
206
|
-
|
|
207
|
-
if not ping_success:
|
|
208
|
-
print("WARNING: Session did not respond to ping within {0} seconds".format(timeout))
|
|
209
|
-
|
|
210
|
-
# Reset command file since ping failed
|
|
211
|
-
try:
|
|
212
|
-
command_data = {
|
|
213
|
-
"status": "ready",
|
|
214
|
-
"command": None,
|
|
215
|
-
"timestamp": time.time()
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
with open(COMMAND_FILE, 'w') as f:
|
|
219
|
-
json.dump(command_data, f, indent=2)
|
|
220
|
-
|
|
221
|
-
print("Reset command file after failed ping")
|
|
222
|
-
except Exception as e:
|
|
223
|
-
print("Error resetting command file: {0}".format(e))
|
|
224
|
-
else:
|
|
225
|
-
print("WARNING: Command file not found, cannot ping session")
|
|
226
|
-
except Exception as e:
|
|
227
|
-
print("Error during ping test: {0}".format(e))
|
|
228
|
-
traceback.print_exc()
|
|
229
|
-
|
|
230
|
-
# Make final determination based on all checks
|
|
231
|
-
session_active = active and (not lock_is_stale or ping_success) and status in ["running", "busy"]
|
|
232
|
-
|
|
233
|
-
# Build status message
|
|
234
|
-
project_info = ""
|
|
235
|
-
if session_state.get('project_open', False):
|
|
236
|
-
project_name = session_state.get('project_name', 'Unknown')
|
|
237
|
-
project_path = session_state.get('project_path', 'Unknown')
|
|
238
|
-
project_info = ", Project: {0} ({1})".format(project_name, project_path)
|
|
239
|
-
|
|
240
|
-
# Include session info
|
|
241
|
-
session_id = session_state.get('session_id', 'Unknown')
|
|
242
|
-
uptime_str = "{0:.0f} seconds".format(uptime) if uptime < 60 else (
|
|
243
|
-
"{0:.1f} minutes".format(uptime/60) if uptime < 3600 else
|
|
244
|
-
"{0:.1f} hours".format(uptime/3600)
|
|
245
|
-
)
|
|
246
|
-
|
|
247
|
-
if session_active:
|
|
248
|
-
print("{0}: Session is active (ID: {1}, Status: {2}, Uptime: {3}{4})".format(SCRIPT_SUCCESS_MARKER, session_id, status, uptime_str, project_info))
|
|
249
|
-
return True
|
|
250
|
-
else:
|
|
251
|
-
# If we get here, session may be in a state where files exist but process is dead
|
|
252
|
-
if active and status in ["running", "busy"] and lock_is_stale and not ping_success:
|
|
253
|
-
print("{0}: Session appears to be zombie (ID: {1}, Status: {2} but unresponsive)".format(SCRIPT_SUCCESS_MARKER, session_id, status))
|
|
254
|
-
# Update session state to error so it will be considered inactive
|
|
255
|
-
try:
|
|
256
|
-
session_state['status'] = 'error'
|
|
257
|
-
session_state['active'] = False
|
|
258
|
-
session_state['error_reason'] = 'Unresponsive to ping'
|
|
259
|
-
|
|
260
|
-
with open(SESSION_STATE_FILE, 'w') as f:
|
|
261
|
-
json.dump(session_state, f, indent=2)
|
|
262
|
-
|
|
263
|
-
print("Updated session state to error due to unresponsiveness")
|
|
264
|
-
except Exception as e:
|
|
265
|
-
print("Error updating session state: {0}".format(e))
|
|
266
|
-
else:
|
|
267
|
-
print("{0}: Session is not active (Status: {1})".format(SCRIPT_SUCCESS_MARKER, status))
|
|
268
|
-
|
|
269
|
-
return False
|
|
270
|
-
|
|
271
|
-
except Exception as e:
|
|
272
|
-
print("{0}: Error checking session status - {1}".format(SCRIPT_ERROR_MARKER, str(e)))
|
|
273
|
-
traceback.print_exc()
|
|
274
|
-
return False
|
|
275
|
-
|
|
276
|
-
# Main script
|
|
277
|
-
if __name__ == "__main__":
|
|
278
|
-
if check_session_status():
|
|
279
|
-
sys.exit(0)
|
|
280
|
-
else:
|
|
281
|
-
sys.exit(1)
|
|
@@ -1,109 +0,0 @@
|
|
|
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_FULL_PATH = "{POU_FULL_PATH}" # Expecting format like "Application/MyPOU" or "Folder/SubFolder/MyPOU"
|
|
16
|
-
DECLARATION_CONTENT = """{DECLARATION_CONTENT}"""
|
|
17
|
-
IMPLEMENTATION_CONTENT = """{IMPLEMENTATION_CONTENT}"""
|
|
18
|
-
|
|
19
|
-
# Define the global PROJECT_FILE_PATH for compatibility with ensure_project_open
|
|
20
|
-
# When this script is embedded in other scripts
|
|
21
|
-
if 'PROJECT_FILE_PATH' not in globals():
|
|
22
|
-
PROJECT_FILE_PATH = "{PROJECT_FILE_PATH}"
|
|
23
|
-
|
|
24
|
-
try:
|
|
25
|
-
print("DEBUG: set_pou_code script: POU_FULL_PATH='%s', Project='%s'" % (POU_FULL_PATH, PROJECT_FILE_PATH))
|
|
26
|
-
primary_project = ensure_project_open(PROJECT_FILE_PATH)
|
|
27
|
-
if not POU_FULL_PATH: raise ValueError("POU full path empty.")
|
|
28
|
-
|
|
29
|
-
# Find the target POU/Method/Property object
|
|
30
|
-
target_object = find_object_by_path_robust(primary_project, POU_FULL_PATH, "target object")
|
|
31
|
-
if not target_object: raise ValueError("Target object not found using path: %s" % POU_FULL_PATH)
|
|
32
|
-
|
|
33
|
-
target_name = getattr(target_object, 'get_name', lambda: POU_FULL_PATH)()
|
|
34
|
-
print("DEBUG: Found target object: %s" % target_name)
|
|
35
|
-
|
|
36
|
-
# --- Set Declaration Part ---
|
|
37
|
-
declaration_updated = False
|
|
38
|
-
# Check if the content is actually provided (might be None/empty if only impl is set)
|
|
39
|
-
has_declaration_content = 'DECLARATION_CONTENT' in locals() or 'DECLARATION_CONTENT' in globals()
|
|
40
|
-
if has_declaration_content and DECLARATION_CONTENT is not None: # Check not None
|
|
41
|
-
if hasattr(target_object, 'textual_declaration'):
|
|
42
|
-
decl_obj = target_object.textual_declaration
|
|
43
|
-
if decl_obj and hasattr(decl_obj, 'replace'):
|
|
44
|
-
try:
|
|
45
|
-
print("DEBUG: Accessing textual_declaration...")
|
|
46
|
-
decl_obj.replace(DECLARATION_CONTENT)
|
|
47
|
-
print("DEBUG: Set declaration text using replace().")
|
|
48
|
-
declaration_updated = True
|
|
49
|
-
except Exception as decl_err:
|
|
50
|
-
print("ERROR: Failed to set declaration text: %s" % decl_err)
|
|
51
|
-
traceback.print_exc() # Print stack trace for detailed error
|
|
52
|
-
else:
|
|
53
|
-
print("WARN: Target '%s' textual_declaration attribute is None or does not have replace(). Skipping declaration update." % target_name)
|
|
54
|
-
else:
|
|
55
|
-
print("WARN: Target '%s' does not have textual_declaration attribute. Skipping declaration update." % target_name)
|
|
56
|
-
else:
|
|
57
|
-
print("DEBUG: Declaration content not provided or is None. Skipping declaration update.")
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
# --- Set Implementation Part ---
|
|
61
|
-
implementation_updated = False
|
|
62
|
-
has_implementation_content = 'IMPLEMENTATION_CONTENT' in locals() or 'IMPLEMENTATION_CONTENT' in globals()
|
|
63
|
-
if has_implementation_content and IMPLEMENTATION_CONTENT is not None: # Check not None
|
|
64
|
-
if hasattr(target_object, 'textual_implementation'):
|
|
65
|
-
impl_obj = target_object.textual_implementation
|
|
66
|
-
if impl_obj and hasattr(impl_obj, 'replace'):
|
|
67
|
-
try:
|
|
68
|
-
print("DEBUG: Accessing textual_implementation...")
|
|
69
|
-
impl_obj.replace(IMPLEMENTATION_CONTENT)
|
|
70
|
-
print("DEBUG: Set implementation text using replace().")
|
|
71
|
-
implementation_updated = True
|
|
72
|
-
except Exception as impl_err:
|
|
73
|
-
print("ERROR: Failed to set implementation text: %s" % impl_err)
|
|
74
|
-
traceback.print_exc() # Print stack trace for detailed error
|
|
75
|
-
else:
|
|
76
|
-
print("WARN: Target '%s' textual_implementation attribute is None or does not have replace(). Skipping implementation update." % target_name)
|
|
77
|
-
else:
|
|
78
|
-
print("WARN: Target '%s' does not have textual_implementation attribute. Skipping implementation update." % target_name)
|
|
79
|
-
else:
|
|
80
|
-
print("DEBUG: Implementation content not provided or is None. Skipping implementation update.")
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
# --- SAVE THE PROJECT TO PERSIST THE CODE CHANGE ---
|
|
84
|
-
# Only save if something was actually updated to avoid unnecessary saves
|
|
85
|
-
if declaration_updated or implementation_updated:
|
|
86
|
-
try:
|
|
87
|
-
print("DEBUG: Saving Project (after code change)...")
|
|
88
|
-
primary_project.save() # Save the overall project file
|
|
89
|
-
print("DEBUG: Project saved successfully after code change.")
|
|
90
|
-
except Exception as save_err:
|
|
91
|
-
print("ERROR: Failed to save Project after setting code: %s" % save_err)
|
|
92
|
-
detailed_error = traceback.format_exc()
|
|
93
|
-
error_message = "Error saving Project after code change for '%s': %s\\n%s" % (target_name, save_err, detailed_error)
|
|
94
|
-
print(error_message); print("SCRIPT_ERROR: %s" % error_message); sys.exit(1)
|
|
95
|
-
else:
|
|
96
|
-
print("DEBUG: No code parts were updated, skipping project save.")
|
|
97
|
-
# --- END SAVING ---
|
|
98
|
-
|
|
99
|
-
print("Code Set For: %s" % target_name)
|
|
100
|
-
print("Path: %s" % POU_FULL_PATH)
|
|
101
|
-
print("SCRIPT_SUCCESS: Declaration and/or implementation set successfully.")
|
|
102
|
-
sys.exit(0)
|
|
103
|
-
|
|
104
|
-
except Exception as e:
|
|
105
|
-
detailed_error = traceback.format_exc()
|
|
106
|
-
error_message = "Error setting code for object '%s' in project '%s': %s\\n%s" % (POU_FULL_PATH, PROJECT_FILE_PATH, e, detailed_error)
|
|
107
|
-
print(error_message)
|
|
108
|
-
print("SCRIPT_ERROR: %s" % error_message)
|
|
109
|
-
sys.exit(1)
|