Problem with the Gev Server Node function "RegisterEventWrittenUpdated"

I want to use an IntegerNode with limiting values. The IntegerNode with its functions “SetMinConfig()” and “SetMaxConfig()” is suitable for this. However, the function “RegisterEventWrittenUpdated()” does not work in this node class. With the node class Int32RegNode, however, this works without problems and I get the values that the client changes in this node. Basically, I only need an integer node whose changed values I get in the Gev server, but whose value range is limited. I have listed both variants in the following example.

#include <iostream>
#include <filesystem>
#include <string>
#include <sys/types.h>
#include <sys/stat.h>
#include <cvb/gevserver/server.hpp>
#include <cvb/gevserver/stream.hpp>
#include <cvb/gevserver/node.hpp>
#include <cvb/data_type.hpp>
#include <cvb/utilities/system_info.hpp> 

Cvb::GevServer::ServerPtr server;
Cvb::GevServer::IntegerNodePtr intGainNode;
std::vector<std::unique_ptr<Cvb::Image>> img_name_vec;
std::vector<std::unique_ptr<Cvb::Image>> img_name_vec_original;
float oldGainValue = 0;

void editGainOfImagesInVector(const Cvb::GevServer::ValueNode&)
{
    int gainValue = intGainNode->Value() * 25.5;
    std::cout << "The gain value has changed to " << gainValue << std::endl;
    for (int i = 0; i < img_name_vec.size(); i++)
    {
        auto& raccess = img_name_vec[i]->Planes()[0].LinearAccess();
        for (int x = 0; x < img_name_vec[i]->Width(); x++)
        {
            for (int y = 0; y < img_name_vec[i]->Height(); y++)
            {
                std::uint8_t rValueNewVec = img_name_vec_original[i]->Planes()[0].LinearAccess().Value<std::uint8_t>(x, y);
                if ((rValueNewVec + gainValue) > 255) raccess.Value<std::uint8_t>(x, y) = 255;
                else raccess.Value<std::uint8_t>(x, y) = rValueNewVec + gainValue;
            }
        }
    }
}
void AddGenICamFeatures()
{
    auto rootNode = server->NodeMap()->Node(CVB_LIT("DeviceControl"));
    intGainNode = Cvb::GevServer::IntegerNode::Create(CVB_LIT("Std::Gain"));
    server->NodeMap()->AddNode(intGainNode);
    intGainNode->SetToolTip(CVB_LIT("Gain factor"));
    intGainNode->SetDisplayName(CVB_LIT("Gain"));
    intGainNode->SetDescription(CVB_LIT("Sets the selected gain as an amplification factor applied to the image."));
    intGainNode->SetMinConfig(std::int64_t(1));
    intGainNode->SetMaxConfig(std::int64_t(10));
    intGainNode->SetValue(1);
    intGainNode->SetVisibility(Cvb::GenApi::Visibility::Beginner);
    intGainNode->SetIsStreamable(1);
    intGainNode->RegisterEventWrittenUpdated([](const auto& value)
        {
            if (oldGainValue != intGainNode->Value())
            {
                editGainOfImagesInVector(value);
                oldGainValue = intGainNode->Value();
            }
        });
    rootNode->Add(intGainNode, Cvb::GevServer::NodeList::Child);
}
int main(int argc, char* argv[])
{
    auto path = Cvb::InstallPath();
    path += CVB_LIT("Tutorial/Clara.bmp");
    path = Cvb::ExpandPath(path);
    try
    {
    //*** Load the images from the given path in a vector***
    auto img = Cvb::Image::Load(path);
    auto img2 = Cvb::Image::Load(path);
    img_name_vec.push_back(std::move(img));
    img_name_vec_original.push_back(std::move(img2));
        // Create server
        server = Cvb::GevServer::Server::CreateWithConstSize(img_name_vec[0]->Size(),
            Cvb::ColorModel::RGBGuess,
            Cvb::DataType::FromNativeType<bool>(),
            static_cast<Cvb::GevServer::DriverType>(0));
        // Create NodeMap and add Nodes
        AddGenICamFeatures();
        oldGainValue = intGainNode->Value();
        // This is required to setup becouse of overwriting the old NodeMap from the client
        server->SetUserVersion("C++ Gev Server");
        server->NodeMap()->SetXmlFileVersion(Cvb::GenApi::NodeMap::GenApiVersion(1, 5, 1));
        // Get connection IP address
        auto address = Cvb::GevServer::LogicalNetworkInterface::GetAllAvailable().at(0).IPAddress();
        // Start server
        server->Start(address);
        std::cout << "Started server on IP address " << address;
        // Set the resend-buffer to zero
        server->Stream()->SetResendBuffersCount(0);
        //+++++++ Send the image ++++++++
        auto vec_index = img_name_vec.begin();
        while (true)
        {
            try
            {
                server->Stream()->Send(**vec_index);
                vec_index++;
                if (vec_index == img_name_vec.end()) vec_index = img_name_vec.begin();
            }
            catch (const std::exception& error) {}
        }
        return 0;
    }
    catch (const std::exception& error)
    {
        std::cout << error.what() << std::endl;
    }
}

