You send the configuration, the SDK reports success, you read the value back - and nothing has moved. On embedded 3D vision units, the 3D sensors that hold their own network, capture and application settings behind a JSON configuration tree, that symptom usually has one first suspect: the parameter is marked readOnly in the device schema, and the device dropped that node while applying the rest of your write. This guide gives the one-command test that confirms it, ranks the other four causes that produce the same silence, and builds the read-back loop that turns a configuration script from hopeful to provable. The worked case is a second Ethernet port that refused to switch to DHCP.
What a clean set() return does and does not mean
A schema-driven device publishes, for every node of its configuration tree, a type, a default value and access flags. Your write arrives, the device merges the tree, and it applies the nodes it is allowed to change. Nodes flagged readOnly are neither rejected nor queued: they are skipped, by design. The design is a convenience - you can send one merged configuration that contains values you just read from the device, without filtering writable from read-only fields on your side. The price of that convenience is the exact bug in front of you: silence.
Calibrate what the return value tells you. It means the message reached the device and the tree was well-formed. It does not mean every value changed. That holds for every write path: the SDK call, the configuration GUI's save action, and a second application holding the parameter tree over TCP.
The devices that behave this way are the ones with on-board processing - stereo, structured-light and ToF units that keep their own network and capture settings - rather than sensors that stream raw frames to your PC.
- Type
- Stereo / structured light
- Type
- Depth / ToF camera
Test 1: read the readOnly flag from the schema
The schema query settles the question in one command - it answers in well under 1 s, with no restart. The vendor's command-line tool prints the whole schema with its getSchema command; pipe it through jq and ask for the parameter's node:
getSchema | jq .properties.device.properties.network.properties.interfaces.properties.eth1.properties.useDHCP
The case port answers in three lines:
{
"default": false,
"readOnly": true,
"type": "boolean"
}
readOnly: true ends the investigation: the firmware fixes this interface's DHCP switch, so every API write to it is discarded - from a script, from the SDK, from the configuration GUI alike. A reboot changes nothing, because nothing was ever stored. In a deploy script, make the same check a gate that runs before any write:
# Illustrative: dev is a connected device handle with set(), get() and a schema query.
# The key names are the device's own schema paths; get_schema() stands in for your SDK's schema call.
path = ["device", "network", "interfaces", "eth1"] # schema path, as the device prints it
param = "useDHCP" # type boolean per the schema node
schema = dev.get_schema() # dict: the published configuration schema
node = schema
for key in path:
node = node["properties"][key] # walk down to the interface's node
if node["properties"][param].get("readOnly") is True:
print(f"{param} on {'/'.join(path)}: readOnly, the write would be ignored")
else:
eth1_cfg = {"useDHCP": True} # boolean literal, matching the schema type
net_cfg = {"interfaces": {"eth1": eth1_cfg} }
dev.set({"device": {"network": net_cfg} })
got = dev.get()["device"]["network"]["interfaces"]["eth1"]["useDHCP"]
assert got is True, f"write dropped, read-back shows {got}"
The gate either prints the skip reason or proves the write with a read-back. Run it once per firmware version and file the result: the readOnly set belongs to the firmware, so keep SDK and firmware aligned before you compare two units.
Five causes that leave a set() silently ignored
The same symptom - a clean return and an unchanged value - has five causes. Test them in this order:
| # | Cause | Signature | Test | Fix |
|---|---|---|---|---|
| 1 |
readOnly in the schema |
Old value reads back immediately, before any restart | Schema query on the parameter path | Route around it: use the port or parameter that is writable |
| 2 | Save and restart missing | New value appears only after the tool's save-and-restart | Read back before and after the save action | Run the save action and let it restart the device |
| 3 | Wrong JSON type | Boolean sent as 0 or 1; a strict SDK answers with an instance-type error naming the parameter |
Compare your literal with the schema's type
|
Send the declared literal: True, not 1
|
| 4 | Value snapped or clamped | Read-back is near the sent value but not equal | Read back and subtract | Pick from the range the device's query lists |
| 5 | Another writer wins | Value reverts after a reconnect: saved user set, launch file, second session | Fresh-session read after a power cycle; audit startup writes | Update the saved set or the startup source; keep one writer |
Work down the table, because the tests get slower as you go: the schema query is one command that answers in under 1 s, the save-and-restart loop costs 2 to 5 minutes per cycle, and hunting a second writer can eat an hour. Causes 4 and 5 have their own walkthroughs - values snapped to the nearest increment and GenICam enumerations that reject values outside their symbolics - and the habit that catches all five is the same: query before you set, as the exposure-range walkthrough does for capture settings.
The fix loop: write, apply, read back, assert
- Query the schema node for the parameter you intend to change.
readOnly: truestops the loop here - route around it. - Check the node's
typeand send that literal:True, never1. - Write to the parameter path exactly as the schema prints it.
- Apply the change through the device's save action. On the case unit the configuration tool's save button applies and restarts in one step, about 60 s end to end.
- Let the restart finish before the next call.
- Open a fresh session - a new connection, not the cached handle - and read the exact parameter path back.
- Compare against the value you sent and assert per parameter, so one dropped node fails the deploy.
- Log the firmware and SDK versions beside the result, so the next engineer knows which behavior this unit ran.
Worked case: the second Ethernet port that would not take DHCP
The unit: an embedded 3D vision device with two ports, eth0 and eth1, configured through a tree rooted at device. The goal was DHCP on eth1, so a development router could address it and the unit could pull its own container images.
Two writes went out together:
dev.set({"device": {"info": {"name": "test4"} } })
dev.set({"device": {"network": {"interfaces": {"eth1": {"useDHCP": True} } } } })
dev.get()["device"]["network"]["interfaces"]["eth1"]
# {'ipv4': {'address': '192.168.42.69', 'dns': '0.0.0.0',
# 'gateway': '0.0.0.0', 'mask': 24}, 'useDHCP': False}
The name change took. The DHCP flag read back False, with the static address 192.168.42.69 and a 24-bit mask (255.255.255.0) still in place, and a reboot moved neither. The schema query then returned the node from Test 1: readOnly: true on eth1's useDHCP. The second port has no DHCP support on that firmware - a limit the release notes had not mentioned - so the write was never failing; the device was discarding it by design.
The decision that unblocked the setup: keep eth1 static, move the plan to eth0, and make that change through the configuration tool's save action, which applies and restarts the unit automatically. After the reconnect, the GUI and the API agreed on both ports - eth0 on DHCP, eth1 static - and the SSH session came in over eth1, the port that had started the hunt.
When the write lands but the network still fails
DHCP can hand out an address and the unit can still fail to reach the wider network - a different fault, downstream of the readOnly one. In the case, the router handed the unit 192.168.100.157 while the development host sat at 192.168.100.156 behind gateway 192.168.100.1. The unit pinged the host and the gateway and failed against 8.8.8.8, which the host reached without trouble. Climb the ladder, cheapest rung first:
- Ping the gateway, 192.168.100.1. Failure here is link or subnet work - the not pingable on a Linux controller walkthrough handles it.
- Ping a public address, 8.8.8.8. Gateway good, public bad: egress is blocked upstream by a plant firewall, a proxy or a VLAN that only routes locally.
- Ping your image registry by name. Address good, name bad: DNS. See what the lease actually conveyed with
systemd-resolve --status | grep -A 10 'DNS Servers', and dump the routes with/sbin/route -n.
The decision for a development unit on a locked-down plant net: do not fight the firewall from the device side. Ask for a development VLAN with internet egress, or pull images on a host inside the plant and mirror them through an internal registry. Keep expectations straight about ping while you are at it: a reply to ICMP does not prove the SDK's control port answers - ping works but the driver times out covers that split.
Two follow-ons belong next to any DHCP win. The address now moves with the lease - expect a new one at every renewal, commonly every 24 h - so keep a way to find the camera's IP after every reboot. And if the unit lands on a net with no DHCP server, run one yourself instead of reverting the port to static.
Where else the readOnly flag bites
Network settings are just where this case ran. Capture, trigger and application nodes carry readOnly too, and the flag set is a firmware fact: a parameter can be read-only on one release and writable on the next, which is one more reason to test after every update. The startup side deserves equal suspicion. A saved user set that loads on startup overwrites your write at every boot until you update the set itself, and startup writes win quietly in general - the trigger-overlap case is the pattern to recognize. Bulk writes multiply the silence: one merged tree, one clean return, several dropped nodes, and nothing in the log says which. The per-parameter assert from the fix loop turns that from a mystery into a one-line failure.
If you need every port under your control
Sometimes the flag blocks behavior the cell genuinely needs - per-port DHCP, custom routing, a second subnet for a camera head. Then stop negotiating with the firmware and move the networking onto a vision controller, where every port answers to a standard OS: DHCP client or server per interface, static routes, VLANs and firewall rules are configuration you own, and each 3D sensor hangs off it as a plain device. The split also scales cleanly: one controller with 5 or more LAN ports carries several heads plus the plant uplink.
- LAN ports
- 5+ LAN ports
- USB 3 ports
- 3-4 USB 3 ports
- LAN ports
- 5+ LAN ports
- USB 3 ports
- 3-4 USB 3 ports
- LAN ports
- 3-4 LAN ports
- USB 3 ports
- 2 USB 3 ports
Size by ports before CPU: count the heads, add one port for the plant uplink, and prefer PoE camera ports where the sensors power over the wire. The camera finder filters 3D sensors by type, and the system configurator builds the chain around them. The MV-VC5525-256G70-NN pairs PoE camera ports with eight built-in light channels; the MV-VC4712-256G10-2TH adds PCIe expansion on top of its PoE ports.
Checklist: make the next write provable
Before the write:
- Schema gate per parameter:
readOnlyabsent or false,typematching your literal. - Parameter path copied from the schema, not retyped from memory.
- One writer: other sessions, GUI tools and startup services paused or accounted for.
Applying:
- Declared literal sent:
True, never1. - Change applied through the device's save action; restart finished before the next call.
After the restart:
- Fresh session, exact parameter path read back, asserted per parameter.
- Firmware and SDK versions logged beside the result.
- With DHCP on: the lease address findable after the next reboot.
If a schema flag on your current unit is what blocked a line decision, send us the parameter path, the firmware version and a schema dump through the contact page - that trio answers most of these questions in one exchange.
Questions
Why does set() report success when the value never changed?
Because the device applies your write as one merged tree and discards the nodes its schema marks readOnly. The call still succeeds, so you can send a full configuration copied from the device without filtering writable fields yourself. Treat the return value as delivery confirmation, not as proof that any given value moved.
How do I list which parameters are readOnly on my unit?
Ask the device for its configuration schema. The vendor command-line tool's getSchema command prints the whole tree, and a jq filter on the parameter path shows the node's readOnly flag. Capture that output once per firmware version and keep it with the project, because the flag set travels with the firmware, not with your script.
Will a reboot or a factory reset apply a write that was ignored?
No. An ignored readOnly write is never stored, so no restart, save action or factory reset revives it. A reboot only matters for writes the device accepted but held until its save action ran. If a value survives both a save-and-restart and a schema check unchanged, the flag is readOnly and the supported path is elsewhere.
Can I enable DHCP on every Ethernet port of an embedded vision unit?
No - support is per port and per firmware. In the worked case the first port took DHCP after the tool's save-and-restart while the second port's useDHCP node was readOnly, so no write path could flip it. Query the schema node for each interface before you plan addressing, then keep the non-DHCP port on a static address.
The read-back value is close to what I sent but not equal - same cause?
Different cause. A near miss is clamping or snapping to the nearest supported increment; a readOnly write returns exactly the old value. Read the parameter back, compare the numbers, and choose from the range the device's query reports. Both the snapping fix and the range query have their own walkthroughs linked above.
How do I prove a configuration change stuck before the line runs?
Restart the device, open a fresh session so no cached handle flatters the result, read the exact parameter path, and assert it in the deploy script rather than trusting the set() return. For network changes add one real operation: ping the gateway, then whatever the cell actually talks to.




