@codesys/mcp-toolkit 2.1.2 → 2.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.
Files changed (78) hide show
  1. package/CHANGELOG.md +100 -0
  2. package/README.md +190 -10
  3. package/README.zh-CN.md +254 -0
  4. package/dist/actions.d.ts +9 -4
  5. package/dist/actions.js +1 -1
  6. package/dist/actions.js.map +1 -1
  7. package/dist/bridge/bridge.d.ts +12 -1
  8. package/dist/bridge/bridge.js +77 -11
  9. package/dist/bridge/bridge.js.map +1 -1
  10. package/dist/cli.js +7 -1
  11. package/dist/cli.js.map +1 -1
  12. package/dist/codesys/debug.d.ts +5 -0
  13. package/dist/codesys/debug.js +89 -0
  14. package/dist/codesys/debug.js.map +1 -0
  15. package/dist/codesys/device-tools.d.ts +3 -0
  16. package/dist/codesys/device-tools.js +38 -0
  17. package/dist/codesys/device-tools.js.map +1 -0
  18. package/dist/codesys/graphical.d.ts +76 -0
  19. package/dist/codesys/graphical.js +226 -0
  20. package/dist/codesys/graphical.js.map +1 -0
  21. package/dist/codesys/lang.d.ts +39 -0
  22. package/dist/codesys/lang.js +177 -0
  23. package/dist/codesys/lang.js.map +1 -0
  24. package/dist/codesys/simtest.d.ts +48 -0
  25. package/dist/codesys/simtest.js +103 -0
  26. package/dist/codesys/simtest.js.map +1 -0
  27. package/dist/codesys/st.d.ts +1 -1
  28. package/dist/codesys/st.js.map +1 -1
  29. package/dist/codesys/trace.d.ts +19 -0
  30. package/dist/codesys/trace.js +139 -0
  31. package/dist/codesys/trace.js.map +1 -0
  32. package/dist/hmi/native.d.ts +89 -0
  33. package/dist/hmi/native.js +447 -0
  34. package/dist/hmi/native.js.map +1 -0
  35. package/dist/hmi/render.d.ts +90 -0
  36. package/dist/hmi/render.js +307 -0
  37. package/dist/hmi/render.js.map +1 -0
  38. package/dist/hmi/route.d.ts +32 -0
  39. package/dist/hmi/route.js +184 -0
  40. package/dist/hmi/route.js.map +1 -0
  41. package/dist/hmi/service.d.ts +45 -0
  42. package/dist/hmi/service.js +182 -0
  43. package/dist/hmi/service.js.map +1 -0
  44. package/dist/hmi/spec.d.ts +821 -0
  45. package/dist/hmi/spec.js +191 -0
  46. package/dist/hmi/spec.js.map +1 -0
  47. package/dist/hmi/templates.d.ts +11 -0
  48. package/dist/hmi/templates.js +92 -0
  49. package/dist/hmi/templates.js.map +1 -0
  50. package/dist/hmi/tools.d.ts +4 -0
  51. package/dist/hmi/tools.js +222 -0
  52. package/dist/hmi/tools.js.map +1 -0
  53. package/dist/hmi/value.d.ts +3 -0
  54. package/dist/hmi/value.js +26 -0
  55. package/dist/hmi/value.js.map +1 -0
  56. package/dist/panel/hmi.js +138 -0
  57. package/dist/panel.d.ts +3 -0
  58. package/dist/panel.js +17 -0
  59. package/dist/panel.js.map +1 -1
  60. package/dist/server.d.ts +1 -1
  61. package/dist/server.js +7 -2
  62. package/dist/server.js.map +1 -1
  63. package/dist/tools.d.ts +5 -1
  64. package/dist/tools.js +19 -3
  65. package/dist/tools.js.map +1 -1
  66. package/dist/viewer.d.ts +1 -1
  67. package/dist/viewer.js +13 -2
  68. package/dist/viewer.js.map +1 -1
  69. package/package.json +1 -1
  70. package/watcher/mcp_alarms.py +134 -0
  71. package/watcher/mcp_debug.py +161 -0
  72. package/watcher/mcp_devices.py +83 -0
  73. package/watcher/mcp_lang.py +179 -0
  74. package/watcher/mcp_ops.py +3 -1
  75. package/watcher/mcp_trace.py +90 -0
  76. package/watcher/mcp_ui.py +54 -0
  77. package/watcher/mcp_visu.py +422 -0
  78. package/watcher/watcher.py +52 -10