In this small example the IntegerNode was used, here the “RegisterEventWrittenUpdated” function does not work.

#include <iostream>
#include <filesystem>
#include <string>
#include <sys/types.h>
#include <sys/stat.h>
#include <cvb/gevserver/server.hpp>
#include <cvb/gevserver/stream.hpp>
#include <cvb/gevserver/node.hpp>
#include <cvb/data_type.hpp>
#include <cvb/utilities/system_info.hpp> 

Cvb::GevServer::ServerPtr server;
Cvb::GevServer::Int32RegNodePtr intGainNode;
std::vector<std::unique_ptr<Cvb::Image>> img_name_vec;
std::vector<std::unique_ptr<Cvb::Image>> img_name_vec_original;
float oldGainValue = 0;

void editGainOfImagesInVector(const Cvb::GevServer::ValueNode&)
{
    int gainValue = intGainNode->Value() * 25.5;
    std::cout << "The gain value has changed to " << gainValue << std::endl;
    for (int i = 0; i < img_name_vec.size(); i++)
    {
        auto& raccess = img_name_vec[i]->Planes()[0].LinearAccess();
        for (int x = 0; x < img_name_vec[i]->Width(); x++)
        {
            for (int y = 0; y < img_name_vec[i]->Height(); y++)
            {
                std::uint8_t rValueNewVec = img_name_vec_original[i]->Planes()[0].LinearAccess().Value<std::uint8_t>(x, y);
                if ((rValueNewVec + gainValue) > 255) raccess.Value<std::uint8_t>(x, y) = 255;
                else raccess.Value<std::uint8_t>(x, y) = rValueNewVec + gainValue;
            }
        }
    }
}

void AddGenICamFeatures()
{
    auto rootNode = server->NodeMap()->Node(CVB_LIT("DeviceControl"));
    intGainNode = Cvb::GevServer::Int32RegNode::Create(CVB_LIT("Std::Gain"));
    server->NodeMap()->AddNode(intGainNode);
    intGainNode->SetToolTip(CVB_LIT("Gain factor"));
    intGainNode->SetDisplayName(CVB_LIT("Gain"));
    intGainNode->SetDescription(CVB_LIT("Sets the selected gain as an amplification factor applied to the image."));
    intGainNode->SetValue(1);
    intGainNode->SetVisibility(Cvb::GenApi::Visibility::Beginner);
    intGainNode->SetIsStreamable(1);
    intGainNode->RegisterEventWrittenUpdated([](const auto& value)
        {
            if (oldGainValue != intGainNode->Value())
            {
                oldGainValue = intGainNode->Value();
                editGainOfImagesInVector(value);   
            }
        });
    rootNode->Add(intGainNode, Cvb::GevServer::NodeList::Child);
}

