Skip to content

Commit ce36f55

Browse files
committed
Harden reverse pipe device status
1 parent 0789fd2 commit ce36f55

3 files changed

Lines changed: 66 additions & 55 deletions

File tree

cli/external_engine/reverse_pipe_client.py

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -340,8 +340,32 @@ def send_command(self, method: str, params: dict | None = None) -> dict:
340340
cmd = {"method": method, "params": params}
341341
_write_msg(pipe_handle, cmd)
342342

343-
# Read response
344-
response = _read_msg(pipe_handle)
343+
# Read response with the same deadline. CODESYS API calls can
344+
# block after the IDE has already accepted the pipe request.
345+
box: dict[str, Any] = {}
346+
347+
def _reader():
348+
try:
349+
box["response"] = _read_msg(pipe_handle)
350+
except Exception as exc:
351+
box["error"] = exc
352+
353+
reader = threading.Thread(target=_reader, daemon=True)
354+
reader.start()
355+
reader.join(self._timeout)
356+
if reader.is_alive():
357+
CancelIo(pipe_handle)
358+
CloseHandle(pipe_handle)
359+
pipe_handle = -1
360+
raise RuntimeError(
361+
f"Timeout ({self._timeout}s) waiting for IDE response to "
362+
f"'{method}'. The daemon accepted the command but did not "
363+
f"return a response. Check the CODESYS window for modal "
364+
f"dialogs or restart Project_daemon.py."
365+
)
366+
if "error" in box:
367+
raise box["error"]
368+
response = box.get("response", {})
345369

346370
# Cache PID from responses that include it
347371
if isinstance(response, dict):
@@ -398,4 +422,4 @@ def send_command_reverse(method: str, params: dict | None = None,
398422
except RuntimeError as e:
399423
print(f"Error: {e}")
400424
except Exception as e:
401-
print(f"Unexpected error: {e}")
425+
print(f"Unexpected error: {e}")

src/ide_bridge/ide_daemon.pyw

Lines changed: 20 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -813,36 +813,29 @@ class DaemonPipeServer(object):
813813
return err
814814
try:
815815
device_filter = (params.get("device") or "").lower()
816-
all_objs = list(project.get_children(recursive=True))
817816
status_list = []
818-
# Properties to try for each object
819-
status_props = [
820-
'IsOnline', 'IsConnected', 'State', 'Online', 'Connected',
821-
'Status', 'IsActive', 'IsRunning', 'DeviceState',
822-
'get_IsOnline', 'get_IsConnected', 'get_State',
823-
]
824-
for obj in all_objs:
817+
818+
app = _ide_online_helpers.get_active_application(project)
819+
if app is not None:
825820
try:
826-
name = self._obj_name(obj)
827-
if not name:
828-
continue
829-
path = self._build_path(obj)
830-
entry = {"name": name, "path": path}
831-
for prop in status_props:
832-
try:
833-
val = getattr(obj, prop)
834-
if val is not None:
835-
if callable(val):
836-
entry[prop] = str(val())
837-
else:
838-
entry[prop] = str(val)
839-
except Exception:
840-
pass
841-
if len(entry) > 2: # has at least one status prop
842-
if not device_filter or device_filter in name.lower():
843-
status_list.append(entry)
821+
name = self._obj_name(app)
844822
except Exception:
845-
pass
823+
name = "Application"
824+
entry = {
825+
"name": name,
826+
"path": self._build_path(app),
827+
"connected": "false",
828+
}
829+
state = getattr(sys, "_codesys_daemon_loop", {})
830+
online_app = state.get("online_app") if isinstance(state, dict) else None
831+
if online_app is not None:
832+
entry["connected"] = "true"
833+
try:
834+
entry["application_state"] = str(online_app.application_state)
835+
except Exception as e:
836+
entry["application_state_error"] = str(e)
837+
if not device_filter or device_filter in name.lower():
838+
status_list.append(entry)
846839
return {"ok": True, "data": {"devices": status_list}}
847840
except Exception as e:
848841
return {"ok": False, "error": "Device status error: {0}".format(e)}

src/ide_bridge/ide_reverse_pipe_loop.py

Lines changed: 19 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1007,34 +1007,28 @@ def _cmd_device_status(params):
10071007
return err
10081008
try:
10091009
device_filter = (params.get("device") or "").lower()
1010-
all_objs = _get_device_objects(project)
10111010
status_list = []
1012-
status_props = [
1013-
'IsOnline', 'IsConnected', 'State', 'Online', 'Connected',
1014-
'Status', 'IsActive', 'IsRunning', 'DeviceState',
1015-
]
1016-
for obj in all_objs:
1011+
1012+
app = _helpers.get_active_application(project)
1013+
if app is not None:
10171014
try:
1018-
name = _obj_name(obj)
1019-
if not name:
1020-
continue
1021-
path = _build_path(obj)
1022-
entry = {"name": name, "path": path}
1023-
for prop in status_props:
1024-
try:
1025-
val = getattr(obj, prop)
1026-
if val is not None:
1027-
if callable(val):
1028-
entry[prop] = str(val())
1029-
else:
1030-
entry[prop] = str(val)
1031-
except Exception:
1032-
pass
1033-
if len(entry) > 2:
1034-
if not device_filter or device_filter in name.lower():
1035-
status_list.append(entry)
1015+
name = _obj_name(app)
10361016
except Exception:
1037-
pass
1017+
name = "Application"
1018+
entry = {
1019+
"name": name,
1020+
"path": _build_path(app),
1021+
"connected": "false",
1022+
}
1023+
online_app = sys._codesys_daemon_loop.get("online_app")
1024+
if online_app is not None:
1025+
entry["connected"] = "true"
1026+
try:
1027+
entry["application_state"] = str(online_app.application_state)
1028+
except Exception as e:
1029+
entry["application_state_error"] = str(e)
1030+
if not device_filter or device_filter in name.lower():
1031+
status_list.append(entry)
10381032
return {"ok": True, "data": {"devices": status_list}}
10391033
except Exception as e:
10401034
return {"ok": False, "error": "Device status error: {0}".format(e)}

0 commit comments

Comments
 (0)