@@ -0,0 +1,83 @@
1
+ # -*- coding: utf-8 -*-
2
+ # Devices from CODESYS's device repository: search the installed device
3
+ # descriptions (fieldbus masters and slaves, I/O modules) and add or plug one into
4
+ # the device tree. Documented scripting API (device_repository, ScriptDeviceObject).
5
+ # Offline only: nothing here goes online.
6
+ #
7
+ # Keep this file ASCII-only (IronPython 2.7 inside CODESYS).
8
+ import mcp_ops
9
+ import mcp_sim
10
+ from mcp_ops import Ctx, OpError, _safe
11
+ from mcp_compat import to_text
12
+
13
+ import scriptengine as se
14
+
15
+
16
+ def _desc(d):
17
+ did = _safe(lambda: d.device_id)
18
+ out = {
19
+ "name": to_text(_safe(lambda: d.name, "")),
20
+ "vendor": to_text(_safe(lambda: d.vendor, "")),
21
+ "description": to_text(_safe(lambda: d.description, "") or "")[:200],
22
+ }
23
+ if did is not None:
24
+ out["type"] = int(_safe(lambda: did.type, 0))
25
+ out["id"] = to_text(_safe(lambda: did.id, ""))
26
+ out["version"] = to_text(_safe(lambda: did.version, ""))
27
+ order = _safe(lambda: d.order_number)
28
+ if order:
29
+ out["order_number"] = to_text(order)
30
+ return out
31
+
32
+
33
+ def op_devices_search(args):
34
+ repo = _safe(lambda: se.device_repository)
35
+ if repo is None:
36
+ raise OpError("unavailable", "This CODESYS has no device repository in scripting.")
37
+ text = args.get("text") or ""
38
+ found = repo.get_all_devices(name=text, source=None)
39
+ out = []
40
+ for d in found or []:
41
+ full = d
42
+ if not to_text(_safe(lambda: d.name, "") or ""):
43
+ did = _safe(lambda: d.device_id)
44
+ full = (_safe(lambda: repo.get_device(did)) if did is not None else None) or d
45
+ item = _desc(full)
46
+ if not item["name"]:
47
+ info = _safe(lambda: full.device_info)
48
+ item["name"] = to_text(_safe(lambda: info.name, "") or "")
49
+ item["vendor"] = item["vendor"] or to_text(_safe(lambda: info.vendor, "") or "")
50
+ item["description"] = item["description"] or to_text(_safe(lambda: info.description, "") or "")[:200]
51
+ out.append(item)
52
+ if len(out) >= int(args.get("limit") or 40):
53
+ break
54
+ return {"devices": out, "more": len(found or []) > len(out)}
55
+
56
+
57
+ def op_devices_add(args):
58
+ c = Ctx()
59
+ parent = c.resolve(args["parent"])
60
+ if not _safe(lambda: parent.is_device, False):
61
+ raise OpError("bad_path", "%s is not a device (add devices under a device or a connector's device, e.g. '/Device')." % args["parent"])
62
+ how = args.get("how") or "add"
63
+ module = args.get("module")
64
+ if how == "plug":
65
+ parent.plug(args["name"], int(args["type"]), args["id"], args["version"], module)
66
+ dev = parent
67
+ else:
68
+ fn = parent.add if how == "add" else parent.insert
69
+ if how == "insert":
70
+ parent.insert(args["name"], int(args.get("index") or 0), int(args["type"]), args["id"], args["version"], module)
71
+ else:
72
+ fn(args["name"], int(args["type"]), args["id"], args["version"], module)
73
+ dev = None
74
+ for ch in parent.get_children(False):
75
+ if to_text(_safe(lambda: ch.get_name(), "")).lower() == args["name"].lower():
76
+ dev = ch
77
+ if dev is None:
78
+ raise OpError("codesys_error", "CODESYS added nothing named %s under %s." % (args["name"], args["parent"]))
79
+ return {"path": c.path_of(dev), "saved": c.save_if(args)}
80
+
81
+
82
+ mcp_ops.OPS["devices.search"] = op_devices_search
83
+ mcp_ops.OPS["devices.add"] = mcp_sim._locked(op_devices_add)
@@ -0,0 +1,179 @@
1
+ # -*- coding: utf-8 -*-
2
+ # What CODESYS's compiler knows about the code: the interface of any POU or type
3
+ # (including FBs from compiled libraries, which the scripting API can't read),
4
+ # and cross-references (where a name is read, written or called).
5
+ #
6
+ # Both use CODESYS's own services, reached through a plugin's APEnvironment:
7
+ # LanguageModelMgr.FindSignaturesByName and CrossReferenceService. They work on the
8
+ # compiled language model, so the application is built first when nothing is found.
9
+ #
10
+ # Keep this file ASCII-only (IronPython 2.7 inside CODESYS).
11
+ import mcp_ops
12
+ from mcp_ops import Ctx, OpError
13
+ from mcp_compat import to_text
14
+
15
+ _ENVS = ("_3S.CoDeSys.RecipeManObject.APEnvironment", "_3S.CoDeSys.VisualEditor.APEnvironment", "_3S.CoDeSys.SecurityManager.APEnvironment")
16
+ _CACHE = {}
17
+
18
+
19
+ def _find_type(name):
20
+ if name in _CACHE:
21
+ return _CACHE[name]
22
+ try:
23
+ from System import AppDomain
24
+ except ImportError:
25
+ raise OpError("unavailable", "CODESYS's language services need CODESYS (not available in this engine).")
26
+ found = None
27
+ for asm in AppDomain.CurrentDomain.GetAssemblies():
28
+ try:
29
+ found = asm.GetType(name)
30
+ except Exception:
31
+ found = None
32
+ if found is not None:
33
+ break
34
+ _CACHE[name] = found
35
+ return found
36
+
37
+
38
+ def _service(prop):
39
+ try:
40
+ from System.Reflection import BindingFlags
41
+ except ImportError:
42
+ raise OpError("unavailable", "CODESYS's language services need CODESYS (not available in this engine).")
43
+ for tname in _ENVS:
44
+ t = _find_type(tname)
45
+ if t is None:
46
+ continue
47
+ p = t.GetProperty(prop, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static)
48
+ if p is not None:
49
+ v = p.GetValue(None, None)
50
+ if v is not None:
51
+ return v
52
+ raise OpError("unavailable", "This CODESYS doesn't offer %s to scripts." % prop)
53
+
54
+
55
+ def _get(o, name, default=None):
56
+ try:
57
+ return getattr(o, name)
58
+ except Exception:
59
+ try:
60
+ t = o.GetType()
61
+ for it in [t] + list(t.GetInterfaces()):
62
+ p = it.GetProperty(name)
63
+ if p is not None:
64
+ return p.GetValue(o, None)
65
+ except Exception:
66
+ pass
67
+ return default
68
+
69
+
70
+ def _caller(c):
71
+ """A POU of the application to resolve names from (its scope sees the libraries)."""
72
+ for ch in c.app.get_children(True):
73
+ try:
74
+ if ch.has_textual_declaration and "PROGRAM" in to_text(ch.textual_declaration.text).upper()[:200]:
75
+ return ch
76
+ except Exception:
77
+ pass
78
+ return c.app
79
+
80
+
81
+ def _clean(s):
82
+ s = to_text(s or "").strip()
83
+ return " ".join(s.split()) if s else ""
84
+
85
+
86
+ def _var(v, section):
87
+ attrs = [to_text(a) for a in (_get(v, "Attributes") or [])]
88
+ out = {"name": to_text(_get(v, "OrgName") or _get(v, "Name")), "type": to_text(_get(v, "Type")), "section": section}
89
+ comment = _clean(_get(v, "Comment")).lstrip("| ").strip()
90
+ if comment:
91
+ out["comment"] = comment
92
+ if "hide" in attrs:
93
+ out["hidden"] = True
94
+ init = _get(v, "Initial")
95
+ if init is not None:
96
+ out["init"] = to_text(init)
97
+ return out
98
+
99
+
100
+ def _signature(lmm, c, sig):
101
+ ins = set(to_text(_get(v, "OrgName")) for v in (_get(sig, "Inputs") or []))
102
+ outs = set(to_text(_get(v, "OrgName")) for v in (_get(sig, "Outputs") or []))
103
+ variables = []
104
+ for v in lmm.GetAllVariables(sig, c.app.guid):
105
+ n = to_text(_get(v, "OrgName"))
106
+ variables.append(_var(v, "input" if n in ins else "output" if n in outs else "other"))
107
+ subs = []
108
+ try:
109
+ for s in sig.GetSubSignatures() or []:
110
+ subs.append(to_text(_get(s, "OrgName") or _get(s, "Name")))
111
+ except Exception:
112
+ pass
113
+ return {
114
+ "name": to_text(_get(sig, "OrgName") or _get(sig, "Name")),
115
+ "kind": to_text(_get(sig, "POUType")),
116
+ "library": to_text(_get(sig, "LibraryPath") or ""),
117
+ "comment": _clean(_get(sig, "Comment")),
118
+ "variables": variables,
119
+ "members": subs,
120
+ }
121
+
122
+
123
+ def op_lang_signature(args):
124
+ c = Ctx()
125
+ lmm = _service("LanguageModelMgr")
126
+ caller = _caller(c)
127
+ built = False
128
+ out = []
129
+ for name in args["names"]:
130
+ sigs = lmm.FindSignaturesByName(c.proj.handle, c.app.guid, caller.guid, name)
131
+ if (sigs is None or len(sigs) == 0) and not built:
132
+ c.app.build()
133
+ built = True
134
+ sigs = lmm.FindSignaturesByName(c.proj.handle, c.app.guid, caller.guid, name)
135
+ if sigs is None or len(sigs) == 0:
136
+ out.append({"name": name, "error": "not found (check the spelling, and that the library is referenced)"})
137
+ continue
138
+ out.append(_signature(lmm, c, sigs[0]))
139
+ return {"signatures": out, "built": built}
140
+
141
+
142
+ def op_lang_xref(args):
143
+ import System
144
+ from System import Guid
145
+ c = Ctx()
146
+ xs = _service("CrossReferenceService")
147
+ T = _find_type("_3S.CoDeSys.LanguageModelUtilities.CrossRefSearchType")
148
+ O = _find_type("_3S.CoDeSys.LanguageModelUtilities.CrossRefOccurence")
149
+ M = _find_type("_3S.CoDeSys.LanguageModelUtilities.CrossReferenceMatchType")
150
+ if T is None or O is None or M is None:
151
+ raise OpError("unavailable", "This CODESYS doesn't offer cross-references to scripts.")
152
+ if args.get("build", True):
153
+ c.app.build()
154
+ kind = {"variable": "Variables", "pou": "POUs", "type": "Types"}.get(args.get("kind") or "variable", "Variables")
155
+ try:
156
+ st = System.Enum.Parse(T, kind)
157
+ except Exception:
158
+ st = System.Enum.Parse(T, "Variables")
159
+ nodes = xs.GetCrossReferenceNodesWithinApplication(args["name"], "", Guid.Empty, st, System.Enum.Parse(O, "LanguageModel"), System.Enum.Parse(M, "Existing"), c.app.guid)
160
+ refs = []
161
+ for nd in nodes or []:
162
+ pos = _get(nd, "Position", 0) or 0
163
+ try:
164
+ pos = int(pos)
165
+ except Exception:
166
+ pos = 0
167
+ refs.append({
168
+ "access": to_text(_get(nd, "AccessText") or ""),
169
+ "pou": to_text(_get(nd, "POUName") or ""),
170
+ "location": to_text(_get(nd, "Location") or ""),
171
+ "text": _clean(_get(nd, "Text")),
172
+ })
173
+ return {"name": args["name"], "references": refs}
174
+
175
+
176
+ mcp_ops.OPS.update({
177
+ "lang.signature": op_lang_signature,
178
+ "lang.xref": op_lang_xref,
179
+ })
@@ -542,6 +542,8 @@ def op_object_create(args):
542
542
  obj = parent.create_dut(name, dt)
