@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.
Files changed (110) hide show
  1. package/CHANGELOG.md +153 -189
  2. package/LICENSE +21 -21
  3. package/README.md +224 -107
  4. package/dist/actions.d.ts +28 -0
  5. package/dist/actions.js +98 -0
  6. package/dist/actions.js.map +1 -0
  7. package/dist/bridge/bridge.d.ts +105 -0
  8. package/dist/bridge/bridge.js +400 -0
  9. package/dist/bridge/bridge.js.map +1 -0
  10. package/dist/cli.d.ts +2 -0
  11. package/dist/cli.js +196 -0
  12. package/dist/cli.js.map +1 -0
  13. package/dist/codesys/cache.d.ts +48 -0
  14. package/dist/codesys/cache.js +82 -0
  15. package/dist/codesys/cache.js.map +1 -0
  16. package/dist/codesys/changes.d.ts +57 -0
  17. package/dist/codesys/changes.js +133 -0
  18. package/dist/codesys/changes.js.map +1 -0
  19. package/dist/codesys/decl.d.ts +45 -0
  20. package/dist/codesys/decl.js +251 -0
  21. package/dist/codesys/decl.js.map +1 -0
  22. package/dist/codesys/devices.d.ts +34 -0
  23. package/dist/codesys/devices.js +82 -0
  24. package/dist/codesys/devices.js.map +1 -0
  25. package/dist/codesys/diff.d.ts +13 -0
  26. package/dist/codesys/diff.js +61 -0
  27. package/dist/codesys/diff.js.map +1 -0
  28. package/dist/codesys/install.d.ts +40 -0
  29. package/dist/codesys/install.js +144 -0
  30. package/dist/codesys/install.js.map +1 -0
  31. package/dist/codesys/library-browse.d.ts +19 -0
  32. package/dist/codesys/library-browse.js +57 -0
  33. package/dist/codesys/library-browse.js.map +1 -0
  34. package/dist/codesys/outline.d.ts +32 -0
  35. package/dist/codesys/outline.js +219 -0
  36. package/dist/codesys/outline.js.map +1 -0
  37. package/dist/codesys/simulation.d.ts +34 -0
  38. package/dist/codesys/simulation.js +57 -0
  39. package/dist/codesys/simulation.js.map +1 -0
  40. package/dist/codesys/st.d.ts +36 -0
  41. package/dist/codesys/st.js +69 -0
  42. package/dist/codesys/st.js.map +1 -0
  43. package/dist/daemon.d.ts +46 -0
  44. package/dist/daemon.js +173 -0
  45. package/dist/daemon.js.map +1 -0
  46. package/dist/driver.d.ts +25 -0
  47. package/dist/driver.js +33 -0
  48. package/dist/driver.js.map +1 -0
  49. package/dist/panel/sim-panel.html +357 -0
  50. package/dist/panel/viewer.html +126 -0
  51. package/dist/panel-types.d.ts +20 -0
  52. package/dist/panel-types.js +3 -0
  53. package/dist/panel-types.js.map +1 -0
  54. package/dist/panel.d.ts +27 -0
  55. package/dist/panel.js +208 -0
  56. package/dist/panel.js.map +1 -0
  57. package/dist/server.d.ts +5 -0
  58. package/dist/server.js +33 -974
  59. package/dist/server.js.map +1 -0
  60. package/dist/tools.d.ts +103 -0
  61. package/dist/tools.js +793 -0
  62. package/dist/tools.js.map +1 -0
  63. package/dist/viewer.d.ts +14 -0
  64. package/dist/viewer.js +125 -0
  65. package/dist/viewer.js.map +1 -0
  66. package/package.json +37 -21
  67. package/watcher/mcp_compat.py +147 -0
  68. package/watcher/mcp_ops.py +1178 -0
  69. package/watcher/mcp_sim.py +246 -0
  70. package/watcher/watcher.py +255 -0
  71. package/.claude/settings.local.json +0 -29
  72. package/CLAUDE.md +0 -26
  73. package/CONTRIBUTING.md +0 -72
  74. package/README.md.enhanced +0 -155
  75. package/codesys-mcp-toolkit-1.2.0.tgz +0 -0
  76. package/codesys-mcp-toolkit-1.2.1.tgz +0 -0
  77. package/codesys-mcp-toolkit-1.5.7.tgz +0 -0
  78. package/codesys-mcp-toolkit-1.5.8.tgz +0 -0
  79. package/dist/bin.js +0 -75
  80. package/dist/check_session_paths.js +0 -149
  81. package/dist/codesys_interop.js +0 -435
  82. package/dist/direct_session_command.js +0 -293
  83. package/dist/scriptLoader.js +0 -165
  84. package/dist/scripts/check_status.py +0 -21
  85. package/dist/scripts/compile_project.py +0 -65
  86. package/dist/scripts/create_method.py +0 -80
  87. package/dist/scripts/create_pou.py +0 -102
  88. package/dist/scripts/create_project.py +0 -116
  89. package/dist/scripts/create_property.py +0 -78
  90. package/dist/scripts/ensure_project_open.py +0 -154
  91. package/dist/scripts/find_object_by_path.py +0 -111
  92. package/dist/scripts/get_pou_code.py +0 -91
  93. package/dist/scripts/get_project_structure.py +0 -72
  94. package/dist/scripts/open_project.py +0 -25
  95. package/dist/scripts/save_project.py +0 -29
  96. package/dist/scripts/session_status.py +0 -281
  97. package/dist/scripts/set_pou_code.py +0 -109
  98. package/dist/scripts/start_session.py +0 -809
  99. package/dist/scripts/stop_session.py +0 -324
  100. package/dist/sessionManager.js +0 -798
  101. package/docs/configuration.md +0 -141
  102. package/docs/index.md +0 -26
  103. package/docs/installation.md +0 -84
  104. package/docs/session_management.md +0 -110
  105. package/examples/README.md +0 -78
  106. package/examples/sample_config.json +0 -32
  107. package/replacements_session_status.txt +0 -16
  108. package/scriptDoc.txt +0 -8679
  109. package/scripts/build.js +0 -25
  110. package/scripts/fix_fstrings.py +0 -76
