A four-camera rig that boots cleanly with every head attached, then core dumps at startup the first time one head is missing, has one specific cause: the controller's saved configuration still lists the absent port, the SDK's port-list call reads that entry's null part-number field without checking it, and the JSON type error that follows is never caught. This is for anyone whose startup self-check is supposed to count cameras and instead kills the process. You will confirm the cause from the configuration document, replace the convenience call with a defensive check that survives a missing head, and get an alarm that names the dead port instead of a dead application.
The symptom: termination inside the SDK's parse
The failure has a clear signature. The C++ runtime prints its terminate message for an uncaught exception whose class name ends in type_error, thrown from the JSON layer bundled with the camera SDK, and the process leaves a core dump of several hundred MB. It dies on the line that enumerates camera ports, before your first port log prints. The network is innocent: a call that reads the whole configuration document from the same controller returns complete data on the same boot, in well under 1 s on a healthy LAN. And it only appears after a head has been disconnected and the controller power-cycled. A startup check that takes the cell down is worse than no check; the sensor-side version of that mistake, where a 3D laser sensor fails its own startup self-check, has its own guide.
Likely causes, in order of probability
-
Uncaught JSON type error in the port-list call. The SDK's convenience method for listing ports parses the device configuration and reads a per-port identity field with no null check. The terminate message naming
type_erroris this failure. Test: call the full-configuration getter yourself; it succeeds while the port-list call dies. - A stale persistent configuration on the controller. A commissioning step saved the port map onto the device, so every boot expects every configured head. Test: read the saved-configuration copy through the SDK's dump or saved-init command and compare it with the live document.
- A real link or power fault on the missing head. True, but rarely the direct cause of the crash, because the configuration read over the same network still works. Test: port LEDs, ping, and the subnet and link checklist for a 3D camera that will not answer.
Why the absent port keeps a null part number
During commissioning, a save operation wrote the port map into the controller's persistent storage; vendor tooling does this under the hood in normal use, and your own save call does the same. From then on the controller boots with that expectation. When a head is missing, the port entry survives, but its info object carries no identity: the part-number field is null.
{ "ports": { "port3": { "info": { "partNumber": null } } } }
That is the whole trap. A controller never given a saved configuration usually reports fewer ports and moves on, so the crash only exists where a save happened. Clearing the save means a factory reset on many controllers, which also wipes user files and containers, so the lasting fix is code that tolerates the null. Leftover device state behind flaky startups is the broader family; the known-state reset for a flaky 3D camera test is the same disease at test time.
The fix: read the tree, check the field, catch the type
Three layers, in this order.
Fetch the configuration document yourself. The full-configuration getter (a Get()-style call on the device object) returns the ports object shown above. You own the null checks there, so a missing head becomes data instead of a crash.
Check identity before you count a port as present. Three conditions: the info key exists, partNumber holds a JSON string, and that string is not empty. Anything else - null, missing key, wrong type - goes on the missing list with its port name.
Catch as a backstop. Wrap each per-port parse in a handler for the JSON library's exception base class, so a malformed entry degrades to "missing" instead of std::terminate.
// Illustrative: adapt the JSON type name to your SDK. On the common
// nlohmann/json-derived stacks, Json::exception catches both parse
// and type errors.
#include <string>
#include <vector>
struct HeadStatus {
int configured = 0; // entries in "ports"
int present = 0; // entries with a usable identity
std::vector<std::string> missing; // port names to alarm on
};
HeadStatus CheckHeads(const Json& config) {
HeadStatus s;
const Json ports = config.value("ports", Json::object());
for (auto it = ports.begin(); it != ports.end(); ++it) {
++s.configured;
try {
const Json info = it.value().value("info", Json::object());
const Json pn = info.value("partNumber", Json());
if (pn.is_string() && !pn.get<std::string>().empty()) {
++s.present;
} else {
s.missing.push_back(it.key());
}
} catch (const Json::exception&) {
s.missing.push_back(it.key()); // malformed entry counts as absent
}
}
return s;
}
And the test that proves the result: port3 carries the null that this failure produces; the other port names and identity strings are illustrative.
const std::string doc = R"(
{ "ports": {
"port2": { "info": { "partNumber": "AA11" } },
"port3": { "info": { "partNumber": null } },
"port4": { "info": { "partNumber": "BB22" } },
"port5": { "info": { "partNumber": "CC33" } } } }
)";
const HeadStatus s = CheckHeads(Json::parse(doc));
assert(s.configured == 4);
assert(s.present == 3);
assert(s.missing.size() == 1 && s.missing[0] == "port3");
Reading a field from a null value with that accessor throws type_error on the nlohmann/json family the SDK stack wraps - the class name in your crash trace ends in type_error for exactly that reason. Once the check passes and several heads stream in one process, keep one acquisition object per camera, as in multiple GenTL cameras in one process.
A worked check: four heads, port3 dead after a reboot
Inputs. The controller's saved configuration lists four ports, port2 through port5. The port3 head was unplugged and the controller power-cycled, so its entry reads { "info": { "partNumber": null } }. The recipe needs four heads for full rate. Policy: poll the check every 2 s across a 30 s reconnect window.
Rule. A port is present only when info.partNumber exists, is a JSON string, and is not empty. The convenience port-list call instead throws at the first null it meets and returns nothing.
Arithmetic. Four entries, two conditions each: 8 checks total. Ports 2, 4 and 5 pass both: 3 × 2 = 6 checks pass. Port3 fails the string test: 2 checks fail, 1 port missing. Present = 4 - 1 = 3; missing list = ["port3"]. The naive path fails on the first of port3's 2 failed checks and reports 0 heads before the process terminates.
Result. After 15 polls × 2 s = 30 s, the check returns configured 4, present 3, missing port3.
Decision. The recipe threshold is 4 of 4, and 3 < 4: hold the cell start, latch the alarm with text that names port3, and keep polling so recovery shows on the first poll after the cable goes back. Had the recipe allowed 3-head degraded running, the same numbers would start the cell with the alarm latched. Set that threshold per recipe in your startup configuration, next to the port map.
Startup policy: hold, degrade, and what to log
- Reconnect window: 30 s, polled every 2 s. Heads and switch ports renegotiate link in roughly 2-5 s, so 30 s covers slow bring-up. Under 10 s you false-alarm on slow links; over 60 s the line idles on a fault an operator has already seen.
- Hold when the present count is below the recipe count. Refuse to start; the alarm text names the port.
- Degrade only when the recipe explicitly allows it. Latch the alarm; a retry loop must not clear it while the head is absent.
- Log the pair, for example "heads 3 of 4, missing port3", plus the saved-configuration identifier, so service can separate a stale save from a cut cable.
After startup, the same head can drop mid-run; that surfaces as empty callbacks, handled in null frames in a multi-camera callback, or as devices that must be reopened cleanly after a crash, covered in camera reinitialization after crash.
Size the controller for the head count the check guards
Four heads plus a service camera need five Ethernet camera ports with power over the cable, so one unplugged head never takes a planned camera port out of service. The MV-VC5525-256G70-NN carries 5+ LAN ports, PoE camera ports and 8 built-in light channels.
- LAN ports
- 5+ LAN ports
- USB 3 ports
- 3-4 USB 3 ports
- PoE
- PoE camera ports
- Light outputs
- 8 built-in channels
Both of these cover the common rig sizes: the MV-VC4712-256G10-2TH lists 3-4 LAN ports, PoE camera ports and 6 built-in light channels, and the MV-VC3301H-128G66 lists 5+ LAN ports, No PoE and 4 built-in light channels, with a VM6200Pro vision license included.
- LAN ports
- 3-4 LAN ports
- USB 3 ports
- 2 USB 3 ports
- LAN ports
- 5+ LAN ports
- USB 3 ports
- 3-4 USB 3 ports
Every head on those ports is one more entry the check must survive. The 3D sensors category lists laser profiler, stereo and structured-light heads, and the camera finder filters them by type; compare controllers in the vision controllers category.
Bench checks before the cell ships
- Pull-the-plug boot test. Disconnect one head, power-cycle the controller, start the application. Startup must finish with a named-port alarm, not a core dump.
- Swap test. Replace a head with a different model. The check must flag the identity change; this is the case where every field is non-null and still wrong.
- Cold save test. Save the port map, then repeat the pull-the-plug boot. The saved configuration is what creates the null, so this run proves the fix.
-
Assert names, not counts. A test that only checks
present == 3passes with the wrong port missing. - Alarm text carries the port name. "port3 head missing" sends a technician; "camera missing" sends a search.
- Keep the commissioning dump. Store the configuration dump - a few kB of JSON - with the cell file, so a stale save is provable in the field.
An enumeration failure with every head attached is a different animal: when nothing is unplugged but the SDK still cannot see a camera, start with camera in Device Manager but not in your SDK.
When the dying program is not the camera SDK
Stored-state crashes reach beyond camera enumeration. HMI and measurement software keeps workspaces and device lists too, and after a hardware change the saved state refuses to open with the same terminal error. The fix there is the vendor's own repair path, never a hand edit: for measurement software, this DAQ software startup-crash repair checklist walks the repair-and-reinstall sequence. If your process instead dies at startup on missing runtime libraries rather than a null field, the separation is laid out in vision application startup error diagnosis, and when two programs enumerate different device lists, camera listed in one app but not another explains the split.
If you are commissioning a multi-head rig now, run the pull-the-plug boot before the cell ships. To match the head count against a controller that has the ports for it, send the port map through the build-a-system configurator or contact the applications team; more startup and enumeration fixes are collected on the systems topic page.
Questions
Why does the crash appear only after a reboot with a camera unplugged?
The port-list call parses the configuration stored on the controller. While the rig runs, live state matches the saved state, so parsing succeeds. Pull a head and cycle power, and the saved configuration still expects it: the port entry survives with a null part-number field, and the parser throws on that null. A controller that was never given a saved configuration generally reports fewer ports instead.
Can I just wrap the port-list call in a try/catch and move on?
Catching stops the core dump, which matters, but on this failure the call yields no usable list, so you still cannot say which head is gone or how many remain. Treat the catch as a backstop for malformed entries and build the real head list from the configuration document, where each port's identity field is readable and checkable.
Does a null part-number field always mean a broken cable?
No. The same null appears when a head is swapped for a different model, when a port was configured for a head never re-attached, or when the saved configuration predates a hand change. Treat null as unverified rather than as a hardware fault: hold the reconnect window, then send service to the named port with the log line and the port indicator to match.
How do I remove the stale saved configuration from the controller?
Ask the vendor's tooling first: the SDK's dump and saved-init commands show exactly what is persisted. Many controllers offer no in-place clear short of a factory reset, and a reset also wipes user files and containers, so plan code that tolerates the null instead of relying on a clean wipe. Keep the commissioning dump on file so a stale save is provable.
What belongs in the startup log line when a head is missing?
Port name, the count pair such as 3 of 4 heads present, the saved-configuration identifier, and a timestamp. Field service then matches the log line to the port indicator on the controller without opening source code. Log the same line on recovery, and the recorded gap separates a cable fault from slow link bring-up within one shift.