int main(int argc, char* argv[])
{
    auto path = Cvb::InstallPath();
    path += CVB_LIT("Tutorial/Clara.bmp");
    path = Cvb::ExpandPath(path);
    try
    {
        //*** Load the images from the given path in a vector***
        auto img = Cvb::Image::Load(path);
        auto img2 = Cvb::Image::Load(path);
        img_name_vec.push_back(std::move(img));
        img_name_vec_original.push_back(std::move(img2));
        // Create server
        server = Cvb::GevServer::Server::CreateWithConstSize(img_name_vec[0]->Size(),
            Cvb::ColorModel::RGBGuess,
            Cvb::DataType::FromNativeType<bool>(),
            static_cast<Cvb::GevServer::DriverType>(0));
			
        // Create NodeMap and add Nodes
        AddGenICamFeatures();
        oldGainValue = intGainNode->Value();
        std::cout << std::endl << "Old Gain Value: "<< oldGainValue << std::endl;
		
        // This is required to setup becouse of overwriting the old NodeMap from the client
        server->SetUserVersion("C++ Gev Server");
        server->NodeMap()->SetXmlFileVersion(Cvb::GenApi::NodeMap::GenApiVersion(1, 7, 1));
		
        // Get connection IP address
        auto address = Cvb::GevServer::LogicalNetworkInterface::GetAllAvailable().at(0).IPAddress();
		
        // Start server
        server->Start(address);
        std::cout << "Started server on IP address " << address;
		
        // Set the resend-buffer to zero
        server->Stream()->SetResendBuffersCount(0);
		
        //+++++++ Send the image ++++++++
        auto vec_index = img_name_vec.begin();
        while (true)
        {
            try
            {
                server->Stream()->Send(**vec_index);
                vec_index++;
                if (vec_index == img_name_vec.end()) vec_index = img_name_vec.begin();
            }
            catch (const std::exception& error) {}
        }
        return 0;
    }
    catch (const std::exception& error)
    {
        std::cout << error.what() << std::endl;
    }
}

In this example, the function “RegisterEventWrittenUpdated” works, but I cannot limit the value range with the Int32RegNode.

I have another, additional question about nodes.
I have read the nodemap of our camera and discovered there different types of nodes, which I don’t find in the gevServer class. So I can’t pass them from the client to the camera. There is a node of type Converter listed in the XML of the camera. I assume this is used since there are no nodes of type Float. Is there any way to rebuild or map this with the Gev server so that this appears afterward as it does with the camera for the client(frame grabber)?

I have attached the part of the read xml in this comment.

<Converter Name="cnvAcquisitionLineRate" NameSpace="Standard">
 <FormulaTo>FROM * 1000</FormulaTo>
 <FormulaFrom>TO / 1000</FormulaFrom>
 <pValue>AcquisitionLineRateRaw</pValue>
 <Representation>Linear</Representation>
 <Slope>Increasing</Slope>
 <IsLinear>No</IsLinear>
</Converter>
<Float Name="AcquisitionLineRate" NameSpace="Standard">
 <ToolTip>Line rate in Hz</ToolTip>
 <Description>Specifies the camera internal line rate
 <DisplayName>Acquisition Line Rate</DisplayName>
 <Visibility>Beginner</Visibility>
<!--
 <pIsLocked>exposureIsTriggerWidth</pIsLocked>
-->
 <Streamable>Yes</Streamable>
 <pValue>cnvAcquisitionLineRate</pValue>
 <Unit>Hz</Unit>
 <Representation>Linear</Representation>
 <DisplayPrecision>0</DisplayPrecision>
</Float>

Hi @ASchmitt,