@@ -1,102 +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
- # Define required parameters
16
- POU_NAME = "{POU_NAME}"
17
- POU_TYPE_STR = "{POU_TYPE_STR}"
18
- IMPL_LANGUAGE_STR = "{IMPL_LANGUAGE_STR}"
19
- PARENT_PATH_REL = "{PARENT_PATH}"
20
-
21
- # Define the global PROJECT_FILE_PATH for compatibility with ensure_project_open
22
- # When this script is embedded in other scripts
23
- if 'PROJECT_FILE_PATH' not in globals():
24
- PROJECT_FILE_PATH = "{PROJECT_FILE_PATH}"
25
-
26
- pou_type_map = {
27
- "Program": script_engine.PouType.Program,
28
- "FunctionBlock": script_engine.PouType.FunctionBlock,
29
- "Function": script_engine.PouType.Function
30
- }
31
- # Map common language names to ImplementationLanguages attributes if needed (optional, None usually works)
32
- # lang_map = { "ST": script_engine.ImplementationLanguage.st, ... }
33
-
34
- try:
35
- 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))
36
- primary_project = ensure_project_open(PROJECT_FILE_PATH)
37
- if not POU_NAME: raise ValueError("POU name empty.")
38
- if not PARENT_PATH_REL: raise ValueError("Parent path empty.")
39
-
40
- # Resolve POU Type Enum
41
- pou_type_enum = pou_type_map.get(POU_TYPE_STR)
42
- if not pou_type_enum: raise ValueError("Invalid POU type string: %s. Use Program, FunctionBlock, or Function." % POU_TYPE_STR)
43
-
44
- # Find parent object using the robust function
45
- parent_object = find_object_by_path_robust(primary_project, PARENT_PATH_REL, "parent container")
46
- if not parent_object: raise ValueError("Parent object not found for path: %s" % PARENT_PATH_REL)
47
-
48
- parent_name = getattr(parent_object, 'get_name', lambda: str(parent_object))()
49
- print("DEBUG: Using parent object: %s (Type: %s)" % (parent_name, type(parent_object).__name__))
50
-
51
- # Check if parent object supports creating POUs (should implement ScriptIecLanguageObjectContainer)
52
- if not hasattr(parent_object, 'create_pou'):
53
- raise TypeError("Parent object '%s' of type %s does not support create_pou." % (parent_name, type(parent_object).__name__))
54
-
55
- # Set language GUID to None (let CODESYS default based on parent/settings)
56
- lang_guid = None
57
- print("DEBUG: Setting language to None (will use default).")
58
- # Example if mapping language string: lang_guid = lang_map.get(IMPL_LANGUAGE_STR, None)
59
-
60
- print("DEBUG: Calling parent_object.create_pou: Name='%s', Type=%s, Lang=%s" % (POU_NAME, pou_type_enum, lang_guid))
61
-
62
- # Call create_pou using keyword arguments
63
- new_pou = parent_object.create_pou(
64
- name=POU_NAME,
65
- type=pou_type_enum,
66
- language=lang_guid # Pass None
67
- )
68
-
69
- print("DEBUG: parent_object.create_pou returned: %s" % new_pou)
70
- if new_pou:
71
- new_pou_name = getattr(new_pou, 'get_name', lambda: POU_NAME)()
72
- print("DEBUG: POU object created: %s" % new_pou_name)
73
-
74
- # --- SAVE THE PROJECT TO PERSIST THE NEW POU ---
75
- try:
76
- print("DEBUG: Saving Project...")
77
- primary_project.save() # Save the overall project file
78
- print("DEBUG: Project saved successfully after POU creation.")
79
- except Exception as save_err:
80
- print("ERROR: Failed to save Project after POU creation: %s" % save_err)
81
- detailed_error = traceback.format_exc()
82
- error_message = "Error saving Project after creating POU '%s': %s\\n%s" % (new_pou_name, save_err, detailed_error)
83
- print(error_message); print("SCRIPT_ERROR: %s" % error_message); sys.exit(1)
84
- # --- END SAVING ---
85
-
86
- print("POU Created: %s" % new_pou_name)
87
- print("Type: %s" % POU_TYPE_STR)
88
- print("Language: %s (Defaulted)" % IMPL_LANGUAGE_STR)
89
- print("Parent Path: %s" % PARENT_PATH_REL)
90
- print("SCRIPT_SUCCESS: POU created successfully.")
91
- sys.exit(0)
92
- else:
93
- error_message = "Failed to create POU '%s'. create_pou returned None." % POU_NAME
94
- print(error_message)
95
- print("SCRIPT_ERROR: %s" % error_message)
96
- sys.exit(1)
97
- except Exception as e:
98
- detailed_error = traceback.format_exc()
99
- error_message = "Error creating POU '%s' in project '%s': %s\\n%s" % (POU_NAME, PROJECT_FILE_PATH, e, detailed_error)
100
- print(error_message)
101
- print("SCRIPT_ERROR: Error creating POU '%s': %s" % (POU_NAME, e))
102
- sys.exit(1)
@@ -1,116 +0,0 @@
1
- import sys, scriptengine as script_engine, os, shutil, time, traceback
2
-
3
- # Direct path assignment - no more raw string handling
4
- # Paths will be correctly escaped in the template substitution
5
- TEMPLATE_PROJECT_PATH = '{TEMPLATE_PROJECT_PATH}'
6
- PROJECT_FILE_PATH = '{PROJECT_FILE_PATH}'
7
- # Optional flag to skip opening the project after creation (useful for sessions)
8
- SKIP_OPEN = '{SKIP_OPEN}' == 'true'
9
-
10
- # Define the global PROJECT_FILE_PATH for compatibility with ensure_project_open
11
- # This is used by some scripts that check the project status
12
- if 'PROJECT_FILE_PATH' not in globals():
13
- globals()['PROJECT_FILE_PATH'] = PROJECT_FILE_PATH
14
-
15
- # Print raw paths for debugging
16
- print("DEBUG: Template path (raw): %s" % repr(TEMPLATE_PROJECT_PATH))
17
- print("DEBUG: Project path (raw): %s" % repr(PROJECT_FILE_PATH))
18
-
19
- print("DEBUG: Processed TEMPLATE_PROJECT_PATH: %s" % TEMPLATE_PROJECT_PATH)
20
- print("DEBUG: Processed PROJECT_FILE_PATH: %s" % PROJECT_FILE_PATH)
21
- try:
22
- print("DEBUG: Python script create_project (copy from template):")
23
- print("DEBUG: Template Source = %s" % TEMPLATE_PROJECT_PATH)
24
- print("DEBUG: Target Path = %s" % PROJECT_FILE_PATH)
25
- if not PROJECT_FILE_PATH: raise ValueError("Target project file path empty.")
26
- if not TEMPLATE_PROJECT_PATH: raise ValueError("Template project file path empty.")
27
- if not os.path.exists(TEMPLATE_PROJECT_PATH): raise IOError("Template project file not found: %s" % TEMPLATE_PROJECT_PATH)
28
-
29
- # 1. Copy the template project file to the new location
30
- target_dir = os.path.dirname(PROJECT_FILE_PATH)
31
- print("DEBUG: Target directory path: %s" % target_dir)
32
-
33
- # More robust directory creation
34
- if not os.path.exists(target_dir):
35
- try:
36
- print("DEBUG: Creating target directory: %s" % target_dir)
37
- os.makedirs(target_dir, exist_ok=True)
38
- print("DEBUG: Directory creation result - exists after creation: %s" % os.path.exists(target_dir))
39
- except Exception as dir_err:
40
- print("ERROR: Failed to create directory %s: %s" % (target_dir, dir_err))
41
- raise
42
-
43
- # Check if target file already exists
44
- if os.path.exists(PROJECT_FILE_PATH): print("WARN: Target project file already exists, overwriting: %s" % PROJECT_FILE_PATH)
45
-
46
- print("DEBUG: Copying '%s' to '%s'..." % (TEMPLATE_PROJECT_PATH, PROJECT_FILE_PATH))
47
- try:
48
- # Complete path diagnosis
49
- print("DEBUG: Template path absolute: %s" % os.path.abspath(TEMPLATE_PROJECT_PATH))
50
- print("DEBUG: Target path absolute: %s" % os.path.abspath(PROJECT_FILE_PATH))
51
- print("DEBUG: Template exists: %s" % os.path.exists(TEMPLATE_PROJECT_PATH))
52
- print("DEBUG: Target dir exists: %s" % os.path.exists(target_dir))
53
- print("DEBUG: Target dir is writable: %s" % os.access(target_dir, os.W_OK))
54
-
55
- # Perform the copy
56
- shutil.copy2(TEMPLATE_PROJECT_PATH, PROJECT_FILE_PATH) # copy2 preserves metadata
57
-
58
- # Verify copy succeeded
59
- if os.path.exists(PROJECT_FILE_PATH):
60
- print("DEBUG: File copy VERIFIED - file exists at destination")
61
- print("DEBUG: File size at destination: %s bytes" % os.path.getsize(PROJECT_FILE_PATH))
62
- else:
63
- print("ERROR: Copy seemed to succeed but file does not exist at destination!")
64
- raise IOError("File not found at destination after copy: %s" % PROJECT_FILE_PATH)
65
- except Exception as copy_err:
66
- print("ERROR: Failed to copy file: %s" % copy_err)
67
- raise
68
-
69
- print("DEBUG: File copy complete.")
70
-
71
- # For sessions, we may want to skip opening the project to avoid conflicts
72
- if SKIP_OPEN:
73
- print("DEBUG: SKIP_OPEN is set to true, skipping project open step.")
74
- print("Project Created from Template Copy at: %s" % PROJECT_FILE_PATH)
75
- print("SCRIPT_SUCCESS: Project copied from template (open step skipped for session compatibility).")
76
- sys.exit(0)
77
-
78
- # 2. Open the newly copied project file
79
- print("DEBUG: Opening the copied project: %s" % PROJECT_FILE_PATH)
80
- # Set flags for silent opening
81
- update_mode = script_engine.VersionUpdateFlags.NoUpdates | script_engine.VersionUpdateFlags.SilentMode
82
- # try:
83
- # update_mode = script_engine.VersionUpdateFlags.NoUpdates | script_engine.VersionUpdateFlags.SilentMode
84
- # except AttributeError:
85
- # print("WARN: VersionUpdateFlags not found, using integer flags for open (1 | 2 = 3).")
86
- # update_mode = 3
87
-
88
- try:
89
- project = script_engine.projects.open(PROJECT_FILE_PATH, update_flags=update_mode)
90
- print("DEBUG: script_engine.projects.open returned: %s" % project)
91
- if project:
92
- print("DEBUG: Pausing briefly after open...")
93
- time.sleep(1.0) # Allow CODESYS to potentially initialize things
94
- try:
95
- print("DEBUG: Explicitly saving project after opening copy...")
96
- project.save();
97
- print("DEBUG: Project save after opening copy succeeded.")
98
- except Exception as save_err:
99
- print("WARN: Explicit save after opening copy failed: %s" % save_err)
100
- # Decide if this is critical - maybe not, but good to know.
101
- print("Project Created from Template Copy at: %s" % PROJECT_FILE_PATH)
102
- print("SCRIPT_SUCCESS: Project copied from template and opened successfully.")
103
- sys.exit(0)
104
- else:
105
- error_message = "Failed to open project copy %s after copying template %s. projects.open returned None." % (PROJECT_FILE_PATH, TEMPLATE_PROJECT_PATH)
106
- print(error_message); print("SCRIPT_ERROR: %s" % error_message); sys.exit(1)
107
- except Exception as open_err:
108
- print("WARN: Error opening project after creation: %s" % open_err)
109
- # Even though we couldn't open it, the project was still created successfully
110
- print("Project Created from Template Copy at: %s (but could not be opened automatically)" % PROJECT_FILE_PATH)
111
- print("SCRIPT_SUCCESS: Project copied from template (but open operation failed: %s)." % open_err)
112
- sys.exit(0)
113
- except Exception as e:
114
- detailed_error = traceback.format_exc()
115
- error_message = "Error creating project '%s' from template '%s': %s\\n%s" % (PROJECT_FILE_PATH, TEMPLATE_PROJECT_PATH, e, detailed_error)
116
- print(error_message); print("SCRIPT_ERROR: Error copying/opening template: %s" % e); sys.exit(1)
@@ -1,78 +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
- PARENT_POU_FULL_PATH = "{PARENT_POU_FULL_PATH}" # e.g., "Application/MyFB"
16
- PROPERTY_NAME = "{PROPERTY_NAME}"
17
- PROPERTY_TYPE = "{PROPERTY_TYPE}"
18
- # Optional: Language for Getter/Setter (usually defaults to ST)
19
- # LANG_GUID_STR = "{LANG_GUID_STR}" # Example if needed
20
-
21
- try:
22
- print("DEBUG: create_property script: ParentPOU='%s', Name='%s', Type='%s', Project='%s'" % (PARENT_POU_FULL_PATH, PROPERTY_NAME, PROPERTY_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 PROPERTY_NAME: raise ValueError("Property name empty.")
26
- if not PROPERTY_TYPE: raise ValueError("Property type 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 properties (should implement ScriptIecLanguageMemberContainer)
36
- if not hasattr(parent_pou_object, 'create_property'):
37
- raise TypeError("Parent object '%s' of type %s does not support create_property." % (parent_pou_name, type(parent_pou_object).__name__))
38
-
39
- # Default language to None (usually ST)
40
- lang_guid = None
41
- print("DEBUG: Calling create_property: Name='%s', Type='%s', Lang=%s" % (PROPERTY_NAME, PROPERTY_TYPE, lang_guid))
42
-
43
- # Call the create_property method ON THE PARENT POU
44
- new_property_object = parent_pou_object.create_property(
45
- name=PROPERTY_NAME,
46
- return_type=PROPERTY_TYPE,
47
- language=lang_guid # Pass None to use default
48
- )
49
-
50
- if new_property_object:
51
- new_prop_name = getattr(new_property_object, 'get_name', lambda: PROPERTY_NAME)()
52
- print("DEBUG: Property object created: %s" % new_prop_name)
53
-
54
- # --- SAVE THE PROJECT TO PERSIST THE NEW PROPERTY OBJECT ---
55
- try:
56
- print("DEBUG: Saving Project (after property creation)...")
57
- primary_project.save()
58
- print("DEBUG: Project saved successfully after property creation.")
59
- except Exception as save_err:
60
- print("ERROR: Failed to save Project after creating property: %s" % save_err)
61
- detailed_error = traceback.format_exc()
62
- error_message = "Error saving Project after creating property '%s': %s\\n%s" % (PROPERTY_NAME, save_err, detailed_error)
63
- print(error_message); print("SCRIPT_ERROR: %s" % error_message); sys.exit(1)
64
- # --- END SAVING ---
65
-
66
- print("Property Created: %s" % new_prop_name)
67
- print("Parent POU: %s" % PARENT_POU_FULL_PATH)
68
- print("Type: %s" % PROPERTY_TYPE)
69
- print("SCRIPT_SUCCESS: Property created successfully.")
70
- sys.exit(0)
71
- else:
72
- error_message = "Failed to create property '%s' under '%s'. create_property returned None." % (PROPERTY_NAME, parent_pou_name)
73
- print(error_message); print("SCRIPT_ERROR: %s" % error_message); sys.exit(1)
74
-
75
- except Exception as e:
76
- detailed_error = traceback.format_exc()
77
- error_message = "Error creating property '%s' under POU '%s' in project '%s': %s\\n%s" % (PROPERTY_NAME, PARENT_POU_FULL_PATH, PROJECT_FILE_PATH, e, detailed_error)
78
- print(error_message); print("SCRIPT_ERROR: %s" % error_message); sys.exit(1)
@@ -1,154 +0,0 @@
1
- import sys
2
- import scriptengine as script_engine
3
- import os
4
- import time
5
- import traceback
6
-
7
- # --- Function to ensure the correct project is open ---
8
- MAX_RETRIES = 3
9
- RETRY_DELAY = 2.0 # seconds (use float for time.sleep)
10
-
11
- def ensure_project_open(target_project_path):
12
- print("DEBUG: Ensuring project is open: %s" % target_project_path)
13
- # Normalize target path once
14
- normalized_target_path = os.path.normcase(os.path.abspath(target_project_path))
15
-
16
- for attempt in range(MAX_RETRIES):
17
- print("DEBUG: Ensure project attempt %d/%d for %s" % (attempt + 1, MAX_RETRIES, normalized_target_path))
18
- primary_project = None
19
- try:
20
- # Getting primary project might fail if CODESYS instance is unstable
21
- primary_project = script_engine.projects.primary
22
- except Exception as primary_err:
23
- print("WARN: Error getting primary project: %s. Assuming none." % primary_err)
24
- # traceback.print_exc() # Optional: Print stack trace for this error
25
- primary_project = None
26
-
27
- current_project_path = ""
28
- project_ok = False # Flag to check if target is confirmed primary and accessible
29
-
30
- if primary_project:
31
- try:
32
- # Getting path should be relatively safe if primary_project object exists
33
- current_project_path = os.path.normcase(os.path.abspath(primary_project.path))
34
- print("DEBUG: Current primary project path: %s" % current_project_path)
35
- if current_project_path == normalized_target_path:
36
- # Found the right project as primary, now check if it's usable
37
- print("DEBUG: Target project path matches primary. Checking access...")
38
- try:
39
- # Try a relatively safe operation to confirm object usability
40
- # Getting children count is a reasonable check
41
- _ = len(primary_project.get_children(False))
42
- print("DEBUG: Target project '%s' is primary and accessible." % target_project_path)
43
- project_ok = True
44
- return primary_project # SUCCESS CASE 1: Already open and accessible
45
- except Exception as access_err:
46
- # Project found, but accessing it failed. Might be unstable.
47
- print("WARN: Primary project access check failed for '%s': %s. Will attempt reopen." % (current_project_path, access_err))
48
- # traceback.print_exc() # Optional: Print stack trace
49
- primary_project = None # Force reopen by falling through
50
- else:
51
- # A *different* project is primary
52
- print("DEBUG: Primary project is '%s', not the target '%s'." % (current_project_path, normalized_target_path))
53
- # Consider closing the wrong project if causing issues, but for now, just open target
54
- # try:
55
- # print("DEBUG: Closing incorrect primary project '%s'..." % current_project_path)
56
- # primary_project.close() # Be careful with unsaved changes
57
- # except Exception as close_err:
58
- # print("WARN: Failed to close incorrect primary project: %s" % close_err)
59
- primary_project = None # Force open target project
60
-
61
- except Exception as path_err:
62
- # Failed even to get the path of the supposed primary project
63
- print("WARN: Could not get path of current primary project: %s. Assuming not the target." % path_err)
64
- # traceback.print_exc() # Optional: Print stack trace
65
- primary_project = None # Force open target project
66
-
67
- # If target project not confirmed as primary and accessible, attempt to open/reopen
68
- if not project_ok:
69
- # Log clearly whether we are opening initially or reopening
70
- if primary_project is None and current_project_path == "":
71
- print("DEBUG: No primary project detected. Attempting to open target: %s" % target_project_path)
72
- elif primary_project is None and current_project_path != "":
73
- print("DEBUG: Primary project was '%s' but failed access check or needed close. Attempting to open target: %s" % (current_project_path, target_project_path))
74
- else: # Includes cases where wrong project was open
75
- print("DEBUG: Target project not primary or initial check failed. Attempting to open/reopen: %s" % target_project_path)
76
-
77
- try:
78
- # Set flags for silent opening, handle potential attribute errors
79
- update_mode = script_engine.VersionUpdateFlags.NoUpdates | script_engine.VersionUpdateFlags.SilentMode
80
- # try:
81
- # update_mode = script_engine.VersionUpdateFlags.NoUpdates | script_engine.VersionUpdateFlags.SilentMode
82
- # except AttributeError:
83
- # print("WARN: VersionUpdateFlags not found, using integer flags for open (1 | 2 = 3).")
84
- # update_mode = 3 # 1=NoUpdates, 2=SilentMode
85
-
86
- opened_project = None
87
- try:
88
- # The actual open call
89
- print("DEBUG: Calling script_engine.projects.open('%s', update_flags=%s)..." % (target_project_path, update_mode))
90
- opened_project = script_engine.projects.open(target_project_path, update_flags=update_mode)
91
-
92
- if not opened_project:
93
- # This is a critical failure if open returns None without exception
94
- print("ERROR: projects.open returned None for %s on attempt %d" % (target_project_path, attempt + 1))
95
- # Allow retry loop to continue
96
- else:
97
- # Open call returned *something*, let's verify
98
- print("DEBUG: projects.open call returned an object for: %s" % target_project_path)
99
- print("DEBUG: Pausing for stabilization after open...")
100
- time.sleep(RETRY_DELAY) # Give CODESYS time
101
- # Re-verify: Is the project now primary and accessible?
102
- recheck_primary = None
103
- try: recheck_primary = script_engine.projects.primary
104
- except Exception as recheck_primary_err: print("WARN: Error getting primary project after reopen: %s" % recheck_primary_err)
105
-
106
- if recheck_primary:
107
- recheck_path = ""
108
- try: # Getting path might fail
109
- recheck_path = os.path.normcase(os.path.abspath(recheck_primary.path))
110
- except Exception as recheck_path_err:
111
- print("WARN: Failed to get path after reopen: %s" % recheck_path_err)
112
-
113
- if recheck_path == normalized_target_path:
114
- print("DEBUG: Target project confirmed as primary after reopening.")
115
- try: # Final sanity check
116
- _ = len(recheck_primary.get_children(False))
117
- print("DEBUG: Reopened project basic access confirmed.")
118
- return recheck_primary # SUCCESS CASE 2: Successfully opened/reopened
119
- except Exception as access_err_reopen:
120
- print("WARN: Reopened project (%s) basic access check failed: %s." % (normalized_target_path, access_err_reopen))
121
- # traceback.print_exc() # Optional
122
- # Allow retry loop to continue
123
- else:
124
- print("WARN: Different project is primary after reopening! Expected '%s', got '%s'." % (normalized_target_path, recheck_path))
125
- # Allow retry loop to continue, maybe it fixes itself
126
- else:
127
- print("WARN: No primary project found after reopening attempt %d!" % (attempt+1))
128
- # Allow retry loop to continue
129
-
130
- except Exception as open_err:
131
- # Catch errors during the open call itself
132
- print("ERROR: Exception during projects.open call on attempt %d: %s" % (attempt + 1, open_err))
133
- traceback.print_exc() # Crucial for diagnosing open failures
134
- # Allow retry loop to continue
135
-
136
- except Exception as outer_open_err:
137
- # Catch errors in the flag setup etc.
138
- print("ERROR: Unexpected error during open setup/logic attempt %d: %s" % (attempt + 1, outer_open_err))
139
- traceback.print_exc()
140
-
141
- # If we didn't return successfully in this attempt, wait before retrying
142
- if attempt < MAX_RETRIES - 1:
143
- print("DEBUG: Ensure project attempt %d did not succeed. Waiting %f seconds..." % (attempt + 1, RETRY_DELAY))
144
- time.sleep(RETRY_DELAY)
145
- else: # Last attempt failed
146
- print("ERROR: Failed all ensure_project_open attempts for %s." % normalized_target_path)
147
-
148
-
149
- # If all retries fail after the loop
150
- raise RuntimeError("Failed to ensure project '%s' is open and accessible after %d attempts." % (target_project_path, MAX_RETRIES))
151
- # --- End of function ---
152
-
153
- # Placeholder for the project file path (must be set in scripts using this snippet)
154
- PROJECT_FILE_PATH = r"{PROJECT_FILE_PATH}"
@@ -1,111 +0,0 @@
1
- import traceback
2
- # --- Find object by path function ---
3
- def find_object_by_path_robust(start_node, full_path, target_type_name="object"):
4
- print("DEBUG: Finding %s by path: '%s'" % (target_type_name, full_path))
5
- normalized_path = full_path.replace('\\\\', '/').strip('/')
6
- path_parts = normalized_path.split('/')
7
- if not path_parts:
8
- print("ERROR: Path is empty.")
9
- return None
10
-
11
- # Determine the actual starting node (project or application)
12
- project = start_node # Assume start_node is project initially
13
- if not hasattr(start_node, 'active_application') and hasattr(start_node, 'project'):
14
- # If start_node is not project but has project ref (e.g., an application), get the project
15
- try: project = start_node.project
16
- except Exception as proj_ref_err:
17
- print("WARN: Could not get project reference from start_node: %s" % proj_ref_err)
18
- # Proceed assuming start_node might be the project anyway or search fails
19
-
20
- # Try to get the application object robustly if we think we have the project
21
- app = None
22
- if hasattr(project, 'active_application'):
23
- try: app = project.active_application
24
- except Exception: pass # Ignore errors getting active app
25
- if not app:
26
- try:
27
- apps = project.find("Application", True) # Search recursively
28
- if apps: app = apps[0]
29
- except Exception: pass
30
-
31
- # Check if the first path part matches the application name
32
- app_name_lower = ""
33
- if app:
34
- try: app_name_lower = (app.get_name() or "application").lower()
35
- except Exception: app_name_lower = "application" # Fallback
36
-
37
- # Decide where to start the traversal
38
- current_obj = start_node # Default to the node passed in
39
- if hasattr(project, 'active_application'): # Only adjust if start_node was likely the project
40
- if app and path_parts[0].lower() == app_name_lower:
41
- print("DEBUG: Path starts with Application name '%s'. Beginning search there." % path_parts[0])
42
- current_obj = app
43
- path_parts = path_parts[1:] # Consume the app name part
44
- # If path was *only* the application name
45
- if not path_parts:
46
- print("DEBUG: Target path is the Application object itself.")
47
- return current_obj
48
- else:
49
- print("DEBUG: Path does not start with Application name. Starting search from project root.")
50
- current_obj = project # Start search from the project root
51
- else:
52
- print("DEBUG: Starting search from originally provided node.")
53
-
54
-
55
- # Traverse the remaining path parts
56
- parent_path_str = getattr(current_obj, 'get_name', lambda: str(current_obj))() # Safer name getting
57
-
58
- for i, part_name in enumerate(path_parts):
59
- is_last_part = (i == len(path_parts) - 1)
60
- print("DEBUG: Searching for part [%d/%d]: '%s' under '%s'" % (i+1, len(path_parts), part_name, parent_path_str))
61
- found_in_parent = None
62
- try:
63
- # Prioritize non-recursive find for direct children
64
- children_of_current = current_obj.get_children(False)
65
- print("DEBUG: Found %d direct children under '%s'." % (len(children_of_current), parent_path_str))
66
- for child in children_of_current:
67
- child_name = getattr(child, 'get_name', lambda: None)() # Safer name getting
68
- # print("DEBUG: Checking child: '%s'" % child_name) # Verbose
69
- if child_name == part_name:
70
- found_in_parent = child
71
- print("DEBUG: Found direct child matching '%s'." % part_name)
72
- break # Found direct child, stop searching children
73
-
74
- # If not found directly, AND it's the last part, try recursive find from current parent
75
- if not found_in_parent and is_last_part:
76
- print("DEBUG: Direct find failed for last part '%s'. Trying recursive find under '%s'." % (part_name, parent_path_str))
77
- found_recursive_list = current_obj.find(part_name, True) # Recursive find
78
- if found_recursive_list:
79
- # Maybe add a check here if multiple are found?
80
- found_in_parent = found_recursive_list[0] # Take the first match
81
- print("DEBUG: Found last part '%s' recursively." % part_name)
82
- else:
83
- print("DEBUG: Recursive find also failed for last part '%s'." % part_name)
84
-
85
- # Update current object if found
86
- if found_in_parent:
87
- current_obj = found_in_parent
88
- parent_path_str = getattr(current_obj, 'get_name', lambda: part_name)() # Safer name getting
89
- print("DEBUG: Stepped into '%s'." % parent_path_str)
90
- else:
91
- # If not found at any point, the path is invalid from this parent
92
- print("ERROR: Path part '%s' not found under '%s'." % (part_name, parent_path_str))
93
- return None # Path broken
94
-
95
- except Exception as find_err:
96
- print("ERROR: Exception while searching for '%s' under '%s': %s" % (part_name, parent_path_str, find_err))
97
- traceback.print_exc()
98
- return None # Error during search
99
-
100
- # Final verification (optional but recommended): Check if the found object's name matches the last part
101
- final_expected_name = full_path.split('/')[-1]
102
- found_final_name = getattr(current_obj, 'get_name', lambda: None)() # Safer name getting
103
-
104
- if found_final_name == final_expected_name:
105
- print("DEBUG: Final %s found and name verified for path '%s': %s" % (target_type_name, full_path, found_final_name))
106
- return current_obj
107
- else:
108
- print("ERROR: Traversal ended on object '%s' but expected final name was '%s'." % (found_final_name, final_expected_name))
109
- return None # Name mismatch implies target not found as expected
110
-
111
- # --- End of find object function ---
@@ -1,91 +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}"
16
- CODE_START_MARKER = "### POU CODE START ###"
17
- CODE_END_MARKER = "### POU CODE END ###"
18
- DECL_START_MARKER = "### POU DECLARATION START ###"
19
- DECL_END_MARKER = "### POU DECLARATION END ###"
20
- IMPL_START_MARKER = "### POU IMPLEMENTATION START ###"
21
- IMPL_END_MARKER = "### POU IMPLEMENTATION END ###"
22
-
23
- try:
24
- print("DEBUG: Getting code: POU_FULL_PATH='%s', Project='%s'" % (POU_FULL_PATH, PROJECT_FILE_PATH))
25
- primary_project = ensure_project_open(PROJECT_FILE_PATH)
26
- if not POU_FULL_PATH: raise ValueError("POU full path empty.")
27
-
28
- # Find the target POU/Method/Property object
29
- target_object = find_object_by_path_robust(primary_project, POU_FULL_PATH, "target object")
30
- if not target_object: raise ValueError("Target object not found using path: %s" % POU_FULL_PATH)
31
-
32
- target_name = getattr(target_object, 'get_name', lambda: POU_FULL_PATH)()
33
- print("DEBUG: Found target object: %s" % target_name)
34
-
35
- declaration_code = ""; implementation_code = ""
36
-
37
- # --- Get Declaration Part ---
38
- if hasattr(target_object, 'textual_declaration'):
39
- decl_obj = target_object.textual_declaration
40
- if decl_obj and hasattr(decl_obj, 'text'):
41
- try:
42
- declaration_code = decl_obj.text
43
- print("DEBUG: Got declaration text.")
44
- except Exception as decl_read_err:
45
- print("ERROR: Failed to read declaration text: %s" % decl_read_err)
46
- declaration_code = "/* ERROR reading declaration: %s */" % decl_read_err
47
- else:
48
- print("WARN: textual_declaration exists but is None or has no 'text' attribute.")
49
- else:
50
- print("WARN: No textual_declaration attribute.")
51
-
52
- # --- Get Implementation Part ---
53
- if hasattr(target_object, 'textual_implementation'):
54
- impl_obj = target_object.textual_implementation
55
- if impl_obj and hasattr(impl_obj, 'text'):
56
- try:
57
- implementation_code = impl_obj.text
58
- print("DEBUG: Got implementation text.")
59
- except Exception as impl_read_err:
60
- print("ERROR: Failed to read implementation text: %s" % impl_read_err)
61
- implementation_code = "/* ERROR reading implementation: %s */" % impl_read_err
62
- else:
63
- print("WARN: textual_implementation exists but is None or has no 'text' attribute.")
64
- else:
65
- print("WARN: No textual_implementation attribute.")
66
-
67
-
68
- print("Code retrieved for: %s" % target_name)
69
- # Print declaration between markers, ensuring markers are on separate lines
70
- print("\\n" + DECL_START_MARKER)
71
- print(declaration_code)
72
- print(DECL_END_MARKER + "\\n")
73
- # Print implementation between markers
74
- print(IMPL_START_MARKER)
75
- print(implementation_code)
76
- print(IMPL_END_MARKER + "\\n")
77
-
78
- # --- LEGACY MARKERS for backward compatibility if needed ---
79
- # Combine both for old marker format, adding a separator line
80
- # legacy_combined_code = declaration_code + "\\n\\n// Implementation\\n" + implementation_code
81
- # print(CODE_START_MARKER); print(legacy_combined_code); print(CODE_END_MARKER)
82
- # --- END LEGACY ---
83
-
84
- print("SCRIPT_SUCCESS: Code retrieved.")
85
- sys.exit(0)
86
- except Exception as e:
87
- detailed_error = traceback.format_exc()
88
- error_message = "Error getting code for object '%s' in project '%s': %s\\n%s" % (POU_FULL_PATH, PROJECT_FILE_PATH, e, detailed_error)
89
- print(error_message)
90
- print("SCRIPT_ERROR: %s" % error_message)
91
- sys.exit(1)