543
543
  elif kind == "gvl":
544
544
  obj = parent.create_gvl(name)
545
+ elif kind == "persistent_vars":
546
+ obj = parent.create_persistentvars(name)
545
547
  elif kind == "folder":
546
548
  obj = parent.create_folder(name)
547
549
  else:
@@ -1097,7 +1099,7 @@ def _reflect_api(path):
1097
1099
  def op_api_probe(args):
1098
1100
  """The scripting API this CODESYS offers: member names of the main objects, the
1099
1101
  enums the watcher relies on, version-like values, and the object type GUIDs of
1100
- the open project with their kinds. For comparing service packs (docs/sp22-plan.md)."""
1102
+ the open project with their kinds. For comparing service packs (docs/codesys-api-sp21-sp22.md)."""
1101
1103
  c = Ctx(require_project=False)
1102
1104
  objs = {"se": se, "system": _safe(lambda: se.system), "projects": _safe(lambda: se.projects), "project": c.proj, "application": c.app}
1103
1105
  samples = {}
@@ -0,0 +1,90 @@
1
+ # -*- coding: utf-8 -*-
2
+ # CODESYS Trace in the simulation: a trace object records variables every cycle of a
3
+ # task; the data comes back as a table. Uses CODESYS's trace script driver
4
+ # (se.trace.create, open_editor().download/start/save), only while CODESYS's own
5
+ # simulation runs (mcp_sim checks the device is in simulation mode).
6
+ #
7
+ # Keep this file ASCII-only (IronPython 2.7 inside CODESYS).
8
+ import os
9
+
10
+ import mcp_ops
11
+ import mcp_sim
12
+ from mcp_ops import Ctx, OpError, name_of
13
+
14
+
15
+ import scriptengine as se
16
+
17
+
18
+ def _find(c, name):
19
+ for ch in c.app.get_children(True):
20
+ if name_of(ch).lower() == name.lower():
21
+ return ch
22
+ return None
23
+
24
+
25
+ def _first_task(c):
26
+ for ch in c.app.get_children(True):
27
+ try:
28
+ if ch.is_task:
29
+ return ch
30
+ except Exception:
31
+ pass
32
+ return None
33
+
34
+
35
+ def op_trace_setup(args):
36
+ c = Ctx()
37
+ if not hasattr(se, "trace"):
38
+ raise OpError("unavailable", "This CODESYS has no trace scripting.")
39
+ name = args["name"]
40
+ old = _find(c, name)
41
+ if old is not None:
42
+ old.remove()
43
+ task = args.get("task")
44
+ if not task:
45
+ t = _first_task(c)
46
+ if t is None:
47
+ raise OpError("not_found", "The application has no task to record in.")
48
+ task = name_of(t)
49
+ tr = se.trace.create(c.app, name, task)
50
+ for v in args["variables"]:
51
+ tv = tr.variable_list.add()
52
+ tv.variable_name = v
53
+ if args.get("every_n_cycles"):
54
+ try:
55
+ tr.every_n_cycles = int(args["every_n_cycles"])
56
+ except Exception:
57
+ pass
58
+ return {"name": name, "task": task, "variables": list(args["variables"]), "saved": c.save_if(args)}
59
+
60
+
61
+ def op_trace_record(args):
62
+ c = Ctx()
63
+ mcp_sim._online() # raises unless the simulation runs
64
+ obj = _find(c, args["name"])
65
+ if obj is None:
66
+ raise OpError("not_found", "No trace named %s. Set it up with trace_setup before starting the simulation." % args["name"])
67
+ if not hasattr(obj, "open_editor"):
68
+ raise OpError("unavailable", "%s is not a trace this CODESYS can record from a script." % args["name"])
69
+ ed = obj.open_editor()
70
+ ed.download()
71
+ ed.start()
72
+ mcp_sim._pause(int(args.get("ms") or 3000))
73
+ path = args["path"]
74
+ folder = os.path.dirname(path)
75
+ if folder and not os.path.isdir(folder):
76
+ os.makedirs(folder)
77
+ ed.save(path)
78
+ try:
79
+ ed.stop()
80
+ except Exception:
81
+ pass
82
+ return {"path": path}
83
+
84
+
85
+ mcp_ops.OPS["trace.setup"] = mcp_sim._locked(op_trace_setup)
86
+ mcp_ops.OPS["trace.record"] = op_trace_record
87
+ # The visualization builder changes the project too: not while the simulation runs.
88
+ for _op in ("visu.build", "visu.manager"):
89
+ if _op in mcp_ops.OPS:
90
+ mcp_ops.OPS[_op] = mcp_sim._locked(mcp_ops.OPS[_op])
@@ -0,0 +1,54 @@
1
+ # -*- coding: utf-8 -*-
2
+ # --show-window extras: open the object a request just wrote in a CODESYS editor tab, so
3
+ # the user sees the code arrive in the window instead of only the project tree.
4
+ #
5
+ # CODESYS's scripting API has no call for this. It uses CODESYS's own (undocumented) .NET
6
+ # frame instead: IFrame.OpenEditorView, the call behind "Edit Object". Checked on CODESYS
7
+ # V3.5 SP21 and SP22. Best-effort: a failure is only logged and never changes a result.
8
+ #
9
+ # Keep this file ASCII-only (IronPython 2.7 inside CODESYS).
10
+
11
+ _CACHE = {}
12
+
13
+ # Any plugin's APEnvironment exposes the engine (with its frame) and the object manager.
14
+ _ENV_TYPES = ("_3S.CoDeSys.SecurityManager.APEnvironment", "_3S.CoDeSys.VisualEditor.APEnvironment")
15
+
16
+
17
+ def _find_env():
18
+ from System import AppDomain
19
+ asms = list(AppDomain.CurrentDomain.GetAssemblies())
20
+ for name in _ENV_TYPES:
21
+ for asm in asms:
22
+ try:
23
+ t = asm.GetType(name)
24
+ except Exception:
25
+ t = None
26
+ if t is not None:
27
+ return t
28
+ return None
29
+
30
+
31
+ def _env():
32
+ if "env" not in _CACHE:
33
+ try:
34
+ _CACHE["env"] = _find_env()
35
+ except Exception:
36
+ _CACHE["env"] = None
37
+ return _CACHE["env"]
38
+
39
+
40
+ def open_editor(se, obj):
41
+ """Opens obj in its editor in the CODESYS window (or brings its tab forward).
42
+
43
+ Returns "opened", "fake" (the test engine) or "unavailable" (no frame found)."""
44
+ if hasattr(se, "FAKE"):
45
+ return "fake"
46
+ env = _env()
47
+ if env is None:
48
+ return "unavailable"
49
+ from System import Guid
50
+ engine = env.GetProperty("Engine").GetValue(None, None)
51
+ objects = env.GetProperty("ObjectMgr").GetValue(None, None)
52
+ stub = objects.GetMetaObjectStub(se.projects.primary.handle, obj.guid)
53
+ engine.Frame.OpenEditorView(stub, Guid.Empty, None)
54
+ return "opened"