you are right, the Integer Node only implements the functionality to access and work with the values. The Integer Register Node has the methods to work with the actual register and manipulate it, including callback registration. The trick is, to create a register node which holds the value and registers the callback on a value change. Additionally, you would need to create an Integer Node and as value register the Integer Register Node. In this way, you can work on the Integer Node for MinConfig() and other values, while the Integer Register changed callback still fires on changes on the IntRegNode.
You can adopt one of your examples as follows:

...
Cvb::GevServer::IntegerNodePtr intGainNode;
Cvb::GevServer::Int32RegNodePtr intRegGainNode;
...

void AddGenICamFeatures()
{
  auto rootNode = server->NodeMap()->Node(CVB_LIT("DeviceControl"));
  
  intRegGainNode = Cvb::GevServer::Int32RegNode::Create(CVB_LIT("Std::GainReg"));
  server->NodeMap()->AddNode(intRegGainNode);
  intRegGainNode->SetVisibility(Cvb::GenApi::Visibility::Invisible);
  intRegGainNode->RegisterEventWrittenUpdated([](const auto& value)
    {
      if (oldGainValue != intGainNode->Value())
      {
        editGainOfImagesInVector(value);
        oldGainValue = intGainNode->Value();
      }
    });

  intGainNode = Cvb::GevServer::IntegerNode::Create(CVB_LIT("Cust::Gain"));
  server->NodeMap()->AddNode(intGainNode);
  intRegGainNode->Add(intGainNode, Cvb::GevServer::NodeList::Addresses);

  intGainNode->SetDisplayName(CVB_LIT("Gain"));
  intGainNode->SetToolTip(CVB_LIT("Gain factor"));
  intGainNode->SetDescription(CVB_LIT("Sets the selected gain as an amplification factor applied to the image."));
  intGainNode->SetMinConfig(minVal);
  intGainNode->SetMaxConfig(std::int64_t(10));
  intGainNode->SetValue(1);
  intGainNode->SetVisibility(Cvb::GenApi::Visibility::Beginner);

  intGainNode->SetValueConfig<Cvb::GevServer::IntegerBaseNodePtr>(intRegGainNode);
  rootNode->Add(intGainNode, Cvb::GevServer::NodeList::Child);
}

Regarding your second question, the Converter Node is currently not implemented as it is a more specific feature which is normally not needed for GevServer use and only makes sense to have on a camera to add computations. The best current fit would be a SwissKnifeNode, which does the same as the converter node, but only in one direction.
If you need to recreate the camera xml, maybe you could do the Converter computations in code and update the nodes accordingly.
So you would display or set the AcquisitionLineRate divided by 1000 and use it in code as normal. This should have the same effect, both for the GevServer implementation and for the camera user end.

2 Likes

Thank you very much for the answer, merging both properties of the nodes worked, perfectly. Is there a way to create a node that allows float values to be entered? The problem is that with the nodes that are currently there, I can’t enter commas or set the range of values with commas.

Yes, use the FloatNode.

But there is no Float Node in GevServer Nodes.
https://help.commonvisionblox.com/API/C++/namespace_cvb_1_1_gev_server.html

Or have I missed something here?

No, you are right. Sorry, we did not wrap the C++ GevServer float nodes yet. Only the C-API float nodes are currently available. We will add them to the C++, .NET and Python wrappers soon. I will get back to you when it is available.

1 Like

Perfect, thank you, Nico, for your great help.
Thanks to you, a part is already running.

I have another question about the EnumerationNodes.
I added several EnumEntryNodes to one EnumerationNode. Is there a way to set one of these EnumEntryNodes as initial value in the EnumerationNode. The documentation says that it only works with nodes that inherit from the IntegerNode with the function SetValueConfig() I guess.
Another question about the EnumerationNodes, is there a function to find out which of the EnumEntryNodes is currently selected while the Gev Server is running?

Thanks for the praise :smiley:
If I understand you correctly, you want to change the default value without setting up an additional integer node. If so, you could just do:

