I am using an external trigger.
When operating according to the sequence below, the system stops responding to the Trigger command.
It seems this issue did not occur with SDK version 4.0.1502.
Issue a Trigger command.
Issue a Retrieve command with a shortened timeout duration.
Repeatedly issue Retrieve commands at high frequency for several seconds.
Terminate the process without a trigger occurring.
Issue a Trigger command again; the system fails to return from the Trigger command call.
I have attached the log from that instance.
What is happening here? Could you please advise on a solution?
thank you for your report. This behaviour is indeed strange, I will have to investigate. It would greatly help me reproduce this if you could also attach:
A JSON containing your camera settings. The settings.json in your zip only contains NxTreeEdit settings.
A log from the terminated triggering process.
A sample program that reproduces this behaviour, if you already have it.
Camera configuration file
I have attached the file (.json).
This is the same file I used with SDK version 4.0.1502, and I am currently writing this same file to the camera for use.
Regarding the mention of “A log from the terminated triggering process” in your reply: I had understood that the file tmp_NxTreeEdit20260929.enslog (extracted from the ZIP file I initially attached) was the log in question. Could you please provide instructions on how to obtain the specific log you are referring to?I am re-attaching the file just in case.
Sorry for the confusion, that was my mistake. I misunderstood your problem description and ths did not examine your log carefully enough. I thought that with “Terminate the process without a trigger occurring.” you meant that you are actually terminating the OS process, and I did not see the inital Trigger/Retrieve in the log. But now I know that everything is happening in a single process.
I managed to reproduce your observation using the following minimal snippet:
The second Trigger command has the Subset parameter set to All, so it issues a trigger request to a camera that is already triggered, i.e., still processing the previous trigger request.
Between versions 4.0.1502 and 4.3.1057, we changed the Trigger command: previously, it enqueued a trigger request and returned immediately. Now, by default, it waits until the current trigger request is actually being processed. This improves error reporting: trigger errors used to surface only later, in a Retrieve call. The drawback is that a second Trigger command now hangs, because the camera is still busy processing the request from the first one.
There are two ways you can fix this:
You can either restore the old behavior by setting the WaitFor parameter to None. The documentation for this parameter is currently incorrect, which we will fix for the upcoming release, but I have referred to the actual implementation:
default: Wait until the request is being processed.
None: Queue the request, return immediately.
Exposure: Wait until the exposure is actually over.
There are two errors in the linked documentation:
It incorrectly claims that the default is None.
The documented behavior for None is actually the (unnamed) default.
If you set WaitFor to None on the second Trigger command, the command will enqueue another trigger request on the camera and return, while the camera continues to process the first one:
NxLibCommand open(cmdOpen);
open.parameters()[itmCameras] = stereo;
open.execute();
NxLibItem()[itmCameras][stereo].setJson(readTextFile("cam1.json"), true);
fmt::print("First trigger works\n");
NxLibCommand(cmdTrigger).execute();
fmt::print("Enqueue a second trigger, keep processing the first\n");
NxLibCommand trigger(cmdTrigger);
trigger.parameters()[itmSubset] = valAll;
trigger.parameters()[itmWaitFor] = valNone;
trigger.execute();
fmt::print("Alright?\n");
Or you can manually abort the current trigger request and enqueue a new one. The only reliable way I found to manually abort a trigger request is to change the TriggerMode:
My guess is that you should abort the current trigger, since it has apparently failed for some reason. Also, if you just keep enqueueing requests for a while, they will pile up.
Thank you for your reply. I will give it a try and report the results. I have one question after looking at the code you provided:
If both the stereo camera and the color camera are open, do I need to change the TriggerMode and reconfigure both of them?
Or is it sufficient to change just one of them (e.g., changing only the stereo camera)?
I would appreciate it if you could clarify this point. Thank you.
Could you please provide some more details regarding the timing for changing the TriggerMode?
I am using code similar to the following to issue a trigger and wait via the Retrieve command; if the cancel button on the screen is pressed, the function exits, and I execute the function again when I want to capture another image.
When the cancel button is pressed, I call the cancel function for the Retrieve command. Should I write the code to change the TriggerMode immediately after that cancel call?
Before change
NxLibCommand trigger(cmdTrigger);
trigger.parameters()[itmSubset] = valAll;
trigger.execute();
NxLibCommand retrieve(cmdRetrieve);
retrieve.parameters()[itmTimeout] = -1;
retrieve.parameters()[itmCameras] = m_serial;
retrieve.execute(static_cast<bool>(false));
while (1) {
std::this_thread::sleep_for(std::chrono::milliseconds(10));
if (retrieve.finished()) {
retrieve.assertSuccessful();
assert(retrieve.result()[m_serial][itmRetrieved].asBool());
bool rc2 = retrieve.result()[m_serial][itmRetrieved].asBool();
if (rc2) {
}
}
if(cancel){
retrieve.cancel();
while (true) {
if (retrieve.finished())
return(1);
}
}
}
HI
When testing the new SDK in the actual operating environment, I observed that occasionally (about once in every ten attempts), an extra image capture is triggered immediately after the target object has been imaged following an external trigger signal.
I did not intentionally issue a trigger command. Furthermore, the issue occurred with the same frequency even when the WaitFor parameter was set to None. I have attached the enslog files from these instances; the logs capture the process starting from normal operation and ending when the issue occurred.
To be thorough, I recorded logs for two separate instances of this situation.
Could you please explain what is causing this issue and how it can be resolved?
Thank you for your assistance.
P.S.
I tried switching the SDK to version V4.0.1502, and the frequency of the issue dropped significantly—to about once in every 100 attempts.
Do you think there might be a specific reason why the SDK makes such a clear difference?
That depends on your requirements. If you are only interested in triggers that arrive while the function is executing, you could set the TriggerMode when entering and even reset it when exiting.
// Reset either here ...
NxLibCommand trigger(cmdTrigger);
trigger.parameters()[itmSubset] = valAll;
trigger.execute();
NxLibCommand retrieve(cmdRetrieve);
retrieve.parameters()[itmTimeout] = -1;
retrieve.parameters()[itmCameras] = m_serial;
retrieve.execute(static_cast<bool>(false));
while (1) {
std::this_thread::sleep_for(std::chrono::milliseconds(10));
if (retrieve.finished()) {
retrieve.assertSuccessful();
assert(retrieve.result()[m_serial][itmRetrieved].asBool());
bool rc2 = retrieve.result()[m_serial][itmRetrieved].asBool();
if (rc2) {
}
}
if(cancel){
retrieve.cancel();
// ... or here
while (true) {
if (retrieve.finished())
return(1);
}
}
}
I have too look into that. One thing I noticed is that in both logs, the first packed of the additinal image arrives very precisely 1.4s after the first packet from the previous frame, which does not seem like a coincidence and rules out excessive line bounce for now.
How are you generating your trigger signals?
I am very surprised that changing the SDK is supposed to make such a big difference, as I was expecting the issue to be more with the camera firmware or the setup. Did you also change the camera firmware? Can you also send me a log from SDK 4.0.1502, please?
I have connected the DO1 output of the KEYENCE PR-G51CP to the start signal terminals (2-6) of the OMRON H3CR-AP, and connected the NC contacts (1-4) to the trigger signal input of the C57.
(Configured for falling-edge operation)
The H3CR-AP is set to “E mode” with a duration of approximately 1.2 seconds.
The camera firmware has also been changed (downgraded).
The issue occurs infrequently with SDK 4.0.1502, so I have not yet been able to capture a log; I will send it once I have obtained one.
Could you also tell me the minimum duration for which the hardware trigger signal must remain ON?
Can you please tell me the firmware versions you have been using with each SDK version?
Assuming that the camera does internally execute an additional trigger, I build the following minimal example, which uses another camera to trigger, but hopefully captures all other necessary details:
Example
// Hardware trigger test:
// The stereo camera captures on the falling edge of its input signal, which is
// wired to the output of a second camera (the "trigger camera"). The trigger
// camera only toggles its output line, it never captures. The color camera of
// the stereo camera runs on its own internal trigger.
//
// Usage: hardware-trigger-test <stereo-serial> <trigger-camera-serial>
// The color camera serial is derived as <stereo-serial>-Color.
#include "nxLib.h"
#include "nxLibConfig.h"
#include <chrono>
#include <cstdio>
#include <string>
#include <thread>
using namespace std::chrono_literals;
int main(int argc, char** argv)
try {
if (argc < 3) {
std::printf(
"Usage: %s <stereo-serial> <trigger-camera-serial>\nThe color camera serial is derived as "
"<stereo-serial>-Color\n",
argc > 0 ? argv[0] : "hardware-trigger-test"
);
return 1;
}
// Enable trace level debug messages before doing anything else
NxLibConfigItem()[cfgDebug][cfgLevel] = valTrace;
int port;
nxLibOpenTcpPort(0, &port);
std::printf("Port %d\n", port);
nxLibInitialize(true);
std::printf("Hardware trigger test\n");
std::string stereoSerial = argv[1];
std::string colorSerial = stereoSerial + "-Color";
std::string triggerSerial = argv[2];
NxLibItem stereoCamera = NxLibItem()[itmCameras][stereoSerial];
NxLibItem colorCamera = NxLibItem()[itmCameras][colorSerial];
NxLibItem triggerCamera = NxLibItem()[itmCameras][triggerSerial];
{
NxLibCommand open(cmdOpen);
open.parameters()[itmCameras][0] = stereoSerial;
open.parameters()[itmCameras][1] = colorSerial;
open.parameters()[itmCameras][2] = triggerSerial;
open.execute();
}
// The stereo camera captures on the falling edge of its input signal (wired
// to the trigger camera's output)
stereoCamera[itmParameters][itmCapture][itmTriggerMode] = valFallingEdge;
// Disable the recalibrator
stereoCamera[itmParameters][itmCapture][itmUseRecalibrator] = false;
// The color camera runs on its own internal trigger
colorCamera[itmParameters][itmCapture][itmTriggerMode] = valInternal;
// Enable binning 2
if (stereoCamera[itmParameters][itmCapture][itmBinning].exists()) {
stereoCamera[itmParameters][itmCapture][itmBinning] = 2;
}
if (colorCamera[itmParameters][itmCapture][itmBinning].exists()) {
colorCamera[itmParameters][itmCapture][itmBinning] = 2;
}
// Set FlexView to 3 frames
if (stereoCamera[itmParameters][itmCapture][itmFlexView].exists()) {
stereoCamera[itmParameters][itmCapture][itmFlexView] = 3;
}
// Disable automatic exposure and fix it to 1 ms on all cameras that get
// triggered (the trigger camera only toggles its IO and never captures)
for (auto [name, cameraNode] : {std::pair{"Stereo", stereoCamera}, std::pair{"Color", colorCamera}}) {
auto capture = cameraNode[itmParameters][itmCapture];
if (capture[itmExposure][itmAutomatic].exists()) {
capture[itmExposure][itmAutomatic] = false;
capture[itmExposure][itmValue] = 1.0;
std::printf("%s: automatic exposure disabled, exposure fixed to 1 ms\n", name);
}
}
// Be nice to office colleagues: disable projector and front light
for (auto [name, node] :
{std::pair{"Projector", stereoCamera[itmParameters][itmCapture][itmProjector]},
std::pair{"Front light", colorCamera[itmParameters][itmCapture][itmFrontLight]}}) {
if (node.exists()) {
node = false;
std::printf("%s disabled\n", name);
} else {
std::printf("%s not available\n", name);
}
}
// The trigger camera's output line is toggled by hand and wired to the
// stereo camera's input, so it captures on the falling edge
triggerCamera[itmParameters][itmIO][itmOutput][itmMode] = valLow;
int c = 0;
while (true) {
{
NxLibCommand trigger(cmdTrigger);
trigger.parameters()[itmCameras][0] = stereoSerial;
trigger.parameters()[itmCameras][1] = colorSerial;
trigger.parameters()[itmSubset] = valAll;
trigger.execute();
}
{
NxLibCommand retrieve(cmdRetrieve);
retrieve.parameters()[itmCameras] = stereoSerial;
retrieve.parameters()[itmTimeout] = -1;
retrieve.executeAsync();
std::this_thread::sleep_for(0.25s);
if (retrieve.finished()) {
abort();
}
// Rising edge, then falling edge: the falling edge triggers the stereo camera
triggerCamera[itmParameters][itmIO][itmOutput][itmMode] = valHigh;
triggerCamera[itmParameters][itmIO][itmOutput][itmMode] = valLow;
retrieve.wait();
retrieve.assertSuccessful();
}
{
NxLibCommand retrieve(cmdRetrieve);
retrieve.parameters()[itmCameras] = colorSerial;
retrieve.parameters()[itmTimeout] = -1;
retrieve.execute();
}
c += 1;
std::printf("%d\n", c);
}
nxLibFinalize();
return 0;
} catch (NxLibException const& e) {
std::printf("NxLib API error %d (%s) occurred", e.getErrorCode(), e.getErrorText().c_str());
auto path = e.getItemPath();
if (!path.empty()) {
std::printf(" while accessing item '%s'", path.c_str());
}
std::printf("\n");
if (e.isCommandExecutionFailure()) {
std::printf("Command error: %s: %s\n", e.getCommandErrorSymbol().c_str(), e.getCommandErrorText().c_str());
}
nxLibFinalize();
return 1;
}