Yes, SysProcessExecuteCommand2 can return your Python output. The vcgencmd test that already works proves the call path; sudo python mytest.py fails on a password prompt with no terminal, a relative script path, and error text on a stream the buffer never sees. Build the command from absolute paths, drop sudo, and test the five stages in order.
Yes, SysProcess.SysProcessExecuteCommand2 can hand your Python output to the PLC. The test that already works, vcgencmd measure_clock arm, proves this runtime can start an external process and bring its text back through pszStdOut. sudo python mytest.py fails for reasons that sit outside Python: sudo asks for a password in a session with nobody to type it, the interpreter and the script are resolved through a search path and a working directory your terminal does not share with the runtime, and the interpreter's error text lands on a stream the buffer never sees.
Separate the failure into five stages and each stage has one test. The end state is a command built from two absolute paths, run without sudo, triggered once per request. This page covers the PLC runtime starting an OS process; when a visualization script freezes the screen instead, fixing HMI script blocking from long-running loops is the page you want.
Why does vcgencmd work while sudo python mytest.py returns nothing?
Because that one command string changes three conditions at once: it adds a password prompt, and it resolves python and mytest.py through a search path and a working directory your terminal does not share with the runtime. The call path itself is good.
Your terminal test tells you the script runs under your account, in your current directory, with your permissions. The runtime starts the command under its own service identity, with its own environment and no terminal attached. Three of the five failure stages live inside that one command string:
sudoadds an authorization step that expects a person to answer it.pythonis found through the search path of the runtime process, which can differ from yours.mytest.pyis a relative name, resolved from the working directory the launched process inherits.
The remaining two stages are the output stream and the trigger flag. One edit to the command string, absolute paths and no sudo, removes the first three stages outright.
What does each symptom point to?
Match what you observe to the stage it names: an empty buffer with a failed result points at the launch, an empty buffer with a clean result points at elevation or the error stream, stale text points at an uncleared buffer, and one-shot behavior points at the flag.
- Empty buffer, and
Resultdiffers from the value the working vcgencmd call returns: the launch failed. Check the command string, the interpreter path and file permissions, in that order. - Empty buffer, and
Resultmatches the working call: the process ran and wrote nothing to standard output. Suspect sudo's password refusal or a Python traceback on the error stream. - The buffer still shows the previous run's text: you never cleared it. Set
stdout1 := '';before every test, or a stale success will mislead you. - The text arrives once and never again:
bDummyBoolwas set false and nothing set it true again. - The text arrives cut off mid-line: the declared string is shorter than the output, because
udiStdOutLen := SIZEOF(stdout1)passes exactly what you declared.
Read the numeric meaning of Result in the SysProcess documentation installed with your runtime, and treat the value from the working call as your reference for good.
How do you check the call path before touching Python?
Repeat the baseline vcgencmd measure_clock arm with the same buffer, the same trigger and the same code path you will use for Python, and record the returned text and the value of Result; that pair is the reference for every later test.
Also confirm the file lives on the runtime host. Run ls -l /opt/scripts/mytest.py on the machine executing the runtime, and nowhere else; a script that exists only on your engineering workstation is invisible to the PLC. Then work the five stages:
| Stage | Test | Success looks like |
|---|---|---|
| Process interface | Repeat vcgencmd measure_clock arm from the PLC |
Clock text in stdout1, Result equal to baseline |
| Interpreter | which python3 on the runtime host |
An absolute path, for example /usr/bin/python3 |
| Script path | ls -l /opt/scripts/mytest.py on the runtime host |
File listed, readable by the runtime account |
| Elevation | sudo -n /usr/bin/python3 /opt/scripts/mytest.py on the host |
Temperature prints; status 1 means a password was demanded |
| Output stream | /usr/bin/python3 -c "print('TEST-OK')" from the PLC |
TEST-OK in stdout1 |
What command string should you pass instead?
Build it from two absolute paths, the interpreter and the script, with no sudo and no relative names: /usr/bin/python3 /opt/scripts/mytest.py. Find the interpreter with which python3 on the runtime host, and put the script in a fixed directory.
A relative name like mytest.py is resolved from the working directory of the launched process. A service can start in a directory where the file does not exist, so the interpreter reports a missing file on the error stream and your buffer stays empty. Absolute paths delete that entire stage. The interpreter is the program you actually launch; why a Python script needs an interpreter to start it covers that split in more depth.
Give command1 room. A STRING[80] in a CODESYS project holds 80 characters of command text plus the null terminator, enough for the 39-character example below. If a path contains a space, quote it inside the string and confirm the parsing rules in the SysProcess documentation before you rely on the quotes. Then open the script and fix the same fault inside it: every file it opens by a relative name inherits the same problem, so make each path in the script absolute too.
How do you test whether sudo is the failure?
Run sudo with the -n flag on the runtime host: -n forbids the password prompt, and sudo's manual documents that when a password would be required it reports an error and exits with status 1. That status is exactly what the runtime-triggered call meets.
A process started from PLC logic has no controlling terminal, so nobody types the password. sudo writes its refusal to the error stream and the child never runs, which is why the buffer comes back empty. The command sudo -n /usr/bin/python3 /opt/scripts/mytest.py in a terminal shows you the same refusal with the message visible, and its status 1 names the stage.
The fix is to drop sudo and let the runtime account read the interface directly. Many boards expose the temperature through a file readable by a group, so adding the runtime account to that group is the clean solution. If the host genuinely requires elevation, have the host administrator write a sudoers entry limited to that one command with NOPASSWD. Never give the runtime account unrestricted passwordless execution; it runs whatever string the PLC sends.
Why can the buffer stay empty when the script ran?
Because print() and the failure paths write to two different streams: your temperature line goes to standard output, which pszStdOut fills, while tracebacks, missing-file messages and sudo's refusal go to standard error, which your single buffer does not collect.
So plan for one stream and make the script speak entirely through print(). Prove the capture stage with the smallest possible command: /usr/bin/python3 -c "print('TEST-OK')" produces 7 characters plus a newline. Clear stdout1 := ''; first, call it once through the flag guard, and record Result.
If TEST-OK arrives, launch and capture are both good and the fault is inside the script or its access to the temperature interface. If it never arrives, the fault is upstream of Python: the launch, the interpreter path or the buffer declaration. One command has split the problem in half. If real output arrives but stops mid-line, declare a longer string, because SIZEOF(stdout1) caps what is collected.
Why does the call fire only once?
Because bDummyBool is set false after the first call and nothing sets it true again, so the guard is doing its job. Keep that single-shot behavior while you commission, and re-arm the flag from an edge or a timer only after the text is arriving.
A call that starts a process and waits for its output belongs in a trigger that runs once per request, and your guard already provides that. When you move to a refresh every 30 s, the re-arm logic should live in one place; local timer versus shared timer in control software sorts out that choice. Until the worked sequence below passes end to end, leave the flag exactly as it is.
How does a worked example run end to end?
Take a 39-character command, /usr/bin/python3 /opt/scripts/mytest.py, a 255-character output buffer, and a script that prints one 12-character temperature line: the arithmetic says the buffer holds the whole line with room to spare, and the four calls below pin the failing stage.
Set up command1 : STRING[80] and stdout1 : STRING[255], move the script to /opt/scripts/mytest.py, and confirm the interpreter with which python3.
Count the command: /usr/bin/python3 is 16 characters, the space is 1, and /opt/scripts/mytest.py is 22. That is 16 + 1 + 22 = 39 characters, so the STRING[80] holds it with 41 to spare. The script prints temp=48.3'C plus its newline, 12 characters against 255 available. The buffer was never the limit.
The four calls, in order:
- Baseline:
vcgencmd measure_clock arm. Clock text arrives; note the value inResult. - Interpreter and capture:
/usr/bin/python3 -c "print('TEST-OK')". The 7-character message lands instdout1, so launch and capture are both good. - Elevation: on the host,
sudo -n /usr/bin/python3 /opt/scripts/mytest.py. Status 1 with a refusal message names the password stage as the blocker. - The fix: add the runtime account to the group that reads the temperature interface, drop sudo, and pass
/usr/bin/python3 /opt/scripts/mytest.pythrough the flag guard.temp=48.3'Cappears instdout1, 12 of 255 characters used, andResultmatches the baseline.
Clear stdout1 := ''; before each call so step 4 cannot show you step 1's leftovers.
Recommendation: Commission with the guard exactly as written: clear the buffer, pass /usr/bin/python3 /opt/scripts/mytest.py with absolute paths and no sudo, and grant the runtime account direct read access to the temperature interface instead of elevating. Keep that form until the temperature line appears and Result matches the baseline, then widen to a timer-driven refresh. Once the line sits in a PLC string, the next decision is what the floor sees, and live factory data display: signage or dashboard makes that split.
Questions
Does SysProcessExecuteCommand2 launch commands through a shell?
Treat it as a direct process launch until the SysProcess documentation in your installation says otherwise. Everything a shell adds, pipes, redirection, variable expansion, compound commands, may be missing or parsed with different rules. Commission with one executable and plain arguments, and read the library documentation before you rely on any shell syntax.
How do I see a Python traceback if the buffer stays empty?
Route the failure through print() yourself. Wrap the script body in try and except, and in the except block write except Exception as e: print('ERR ' + str(e)). A bare traceback goes to standard error, which your single output buffer may never receive, so this one line turns any crash into text the PLC can read.
Is sudo ever the right way to read a temperature from PLC code?
Test without it first, under the runtime's own account. Many temperature interfaces grant read access to a group, and adding the runtime account to that group is the clean fix. If the host genuinely requires elevation, the host administrator can write a sudoers entry limited to that one command with NOPASSWD, which removes the prompt without granting broad rights.
Should the command say python or python3?
Use whichever the runtime host actually reports. Log in to that host, run which python and which python3, and take the absolute path of the interpreter your successful test used. On many hosts python is absent or points at a different major version, so python3 with its full path is the dependable default.
Planning information, not legal or engineering advice. Rules differ by place: check codes and permits with your local authority and your licensed trades.