enumerationNode->SetValueConfig(std::int64_t(2));

Which would set the third added enumEntryNode as the starting value.

As for your second question:
Yes, you can react to a change in the selected enum entry and get the value the node is set to. I would suggest to have a look at the QmlGevServer example which should come with your CVB installtion.
Basicially you would not set the single value as starting value but set an integer node. Additionally you would need to register a callback on the integer node. When the user then changes the enum entry using the supplied dropdown menu in the GenICamBrowser for example, the index value of the enumeration node is changed to the newly selected enum entry node. Since we then have an integer node registered to hold the enumeration node’s current index, the callback on this integer node is triggered. Within the callback, you have access to the integer node’s current value. In the QmlGevServer example, look for these parts:

...
windowStateRegNode_ = Cvb::GevServer::Int32RegNode::Create(CVB_LIT("Cust::WindowStateReg"));
...
 // Set default startup value
windowStateRegNode_->SetValue(1);
// Set event to react to changes of the enumeration node's current selection
windowStateRegNode_->RegisterEventWrittenUpdated(
      [this](const Cvb::GevServer::ValueNode &value) { OnWindowSizeChanged(value); });
...
// Callback to react to enumeration index change
void BackEnd::OnWindowSizeChanged(const Cvb::GevServer::ValueNode & /*value*/)
{
  // Switch on the current user selected value
  switch (windowStateRegNode_->Value())
  {
  case 0:
    view_->showMinimized();
    break;
  case 1:
...
  default:
    throw std::runtime_error("Invalid window state value");
    break;
  }
}

I have now understood the SetValueConfig() function.
I had looked at the example from the tutorial on this. I think I have to change my question a bit. In an EnumerationNode I have several EnumEntryNode which can be selected. Another IntegerNode is set and when this happens, I want to know which EnumEntryNode was selected in the Enumeration node.
Short example:

//Setup EnumerationNode with Entry Nodes
global_enumNodeGainSelec = Cvb::GevServer::EnumerationNode::Create(CVB_LIT("Std::GainSelector"));
auto enumEntNodeDigiAllNode = Cvb::GevServer::EnumEntryNode::Create(CVB_LIT("std::DigitalAll"));
auto enumEntNodeDigiRedNode = Cvb::GevServer::EnumEntryNode::Create(CVB_LIT("std::DigitalRed"));
auto enumEntNodeDigiGreenNode = Cvb::GevServer::EnumEntryNode::Create(CVB_LIT("std::DigitalGreen"));
auto enumEntNodeDigiBlueNode = Cvb::GevServer::EnumEntryNode::Create(CVB_LIT("std::DigitalBlue"));

global_server->NodeMap()->AddNode(global_enumNodeGainSelec);
global_enumNodeGainSelec->SetDisplayName(CVB_LIT("Gain Selector"));
global_enumNodeGainSelec->SetValueConfig(std::int64_t(0));
// I want to know which of them is selected when the function intGainRegNode->RegisterEventWrittenUpdated() is triggered
	enumEntNodeDigiAllNode->SetNumericValue(1);
	global_enumNodeGainSelec->Add(enumEntNodeDigiAllNode, Cvb::GevServer::NodeList::Child);

	enumEntNodeDigiRedNode->SetNumericValue(2);
	global_enumNodeGainSelec->Add(enumEntNodeDigiRedNode, Cvb::GevServer::NodeList::Child);

	enumEntNodeDigiGreenNode->SetNumericValue(3);
	global_enumNodeGainSelec->Add(enumEntNodeDigiGreenNode, Cvb::GevServer::NodeList::Child);

	enumEntNodeDigiBlueNode->SetNumericValue(4);
	global_enumNodeGainSelec->Add(enumEntNodeDigiBlueNode, Cvb::GevServer::NodeList::Child);

//Setup Gain value node

