The pattern is unmistakable: your startup program talks to the 3D camera all day, then you log in over SSH or at the console, run the same vendor tool, and get an XML-RPC timeout that asks whether you can ping the sensor. You can. Both boxes answer ping, the switch links them, and the boot job keeps streaming results. This guide is for anyone running a 3D camera SDK from a headless Linux host. It shows why that combination points at shell environment scope rather than the network, gives you a one-line test that proves which shell holds the camera address, and walks through the persistent fix for login shells, interactive shells and the boot service.
The symptom, stated precisely
Everything on the wire is static: the host, the camera, the switch ports. The startup job runs, pulls depth frames from the camera and publishes results without complaint. Then you log in, over SSH from a desk or with a monitor and keyboard straight on the box, and every SDK command fails with the same message: an XML-RPC timeout that asks whether you can ping the sensor. Ping succeeds. Moving the camera onto a direct cable changes nothing, and that is the telling detail: the fault travels with the shell, not with the wiring.
The startup job and your login session are different processes. They share the network stack, the cable and the camera. They do not share an environment, and that is exactly where this fault lives.
Read the error before you touch the network
The timeout comes from the control channel, not the image stream. Before an SDK can configure anything it opens a small TCP session to the sensor's XML-RPC server; in the SDK behind this error that server listens on TCP port 80 by default, and image frames ride separate, faster channels once control is up. If your data pipeline keeps flowing while your shell fails, you are watching that split in real time.
So when ping answers and the tool still times out, the tool is not dialing the camera at all. It found no address in its environment, fell back to the SDK's built-in default, and knocked on a subnet where nothing lives. On a /24 network like 192.168.1.0/24 there are 254 usable addresses, so a default pointing at a different subnet fails instantly, not after a slow crawl. Ping has already cleared the cable, the switch and the addressing. The suspect left standing is the variable that carries the camera address, and which process actually holds it.
Two neighbouring faults look alike but live elsewhere. Discovery that finds no camera at all starts at GenICam discovery uses subnet broadcast: why ping is not enough. Control that succeeds while pixel data never arrives is the timeout waiting for an image buffer case instead.
Likely causes, in order of probability
Work down this list with the child-shell test from the worked example below; the first cause explains most of these cases on its own.
-
Assigned but never exported. A plain assignment lives in the shell's own variable table. echo finds it, so the check looks green; child processes inherit nothing, so the tool starts address-less and times out. Test:
sh -c 'echo "$CAMERA_IP"'. Empty output beside a correct bare echo is a conviction. - Set in a file the failing shell never reads. Profile files cover login shells: a console login or a fresh SSH session. rc files cover interactive shells: the shells inside tmux, screen or a terminal emulator. Service units read neither. A line in only one of those files leaves the other two contexts empty. Test: open a brand-new SSH session and run the child-shell check there first.
-
The service unit carries its own copy. A unit receives only what it declares through
Environment=orEnvironmentFile=; it inherits nothing from any profile file. That is why the startup job works while every interactive command fails. Test: read the unit file. If the camera address appears there, the boot side is proven good and your shell side is confirmed empty. A camera that is not yet on the network when the unit starts is a different, boot-order fault — see GigE camera not discovered on startup: fix the boot order. - You fixed the current shell by hand earlier. One manual export masks a missing persistent line until the next login. Test: disconnect, reconnect, child-shell check first.
-
Something overrides the environment. A wrapper script, a stale config file or a differently named variable can starve the tool even when your shell looks right. Test: pass the address explicitly with the tool's
--ipoption. An explicit call that succeeds beside a bare call that fails keeps the environment guilty; both failing moves the case to the camera or the network.
Four of the five are one disease: the process you checked is not the process that fails.
Worked example: headless host, camera at 192.168.1.50, SSH shell failing
The case: a headless Linux board with no desktop, a 3D camera at the static address 192.168.1.50/24, and a boot service that processes depth data without complaint. The address is a placeholder for your own static IP; the setup otherwise mirrors the failing case this guide is built on. The SDK's command-line tool appears below as camtool, and the variable that carries the camera address as CAMERA_IP — both placeholders, not the real names; take the exact names from the SDK's environment table. CAMERA_IP holds the camera's IPv4 address as text, and its initial state in the failing session is unset for child processes.
One rule decides the case: a shell passes a variable to a child process only when the variable has been exported, and echo reads the shell's own table, unexported names included. That is why echo can look right while every child sees nothing.
Run the three checks in the failing SSH session:
ping -c 2 192.168.1.50
# 2 packets transmitted, 2 received, 0% packet loss
echo $CAMERA_IP
# 192.168.1.50
sh -c 'echo "CAM_IP=$CAMERA_IP"'
# CAM_IP=
Read the results together. The network answered 2 out of 2 packets with 0% loss, so the path is good. The parent shell shows the address. The child shell — spawned the same way the tool spawns — shows an empty string. One present, one absent, same name: the assignment was never exported. The tool therefore starts with no address, and in this SDK an unset variable means it dials its built-in default, a host that does not exist on your /24. The XML-RPC session never connects, and the error asks you to ping a camera that ping has already cleared.
The fix is two export lines plus one unit line:
# profile file (console and SSH logins)
export CAMERA_IP=192.168.1.50
# rc file (interactive shells: tmux, screen)
export CAMERA_IP=192.168.1.50
[Service]
Environment=CAMERA_IP=192.168.1.50
Unit syntax is not shell syntax: no export keyword, no expansion of other variables, one NAME=value per line. After editing the unit, reload the service manager's configuration and restart the unit.
Then prove it, in order:
sh -c 'echo "CAM_IP=$CAMERA_IP"'
# CAM_IP=192.168.1.50
camtool --ip=192.168.1.50 dump # dump = the tool's sensor-query subcommand
camtool dump # same call, no address: inherits the variable
The explicit call proves the camera speaks XML-RPC on this address. The bare call proves the variable now crosses into children. Both completing is the pass condition.
The decision tree, from the same three checks:
- Child shell empty, bare echo correct → never exported; the two export lines are the whole cure. This is the most common outcome.
- Child shell correct, tool still times out → the tool runs in another context: fix the unit, the cron entry or the wrapper there.
- Child shell shows a different address → two startup files disagree, and the last file read wins; delete one line.
- Child shell correct, explicit
--ipworks, bare call fails → the name is wrong; the tool reads a different variable, and its exact name is in the SDK's environment table.
Make the fix stick in all three places
Two export lines cover every human login on the box: one in the file the shell reads at login, one in the file it reads for every interactive shell. Keep both lines identical, byte for byte, because a discrepancy between them is the "child shell shows a different address" case above.
Services need their own line, because they inherit from nothing: add the Environment= line to every unit that runs camera code, then reload the manager's configuration and restart each unit. A running process never picks up a new variable; only a restarted one does.
Scripts that run from cron, CI or another host should not depend on the environment at all. Pass --ip explicitly there and let the variable remain a convenience for humans at the keyboard.
Prevention checklist
Five habits keep this fault from coming back:
- Test inheritance, not echo: the child-shell check is the only version that predicts what a launched tool will see.
- Open a new session after every edit: startup files are read once, when the shell starts, never again.
- Prefer
--ipin deployed scripts: cron and CI read no profile file, so the variable is not there for them. - Keep one source of truth: if the unit file carries the address, say so in the runbook so nobody "fixes" it a second time.
- Sweep for stale values when the camera address changes: a recursive search across
/etcand the shell dotfiles finds every old line before it becomes the next mystery.
When you are choosing the hardware
The working pattern is one headless controller with a network port of its own for the camera, plus a depth camera that holds a static address. When the box that runs the job is yours to pick, start from the vision controllers range and the 3D sensors range. Filter the controller range in its category listing by LAN-port count, memory, processor and PoE camera ports; the 3D camera finder filters by profiler, stereo and ToF type.
- LAN ports
- 5+ LAN ports
- USB 3 ports
- 3-4 USB 3 ports
- Light outputs
- 4 built-in channels
- Memory
- 8 GB
The MV-VC3301H-128G66 runs an Intel Core i3 with 8 GB of memory, 5+ LAN ports and 4 built-in light channels — one box for the capture service, the SDK shell work and the light wiring.
- Type
- Depth / ToF camera
On the camera side, the MV-DT01WMU is a ToF sensing camera of exactly this class: the depth-camera end of the wire in this case. The profiler and stereo lines we carry in the same category cover longer range and higher accuracy.
The decision
If the boot job works and your login fails, leave the camera, the switch and the cable alone. Run the child-shell check. An empty result means the two export lines plus the unit's Environment= line, then a restart, then the explicit --ip call and the bare one to confirm. A correct result means the tool runs in some other context, and that unit, cron entry or wrapper is where the fix goes. On the bench this is a five-minute diagnosis, and it needs nothing but a second shell.
If the trace moves the case to the camera or the network after all, the depth-camera first-connection guide and the stereo depth-failure checks pick up from there. More fixes in the same family sit on the 3D vision topic page, and the team answers hardware questions through the contact page.
Questions
Why does the boot program reach the camera while my SSH session times out?
They are separate processes with separate environments. The service receives only what its unit file declares, so if the unit carries the camera IP, boot works. Your login shell builds its environment from profile and rc files; when those never export the variable, every command you type dials the SDK's default address instead. Cron behaves like a service, not like a login, so a job that runs fine under cron can still time out interactively, and the reverse.
Where do I put the export so every shell and service sees the camera IP?
Three places, because three contexts read three different files: the profile file for login shells (console and SSH), the rc file for interactive shells under tmux or screen, and an Environment= line in each service unit that runs camera code. Two identical export lines plus one unit line is the whole job. Keep the three values byte-identical, and leave a comment in each file naming the other two; that note saves the next engineer an hour of re-diagnosis.
Does a successful ping prove the SDK can reach the camera?
No. Ping is ICMP: it proves the address is alive and the cabling and subnet are right. The failing call here is a TCP control session to the sensor's XML-RPC server, and a host firewall or a blocked port can refuse that session while ICMP sails through. Use ping to clear the network layer, then test the control channel itself by calling the tool with the camera address passed explicitly.
How do I see the variable exactly the way the tool sees it?
Run the check inside a fresh child shell, which inherits only exported variables, exactly like a tool you launch: sh -c 'echo "$CAMERA_IP"'. An empty result next to a correct bare echo means the assignment exists but was never exported. To see the whole environment a child would receive, run env | grep -i CAMERA. Both commands only read state; they change nothing on the system.
Do I need to reboot after fixing the startup files?
No. Your open terminal keeps the environment it started with, so open a new SSH session or type the export once for the current shell. Services are stricter: after editing a unit file, reload the service manager's configuration and restart the unit before testing, because a running process never picks up a new Environment= line on its own. A reboot also works, but it hides which step actually fixed the fault.