auto intGainRegNode = Cvb::GevServer::Int32RegNode::Create(CVB_LIT("Std::GainReg"));
global_server->NodeMap()->AddNode(intGainRegNode);
intGainRegNode->SetVisibility(Cvb::GenApi::Visibility::Invisible);
intGainRegNode->RegisterEventWrittenUpdated([](const auto& value)
	{
		if (global_oldGainValue != global_intGainNode->Value())
		{
			std::thread{ editGainOfImagesInVector }.detach();
			global_oldGainValue = global_intGainNode->Value();
		}
	});
	
global_intGainNode = Cvb::GevServer::IntegerNode::Create(CVB_LIT("Std::Gain"));
global_server->NodeMap()->AddNode(global_intGainNode);
global_intGainNode->SetMinConfig(std::int64_t(0));
global_intGainNode->SetMaxConfig(std::int64_t(255));
global_intGainNode->SetValue(50);
global_intGainNode->SetValueConfig<Cvb::GevServer::IntegerBaseNodePtr>(intGainRegNode);

Hi @ASchmitt :slight_smile:

after reading @usernv answer, I dont quite get at which point you are struggling.

There are three ways your enumeration node might change its selected item:

  1. When you newly set it up → SetValueConfig defines which element is selected

  2. When the user changes the value → Register callback and store the value of the selected item or whatever you would like to do then

  3. At any time just out of curiosity or because you need to know what value was set without reacting to each change → Node->Value()

Cheers
Chris

Hi @Chris

To your third point, with EnumerationNode->value() I get the name or the string of the EnumerationEntryNode but not the value I set with EnumerationEntryNode->SetNumericValue(2). I could resolve the string afterwards and then convert it back to a number, but I was hoping there would be an easier way here.

I have now also tried SetValueConfig. Unfortunately none of the nodes are preselected, or I’m still doing something wrong here. I will look into this again.

Ah, I see.
I am not sure on this one but I think there is no builtin way in C++ to access an enum using the entry as well as its integer value as you do in C#.
I guess you would have to do the conversion yourself using a std::map or an array.

Cheers
Chris

I also just realized that I can’t regest the changed value of the EnumerationNode that I changed via the GenICam browser.
My approach:
I have an IntegerNode with a RegisterEventWrittenUpdated() function and an EnumerationNode with several EnumEntyNodes.
I set the value of the EnumerationNode in the GenICam Browser by selecting one of the EnumEntryNodes.
Then I change the value of the IntegerNode.
The IntegerNode registers a change of the value via the RegisterEventWrittenUpdated() function and is forwarded to a custom sub-function.
In this subfunction then the value of the IntegerNode is queried → This changed value is recognized correctly;
the value of the EnumerationNode is queried → the changed value is not recognized, the default value of SetValueConfig is displayed.

Hi @ASchmitt

I dont get what you try to achieve.

If I understand you correctly you are saying, that when you change the value of an integer node, the callback method gets triggered correctly and the value of the integer node is changed to the correct value after this routine.

This does not apply to the enumeration node?!

No, it might help if I describe the scenario.
I have an IntegerNode that I want to use to change the gain of an image and I have an EnumerationNode that contains the information from which channel the gain should be changed.
The first step is to set which channel to change via the EnumerationNode.
In the second step, the Gain (IntegerNode) is used to set by how much the gain of the channel should be changed.
The IntegerNode has a RegisterEventUpdated() function. This is now triggered when the gain is changed.
If this happens, the value of the Enumeration node is queried with EnumerationNode->Value() to get the information about the selected channel → but here it comes to the error, because the value set via the GenICam Browser is not displayed.
With IntegerNode-Value() the set value of the gain is queried–> this also works, the value that was set in the GenICam Browser is there.

So your problem is, that the enumeration node does not update the value correctly? You change the value of the enumeration node and when at some point you read it, it is still at its default value?

Yes, that is the problem I face.

Do you get the changes in the enumeration node within its callback? If so, a workaround could be to have a property that is changed accordingly.